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}