/* Copyright 2021, Ableton AG, Berlin. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* If you would like to incorporate Link into a proprietary software application,
* please contact .
*/
#include
#include
#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus
/*!
* @discussion Each abl_link instance has its own session state which
* represents a beat timeline and a transport start/stop state. The
* timeline starts running from beat 0 at the initial tempo when
* constructed. The timeline always advances at a speed defined by
* its current tempo, even if transport is stopped. Synchronizing to the
* transport start/stop state of Link is optional for every peer.
* The transport start/stop state is only shared with other peers when
* start/stop synchronization is enabled.
*
* An abl_link instance is initially disabled after construction, which
* means that it will not communicate on the network. Once enabled,
* an abl_link instance initiates network communication in an effort to
* discover other peers. When peers are discovered, they immediately
* become part of a shared Link session.
*
* Each function documents its thread-safety and
* realtime-safety properties. When a function is marked thread-safe,
* it means it is safe to call from multiple threads
* concurrently. When a function is marked realtime-safe, it means that
* it does not block and is appropriate for use in the thread that
* performs audio IO.
*
* One session state capture/commit function pair for use
* in the audio thread and one for all other application contexts is provided.
* In general, modifying the session state should be done in the audio
* thread for the most accurate timing results. The ability to modify
* the session state from application threads should only be used in
* cases where an application's audio thread is not actively running
* or if it doesn't generate audio at all. Modifying the Link session
* state from both the audio thread and an application thread
* concurrently is not advised and will potentially lead to unexpected
* behavior.
*/
/*! @brief The representation of an abl_link instance*/
typedef struct abl_link
{
void *impl;
} abl_link;
/*! @brief Construct a new abl_link instance with an initial tempo.
* Thread-safe: yes
* Realtime-safe: no
*/
abl_link abl_link_create(double bpm);
/*! @brief Delete an abl_link instance.
* Thread-safe: yes
* Realtime-safe: no
*/
void abl_link_destroy(abl_link link);
/*! @brief Is Link currently enabled?
* Thread-safe: yes
* Realtime-safe: yes
*/
bool abl_link_is_enabled(abl_link link);
/*! @brief Enable/disable Link.
* Thread-safe: yes
* Realtime-safe: no
*/
void abl_link_enable(abl_link link, bool enable);
/*! @brief: Is start/stop synchronization enabled?
* Thread-safe: yes
* Realtime-safe: no
*/
bool abl_link_is_start_stop_sync_enabled(abl_link link);
/*! @brief: Enable start/stop synchronization.
* Thread-safe: yes
* Realtime-safe: no
*/
void abl_link_enable_start_stop_sync(abl_link link, bool enabled);
/*! @brief How many peers are currently connected in a Link session?
* Thread-safe: yes
* Realtime-safe: yes
*/
uint64_t abl_link_num_peers(abl_link link);
/*! @brief Register a callback to be notified when the number of
* peers in the Link session changes.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The callback is invoked on a Link-managed thread.
*/
typedef void (*abl_link_num_peers_callback)(uint64_t num_peers, void *context);
void abl_link_set_num_peers_callback(
abl_link link, abl_link_num_peers_callback callback, void *context);
/*! @brief Register a callback to be notified when the session
* tempo changes.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The callback is invoked on a Link-managed thread.
*/
typedef void (*abl_link_tempo_callback)(double tempo, void *context);
void abl_link_set_tempo_callback(
abl_link link, abl_link_tempo_callback callback, void *context);
/*! brief: Register a callback to be notified when the state of
* start/stop isPlaying changes.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The callback is invoked on a Link-managed thread.
*/
typedef void (*abl_link_start_stop_callback)(bool is_playing, void *context);
void abl_link_set_start_stop_callback(
abl_link link, abl_link_start_stop_callback callback, void *context);
/*! brief: Get the current link clock time in microseconds.
* Thread-safe: yes
* Realtime-safe: yes
*/
int64_t abl_link_clock_micros(abl_link link);
/*! @brief The representation of the current local state of a client in a Link Session
*
* @discussion A session state represents a timeline and the start/stop
* state. The timeline is a representation of a mapping between time and
* beats for varying quanta. The start/stop state represents the user
* intention to start or stop transport at a specific time. Start stop
* synchronization is an optional feature that allows to share the user
* request to start or stop transport between a subgroup of peers in a
* Link session. When observing a change of start/stop state, audio
* playback of a peer should be started or stopped the same way it would
* have happened if the user had requested that change at the according
* time locally. The start/stop state can only be changed by the user.
* This means that the current local start/stop state persists when
* joining or leaving a Link session. After joining a Link session
* start/stop change requests will be communicated to all connected peers.
*/
typedef struct abl_link_session_state
{
void *impl;
} abl_link_session_state;
/*! @brief Create a new session_state instance.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The session_state is to be used with the abl_link_capture... and
* abl_link_commit... functions to capture snapshots of the current link state and pass
* changes to the link session.
*/
abl_link_session_state abl_link_create_session_state(void);
/*! @brief Delete a session_state instance.
* Thread-safe: yes
* Realtime-safe: no
*/
void abl_link_destroy_session_state(abl_link_session_state abl_link_session_state);
/*! @brief Capture the current Link Session State from the audio thread.
* Thread-safe: no
* Realtime-safe: yes
*
* @discussion This function should ONLY be called in the audio thread and must not be
* accessed from any other threads. After capturing the session_state holds a snapshot
* of the current Link Session State, so it should be used in a local scope. The
* session_state should not be created on the audio thread.
*/
void abl_link_capture_audio_session_state(
abl_link link, abl_link_session_state session_state);
/*! @brief Commit the given Session State to the Link session from the
* audio thread.
* Thread-safe: no
* Realtime-safe: yes
*
* @discussion This function should ONLY be called in the audio thread. The given
* session_state will replace the current Link state. Modifications will be
* communicated to other peers in the session.
*/
void abl_link_commit_audio_session_state(
abl_link link, abl_link_session_state session_state);
/*! @brief Capture the current Link Session State from an application thread.
* Thread-safe: no
* Realtime-safe: yes
*
* @discussion Provides a mechanism for capturing the Link Session State from an
* application thread (other than the audio thread). After capturing the session_state
* contains a snapshot of the current Link state, so it should be used in a local
* scope.
*/
void abl_link_capture_app_session_state(
abl_link link, abl_link_session_state session_state);
/*! @brief Commit the given Session State to the Link session from an
* application thread.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The given session_state will replace the current Link Session State.
* Modifications of the Session State will be communicated to other peers in the
* session.
*/
void abl_link_commit_app_session_state(
abl_link link, abl_link_session_state session_state);
/*! @brief: The tempo of the timeline, in Beats Per Minute.
*
* @discussion This is a stable value that is appropriate for display to the user. Beat
* time progress will not necessarily match this tempo exactly because of clock drift
* compensation.
*/
double abl_link_tempo(abl_link_session_state session_state);
/*! @brief: Set the timeline tempo to the given bpm value, taking effect at the given
* time.
*/
void abl_link_set_tempo(
abl_link_session_state session_state, double bpm, int64_t at_time);
/*! @brief: Get the beat value corresponding to the given time for the given quantum.
*
* @discussion: The magnitude of the resulting beat value is unique to this Link
* client, but its phase with respect to the provided quantum is shared among all
* session peers. For non-negative beat values, the following property holds:
* fmod(beatAtTime(t, q), q) == phaseAtTime(t, q)
*/
double abl_link_beat_at_time(
abl_link_session_state session_state, int64_t time, double quantum);
/*! @brief: Get the session phase at the given time for the given quantum.
*
* @discussion: The result is in the interval [0, quantum). The result is equivalent to
* fmod(beatAtTime(t, q), q) for non-negative beat values. This function is convenient
* if the client application is only interested in the phase and not the beat
* magnitude. Also, unlike fmod, it handles negative beat values correctly.
*/
double abl_link_phase_at_time(
abl_link_session_state session_state, int64_t time, double quantum);
/*! @brief: Get the time at which the given beat occurs for the given quantum.
*
* @discussion: The inverse of beatAtTime, assuming a constant tempo.
* beatAtTime(timeAtBeat(b, q), q) === b.
*/
int64_t abl_link_time_at_beat(
abl_link_session_state session_state, double beat, double quantum);
/*! @brief: Attempt to map the given beat to the given time in the context of the given
* quantum.
*
* @discussion: This function behaves differently depending on the state of the
* session. If no other peers are connected, then this abl_link instance is in a
* session by itself and is free to re-map the beat/time relationship whenever it
* pleases. In this case, beatAtTime(time, quantum) == beat after this funtion has been
* called.
*
* If there are other peers in the session, this abl_link instance should not abruptly
* re-map the beat/time relationship in the session because that would lead to beat
* discontinuities among the other peers. In this case, the given beat will be mapped
* to the next time value greater than the given time with the same phase as the given
* beat.
*
* This function is specifically designed to enable the concept of "quantized launch"
* in client applications. If there are no other peers in the session, then an event
* (such as starting transport) happens immediately when it is requested. If there are
* other peers, however, we wait until the next time at which the session phase matches
* the phase of the event, thereby executing the event in-phase with the other peers in
* the session. The client application only needs to invoke this function to achieve
* this behavior and should not need to explicitly check the number of peers.
*/
void abl_link_request_beat_at_time(
abl_link_session_state session_state, double beat, int64_t time, double quantum);
/*! @brief: Rudely re-map the beat/time relationship for all peers in a session.
*
* @discussion: DANGER: This function should only be needed in certain special
* circumstances. Most applications should not use it. It is very similar to
* requestBeatAtTime except that it does not fall back to the quantizing behavior when
* it is in a session with other peers. Calling this function will unconditionally map
* the given beat to the given time and broadcast the result to the session. This is
* very anti-social behavior and should be avoided.
*
* One of the few legitimate uses of this function is to synchronize a Link session
* with an external clock source. By periodically forcing the beat/time mapping
* according to an external clock source, a peer can effectively bridge that clock into
* a Link session. Much care must be taken at the application layer when implementing
* such a feature so that users do not accidentally disrupt Link sessions that they may
* join.
*/
void abl_link_force_beat_at_time(
abl_link_session_state session_state, double beat, uint64_t time, double quantum);
/*! @brief: Set if transport should be playing or stopped, taking effect at the given
* time.
*/
void abl_link_set_is_playing(
abl_link_session_state session_state, bool is_playing, uint64_t time);
/*! @brief: Is transport playing? */
bool abl_link_is_playing(abl_link_session_state session_state);
/*! @brief: Get the time at which a transport start/stop occurs */
uint64_t abl_link_time_for_is_playing(abl_link_session_state session_state);
/*! @brief: Convenience function to attempt to map the given beat to the time
* when transport is starting to play in context of the given quantum.
* This function evaluates to a no-op if abl_link_is_playing equals false.
*/
void abl_link_request_beat_at_start_playing_time(
abl_link_session_state session_state, double beat, double quantum);
/*! @brief: Convenience function to start or stop transport at a given time and attempt
* to map the given beat to this time in context of the given quantum. */
void abl_link_set_is_playing_and_request_beat_at_time(
abl_link_session_state session_state,
bool is_playing,
uint64_t time,
double beat,
double quantum);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus