ableton_link/
lib.rs

1//! An overview of Link concepts can be found at
2//! [http://ableton.github.io/link](http://ableton.github.io/link).
3//! 
4//! Then see the doc of [Link](struct.Link.html) and
5//! [SessionState](struct.SessionState.html).
6//! Most of it is directly taken from the original Link docs.
7//! 
8//! All i64 time values are in microseconds and should be used with the
9//! [Clock](struct.Clock.html).
10
11#[allow(non_camel_case_types)]
12#[allow(non_snake_case)]
13mod sys {
14    include!(concat!(env!("OUT_DIR"), "/link_rs.rs"));
15}
16
17use sys::*;
18use std::os::raw::c_void;
19
20/// # Represents a participant in a Link session.
21/// 
22/// Each Link instance has its own session state which
23/// represents a beat timeline and a transport start/stop state. The
24/// timeline starts running from beat 0 at the initial tempo when
25/// constructed. The timeline always advances at a speed defined by
26/// its current tempo, even if transport is stopped. Synchronizing to the
27/// transport start/stop state of Link is optional for every peer.
28/// The transport start/stop state is only shared with other peers when
29/// start/stop synchronization is enabled.
30/// 
31/// A Link instance is initially disabled after construction, which
32/// means that it will not communicate on the network. Once enabled,
33/// a Link instance initiates network communication in an effort to
34/// discover other peers. When peers are discovered, they immediately
35/// become part of a shared Link session.
36/// 
37/// Each method of the Link type documents its thread-safety and
38/// realtime-safety properties. When a method is marked thread-safe,
39/// it means it is safe to call from multiple threads
40/// concurrently. When a method is marked realtime-safe, it means that
41/// it does not block and is appropriate for use in the thread that
42/// performs audio IO.
43/// 
44/// Link provides one session state capture/commit method pair for use
45/// in the audio thread and one for all other application contexts. In
46/// general, modifying the session state should be done in the audio
47/// thread for the most accurate timing results. The ability to modify
48/// the session state from application threads should only be used in
49/// cases where an application's audio thread is not actively running
50/// or if it doesn't generate audio at all. Modifying the Link session
51/// state from both the audio thread and an application thread
52/// concurrently is not advised and will potentially lead to unexpected
53/// behavior.
54pub struct Link {
55    wlink: *mut WLink,
56}
57
58impl Drop for Link {
59    fn drop(&mut self) {
60        unsafe { Link_destroy(self.wlink) }
61        // println!("Link destroyed!")
62    }
63}
64
65impl Link {
66    /// Construct with an initial tempo.
67    pub fn new(bpm: f64) -> Link {
68        Link { wlink: unsafe { Link_create(bpm) } }
69    }
70
71    /// Is Link currently enabled?
72    /// * Thread-safe: yes
73    /// * Realtime-safe: yes
74    pub fn is_enabled(&self) -> bool {
75        unsafe { Link_isEnabled(self.wlink) }
76    }
77
78    /// Enable/disable Link.
79    /// * Thread-safe: yes
80    /// * Realtime-safe: no
81    pub fn enable(&mut self, enable: bool) {
82        unsafe { Link_enable(self.wlink, enable) }
83    }
84
85    /// Is start/stop synchronization enabled?
86    /// * Thread-safe: yes
87    /// * Realtime-safe: no
88    pub fn is_start_stop_sync_enabled(&self) -> bool {
89        unsafe { Link_isStartStopSyncEnabled(self.wlink) }
90    }
91
92    /// Enable start/stop synchronization.
93    /// * Thread-safe: yes
94    /// * Realtime-safe: no
95    pub fn enable_start_stop_sync(&mut self, enable: bool) {
96        unsafe { Link_enableStartStopSync(self.wlink, enable) }
97    }
98
99    /// How many peers are currently connected in a Link session?
100    /// * Thread-safe: yes
101    /// * Realtime-safe: yes
102    pub fn num_peers(&self) -> usize {
103        unsafe { Link_numPeers(self.wlink) }
104    }
105
106    /// Register a callback to be notified when the number of
107    /// peers in the Link session changes.
108    /// * Thread-safe: yes
109    /// * Realtime-safe: no
110    /// 
111    /// The callback is invoked on a Link-managed thread.
112    pub fn set_num_peers_callback(&mut self, callback: extern fn(usize)) {
113        unsafe {
114            let cb = callback as unsafe extern fn(usize);
115            Link_setNumPeersCallback(self.wlink, Some(cb));
116        }
117    }
118
119    /// Register a callback to be notified when the session tempo changes.
120    /// * Thread-safe: yes
121    /// * Realtime-safe: no
122    /// 
123    /// The callback is invoked on a Link-managed thread.
124    pub fn set_tempo_callback(&mut self, callback: extern fn(f64)) {
125        unsafe {
126            let cb = callback as unsafe extern fn(f64);
127            Link_setTempoCallback(self.wlink, Some(cb));
128        }
129    }
130
131    /// Register a callback to be notified when the state of
132    /// start/stop isPlaying changes.
133    /// * Thread-safe: yes
134    /// * Realtime-safe: no
135    /// 
136    /// The callback is invoked on a Link-managed thread.
137    pub fn set_start_stop_callback(&mut self, callback: extern fn(bool)) {
138        unsafe {
139            let cb = callback as unsafe extern fn(bool);
140            Link_setStartStopCallback(self.wlink, Some(cb));
141        }
142    }
143
144    /// The clock used by Link.
145    /// * Thread-safe: yes
146    /// * Realtime-safe: yes
147    /// 
148    /// The Clock type is a platform-dependent
149    /// representation of the system clock. It exposes a `ticks()` method
150    /// that returns the current ticks of the system clock as well as
151    /// `micros()`, which is a normalized representation of the current system
152    /// time in std::chrono::microseconds. It also provides conversion
153    /// functions `ticksToMicros()` and `microsToTicks()` to faciliate
154    /// converting between these units.
155    pub fn clock(&self) -> Clock {
156        Clock { wc: unsafe { Link_clock(self.wlink) } }
157    }
158
159    // Capture the current Link Session State from the audio thread.
160    // * Thread-safe: no
161    // * Realtime-safe: yes
162    // 
163    // This method should ONLY be called in the audio thread
164    // and must not be accessed from any other threads. The returned
165    // object stores a snapshot of the current Link Session State, so it
166    // should be captured and used in a local scope. Storing the
167    // Session State for later use in a different context is not advised
168    // because it will provide an outdated view.
169    // fn capture_audio_session_state(&self) -> SessionState {
170    //     unimplemented!()
171    // }
172
173    /// Capture the current Link Session State from the audio thread.
174    /// * Thread-safe: no
175    /// * Realtime-safe: yes
176    /// 
177    /// This method should ONLY be called in the audio thread
178    /// and must not be accessed from any other threads. The closure
179    /// passes a snapshot of the current Link Session State, it
180    /// should only be used in the local scope. Storing the
181    /// Session State for later use in a different context is not advised
182    /// because it will provide an outdated view.
183    pub fn with_audio_session_state<F>(&self, f: F)
184        where F: FnMut(SessionState)
185    {
186        let user_data = &f as *const _ as *mut c_void;
187        unsafe {
188            Link_withAudioSessionState(self.wlink, Some(closure_wrapper::<F>), user_data);
189        }
190
191        extern fn closure_wrapper<F>(closure: *mut c_void, wss: *mut WSessionState)
192            where F: FnMut(SessionState)
193        {
194            let opt_closure = closure as *mut Option<F>;
195            unsafe {
196                let mut fnx = (*opt_closure).take().unwrap();
197                let ss = SessionState { wss };
198                fnx(ss);
199            }
200        }
201    }
202
203    /// Commit the given Session State to the Link session from the audio thread.
204    /// * Thread-safe: no
205    /// * Realtime-safe: yes
206    /// 
207    /// This method should ONLY be called in the audio
208    /// thread. The given Session State will replace the current Link
209    /// state. Modifications will be communicated to other peers in the
210    /// session.
211    pub fn commit_audio_session_state(&mut self, ss: SessionState) {
212        unsafe { Link_commitAudioSessionState(self.wlink, ss.wss) }
213    }
214
215    // Capture the current Link Session State from an application thread.
216    // * Thread-safe: yes
217    // * Realtime-safe: no
218    // 
219    // Provides a mechanism for capturing the Link Session
220    // State from an application thread (other than the audio thread).
221    // The returned Session State stores a snapshot of the current Link
222    // state, so it should be captured and used in a local scope.
223    // Storing the it for later use in a different context is not
224    // advised because it will provide an outdated view.
225    // pub fn capture_app_session_state(&self) -> SessionState {
226    //     let wss = unsafe { Link_captureAppSessionState(self.wlink) };
227    //     SessionState { wss }
228    // }
229
230    /// Capture the current Link Session State from an application thread.
231    /// * Thread-safe: yes
232    /// * Realtime-safe: no
233    /// 
234    /// Provides a mechanism for capturing the Link Session
235    /// State from an application thread (other than the audio thread).
236    /// The closure passes a Session State that stores a snapshot of the current Link
237    /// state, it should only be used in the local scope.
238    /// Storing it for later use in a different context is not
239    /// advised because it will provide an outdated view.
240    pub fn with_app_session_state<F>(&self, f: F)
241        where F: FnMut(SessionState)
242    {
243        let user_data = &f as *const _ as *mut c_void;
244        unsafe {
245            Link_withAppSessionState(self.wlink, Some(closure_wrapper::<F>), user_data);
246        }
247
248        extern fn closure_wrapper<F>(closure: *mut c_void, wss: *mut WSessionState)
249            where F: FnMut(SessionState)
250        {
251            let opt_closure = closure as *mut Option<F>;
252            unsafe {
253                let mut fnx = (*opt_closure).take().unwrap();
254                let ss = SessionState { wss };
255                fnx(ss);
256            }
257        }
258    }
259
260    /// Commit the given Session State to the Link session from an
261    /// application thread.
262    /// * Thread-safe: yes
263    /// * Realtime-safe: no
264    /// 
265    /// The given Session State will replace the current Link
266    /// Session State. Modifications of the Session State will be
267    /// communicated to other peers in the session.
268    pub fn commit_app_session_state(&mut self, ss: SessionState) {
269        unsafe { Link_commitAppSessionState(self.wlink, ss.wss) }
270    }
271}
272
273/// # Representation of a timeline and the start/stop state
274/// 
275/// A SessionState object is intended for use in a local scope within
276/// a single thread - none of its methods are thread-safe. All of its methods are
277/// non-blocking, so it is safe to use from a realtime thread.
278/// It provides functions to observe and manipulate the timeline and start/stop
279/// state.
280/// 
281/// The timeline is a representation of a mapping between time and beats for varying
282/// quanta.
283/// 
284/// The start/stop state represents the user intention to start or stop transport at
285/// a specific time. Start stop synchronization is an optional feature that allows to
286/// share the user request to start or stop transport between a subgroup of peers in
287/// a Link session. When observing a change of start/stop state, audio playback of a
288/// peer should be started or stopped the same way it would have happened if the user
289/// had requested that change at the according time locally. The start/stop state can
290/// only be changed by the user. This means that the current local start/stop state
291/// persists when joining or leaving a Link session. After joining a Link session
292/// start/stop change requests will be communicated to all connected peers.
293pub struct SessionState {
294    wss: *mut WSessionState,
295}
296
297// impl Drop for SessionState {
298//     fn drop(&mut self) {
299//         unsafe { SessionState_destroy(self.wss) }
300//     }
301// }
302
303impl SessionState {
304    /// The tempo of the timeline, in bpm.
305    pub fn tempo(&self) -> f64 {
306        unsafe { SessionState_tempo(self.wss) }
307    }
308
309    /// Set the timeline tempo to the given bpm value, taking
310    /// effect at the given time.
311    pub fn set_tempo(&mut self, bpm: f64, at_time: i64) {
312        unsafe { SessionState_setTempo(self.wss, bpm, at_time) }
313    }
314
315    /// Get the beat value corresponding to the given time
316    /// for the given quantum.
317    ///
318    /// The magnitude of the resulting beat value is
319    /// unique to this Link instance, but its phase with respect to
320    /// the provided quantum is shared among all session
321    /// peers. For non-negative beat values, the following
322    /// property holds: fmod(beatAtTime(t, q), q) == phaseAtTime(t, q)
323    pub fn beat_at_time(&self, time: i64, quantum: f64) -> f64 {
324        unsafe { SessionState_beatAtTime(self.wss, time, quantum) }
325    }
326
327    /// Get the session phase at the given time for the given quantum.
328    /// 
329    /// The result is in the interval [0, quantum). The
330    /// result is equivalent to fmod(beatAtTime(t, q), q) for
331    /// non-negative beat values. This method is convenient if the
332    /// client is only interested in the phase and not the beat
333    /// magnitude. Also, unlike fmod, it handles negative beat values
334    /// correctly.
335    pub fn phase_at_time(&self, time: i64, quantum: f64) -> f64 {
336        unsafe { SessionState_phaseAtTime(self.wss, time, quantum) }
337    }
338
339    /// Get the time at which the given beat occurs for the
340    /// given quantum.
341    /// 
342    /// The inverse of beatAtTime, assuming a constant
343    /// tempo. beatAtTime(timeAtBeat(b, q), q) === b.
344    pub fn time_at_beat(&self, beat: f64, quantum: f64) -> i64 {
345        unsafe { SessionState_timeAtBeat(self.wss, beat, quantum) }
346    }
347
348    /// Attempt to map the given beat to the given time in the
349    /// context of the given quantum.
350    /// 
351    /// This method behaves differently depending on the
352    /// state of the session. If no other peers are connected,
353    /// then this instance is in a session by itself and is free to
354    /// re-map the beat/time relationship whenever it pleases. In this
355    /// case, beatAtTime(time, quantum) == beat after this method has
356    /// been called.
357    /// 
358    /// If there are other peers in the session, this instance
359    /// should not abruptly re-map the beat/time relationship in the
360    /// session because that would lead to beat discontinuities among
361    /// the other peers. In this case, the given beat will be mapped
362    /// to the next time value greater than the given time with the
363    /// same phase as the given beat.
364    /// 
365    /// This method is specifically designed to enable the concept of
366    /// "quantized launch" in client applications. If there are no other
367    /// peers in the session, then an event (such as starting
368    /// transport) happens immediately when it is requested. If there
369    /// are other peers, however, we wait until the next time at which
370    /// the session phase matches the phase of the event, thereby
371    /// executing the event in-phase with the other peers in the
372    /// session. The client only needs to invoke this method to
373    /// achieve this behavior and should not need to explicitly check
374    /// the number of peers.
375    pub fn request_beat_at_time(&mut self, beat: f64, time: i64, quantum: f64) {
376        unsafe { SessionState_requestBeatAtTime(self.wss, beat, time, quantum) }
377    }
378
379    /// Rudely re-map the beat/time relationship for all peers
380    /// in a session.
381    /// 
382    /// DANGER: This method should only be needed in
383    /// certain special circumstances. Most applications should not
384    /// use it. It is very similar to requestBeatAtTime except that it
385    /// does not fall back to the quantizing behavior when it is in a
386    /// session with other peers. Calling this method will
387    /// unconditionally map the given beat to the given time and
388    /// broadcast the result to the session. This is very anti-social
389    /// behavior and should be avoided.
390    /// 
391    /// One of the few legitimate uses of this method is to
392    /// synchronize a Link session with an external clock source. By
393    /// periodically forcing the beat/time mapping according to an
394    /// external clock source, a peer can effectively bridge that
395    /// clock into a Link session. Much care must be taken at the
396    /// application layer when implementing such a feature so that
397    /// users do not accidentally disrupt Link sessions that they may
398    /// join.
399    pub fn force_beat_at_time(&mut self, beat: f64, time: i64, quantum: f64) {
400        unsafe { SessionState_forceBeatAtTime(self.wss, beat, time, quantum) }
401    }
402
403    /// Set if transport should be playing or stopped, taking effect
404    /// at the given time.
405    pub fn set_is_playing(&mut self, is_playing: bool, time: i64) {
406        unsafe { SessionState_setIsPlaying(self.wss, is_playing, time) }
407    }
408
409    /// Is transport playing?
410    pub fn is_playing(&self) -> bool {
411        unsafe { SessionState_isPlaying(self.wss) }
412    }
413
414    /// Get the time at which a transport start/stop occurs.
415    pub fn time_for_is_playing(&self) -> i64 {
416        unsafe { SessionState_timeForIsPlaying(self.wss) }
417    }
418
419    /// Convenience function to attempt to map the given beat to the time
420    /// when transport is starting to play in context of the given quantum.
421    /// This function evaluates to a no-op if isPlaying() equals false.
422    pub fn request_beat_at_start_playing_time(&mut self, beat: f64, quantum: f64) {
423        unsafe { SessionState_requestBeatAtStartPlayingTime(self.wss, beat, quantum) }
424    }
425
426    /// Convenience function to start or stop transport at a given time and
427    /// attempt to map the given beat to this time in context of the given quantum.
428    pub fn set_is_playing_and_request_beat_at_time(&mut self,
429        is_playing: bool, time: i64, beat: f64, quantum: f64) {
430
431        unsafe { SessionState_setIsPlayingAndRequestBeatAtTime(self.wss, 
432            is_playing, time, beat, quantum) }
433    }
434}
435
436pub struct Clock {
437    wc: *mut WClock,
438}
439
440impl Drop for Clock{
441    fn drop(&mut self) {
442        unsafe { Clock_destroy(self.wc) }
443    }
444}
445
446impl Clock {
447    #[cfg(target_os = "macos")]
448    pub fn ticks_to_micros(&self, ticks: u64) -> i64 {
449        unsafe { Clock_ticksToMicros(self.wc, ticks) }
450    }
451
452    #[cfg(target_os = "macos")]
453    pub fn micros_to_ticks(&self, micros: i64) -> u64 {
454        unsafe { Clock_microsToTicks(self.wc, micros) }
455    }
456
457    #[cfg(target_os = "macos")]
458    pub fn ticks(&self) -> u64 {
459        unsafe { Clock_ticks(self.wc) }
460    }
461
462    pub fn micros(&self) -> i64 {
463        unsafe { Clock_micros(self.wc) }
464    }
465}
466
467#[cfg(test)]
468mod tests {
469    #[test]
470    fn it_works() {
471        assert_eq!(2 + 2, 4);
472    }
473}