-
Notifications
You must be signed in to change notification settings - Fork 172
Expand file tree
/
Copy pathLinkAudio.hpp
More file actions
358 lines (311 loc) · 12.3 KB
/
LinkAudio.hpp
File metadata and controls
358 lines (311 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*! @file LinkAudio.hpp
* @copyright 2025, Ableton AG, Berlin. All rights reserved.
* @brief Library for cross-device shared tempo, quantized beat grid, and sharing audio
*
* @license:
* 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 <http://www.gnu.org/licenses/>.
*
* If you would like to incorporate Link into a proprietary software application,
* please contact <link-devs@ableton.com>.
*/
#pragma once
#define LINK_AUDIO YES
#include <ableton/link_audio/ApiConfig.hpp>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace ableton
{
/*! @class LinkAudio and BasicLinkAudio
* @brief LinkAudio provides the Link functionality plus audio sharing.
* To enable audio sharing include this header and replace the Link class with the
* LinkAudio and Link should not be used simultaneously.
*
* @discussion
* LinkAudio provides the same functionality as Link. See Link.hpp for the respective
* documentation. On top of that, LinkAudio allows to announce and discover audio
* channels in a Link session. When sending and receiving audio, the Link beat time grid
* and quantum can be used to align audio from different peers.
*
* Channels in a Link session are represented as sinks on the sending side and sources on
* the receiving side.
* Sinks only send audio if at least one corresponding source is present in the session.
* Audio buffers are interleaved and samples are represented as 16-bit signed integers.
*/
template <typename Clock>
class BasicLinkAudio : public BasicLink<Clock>
{
public:
/*! @brief Construct with an initial tempo and local peer name for identification in the
* Link session. Names longer than 256 characters will be truncated.
*/
BasicLinkAudio(double bpm, std::string name);
~BasicLinkAudio();
/*! @brief Is audio currently enabled?
* Thread-safe: yes
* Realtime-safe: yes
*/
bool isLinkAudioEnabled() const;
/*! @brief Enable or disable audio.
* Thread-safe: yes
* Realtime-safe: no
*/
void enableLinkAudio(bool bEnable);
/*! @brief Get the local peer name for identification in the Link session.
* Thread-safe: yes
* Realtime-safe: no
*/
std::string peerName() const;
/*! @brief Change the local peer name for identification in the Link session.
* Names longer than 256 characters will be truncated.
* Thread-safe: yes
* Realtime-safe: no
*/
void setPeerName(std::string name);
/*! @brief Register a callback to be notified when the set of available
* audio channels changes. This will be called when channels are discovered or
* disappeared and when names change.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The callback is invoked on a Link-managed thread.
* @param callback The callback signature is: void ()
*/
template <typename Callback>
void setChannelsChangedCallback(Callback callback);
/*! @struct Channel
* @brief Describes an audio channel available in the Link session.
* @discussion
* The unique identifiers for channels and peers are persistent for the lifetime of a
* channel. Names may change over time and are meant for display purposes.
*/
struct Channel
{
ChannelId id; /*!< Unique identifier for the channel. */
std::string name; /*!< Name of the channel. */
PeerId peerId; /*!< Identifier of the peer providing the channel. */
std::string peerName; /*!< Name of the peer providing the channel. */
};
/*! @brief Get the list of currently available audio channels in the session.
* Thread-safe: yes
* Realtime-safe: no
*/
std::vector<Channel> channels() const;
/*! @brief Call a function on the Link thread.
* Thread-safe: yes
* Realtime-safe: no
*
* @discussion The function will be called on the Link thread, which is managed by the
* LinkAudio instance. This is useful for setting the thread priority for example.
* @param func The function signature is: void ()
*/
template <typename Function>
void callOnLinkThread(Function func);
private:
using Controller = ableton::link::ApiController<Clock>;
link_audio::ChannelsChangedCallback mChannelsChangedCallback = []() {};
};
class LinkAudio : public BasicLinkAudio<link::platform::Clock>
{
public:
using Clock = link::platform::Clock;
LinkAudio(double bpm, std::string name)
: BasicLinkAudio<Clock>(bpm, name)
{
}
private:
friend class LinkAudioSink;
friend class LinkAudioSource;
};
/*! @class LinkAudioSink
* @brief Announces an audio channel to the Link session.
*
* @discussion
* The announced channel is visible to other peers for the lifetime of the sink.
* The sink can be used to write audio samples, which will be sent to peers that have
* respective sources.
*/
class LinkAudioSink
{
public:
/*! @brief Construct a LinkAudioSink with a LinkAudio instance, a name of the audio
* channel, and the maximum buffer size in samples. This buffer size should account for
* the number of channels times the number of samples per channel in one audio
* callback. Names longer than 256 characters will be truncated.
*/
template <typename LinkAudio>
LinkAudioSink(LinkAudio& link, std::string name, size_t maxNumSamples);
LinkAudioSink(const LinkAudioSink&) = default;
LinkAudioSink& operator=(const LinkAudioSink&) = default;
LinkAudioSink(LinkAudioSink&&) = default;
LinkAudioSink& operator=(LinkAudioSink&&) = default;
/*! @brief Get the name of the channel.
* Thread-safe: no
* Realtime-safe: no
*/
std::string name() const;
/*! @brief Set the name of this channel. Names longer than 256 characters will be
* truncated.
* Thread-safe: no
* Realtime-safe: no
*/
void setName(std::string name);
/*! @brief Request a maximum buffer size for future buffers.
* Thread-safe: yes
* Realtime-safe: yes
*
* @discussion Increase the number of samples retained buffer handles can hold. If the
* requested number of samples is smaller than the current maximum number of samples
* this is a no-op.
*/
void requestMaxNumSamples(size_t numSamples);
/*! @brief Get the current maximum number of samples a buffer handle can hold.
* Thread-safe: yes
* Realtime-safe: yes
*/
size_t maxNumSamples() const;
/*! @struct BufferHandle
* @brief Handle to a buffer for writing audio samples.
*/
struct BufferHandle
{
/*! @brief Retain a buffer for writing audio samples. Only on BufferHandle can be
* retained at a time. A BufferHandle should never outlive the LinkAudioSink it was
* created from.
* Thread-safe: no
* Realtime-safe: yes
*/
BufferHandle(LinkAudioSink&);
BufferHandle(const BufferHandle&) = delete;
BufferHandle(BufferHandle&& other) = delete;
BufferHandle& operator=(const BufferHandle&) = delete;
BufferHandle& operator=(BufferHandle&& other) = delete;
~BufferHandle();
/*! @brief Check if the handle is valid. Make sure to check this before
* using the handle. The handle may be invalid if no corresponding source exists or
* no buffer is available.
*
* Thread-safe: no
* Realtime-safe: yes
*/
operator bool() const;
int16_t* samples; /*!< Pointer to the buffer for writing samples. */
size_t maxNumSamples; /*!< Maximum number of samples that can be written. */
/*! @brief Commit the buffer after writing samples. After this the buffer the object
* should not be used anymore.
* @param sessionState The current Link session state.
* @param beatsAtBufferBegin Beat at the start of the buffer.
* @param quantum Quantum value for beat mapping.
* @param numFrames Number of frames written. numFrames * numChannels may not exceed
* maxNumSamples.
* @param numChannels Number of channels. Can be 1 for mono or 2 for stereo.
* @param sampleRate Sample rate in Hz.
* @return True if the buffer was successfully committed.
*
* Thread-safe: no
* Realtime-safe: yes
*
* @discussion The Link session state, the quantum, and the beats at buffer begin
* must be same as used for rendering the audio locally. Changes to the Link session
* state should always be made before rendering and eventually writing the buffer.
*/
template <typename SessionState>
bool commit(const SessionState&,
double beatsAtBufferBegin,
double quantum,
size_t numFrames,
size_t numChannels,
uint32_t sampleRate);
private:
std::weak_ptr<link_audio::Sink> mpSink;
link_audio::Buffer<int16_t>* mpBuffer;
};
private:
friend struct BufferHandle;
std::shared_ptr<link_audio::Sink> mpImpl;
};
/*! @class LinkAudioSource
* @brief Receives audio streams from other peers in the Link session.
*
* @discussion
* LinkAudioSource allows an application to subscribe to an audio channel
* published by another peer, receiving buffers as they become available.
*/
class LinkAudioSource
{
public:
struct BufferHandle;
/*! @brief Construct a LinkAudioSource to receive audio for a specifc channel.
* @param link The LinkAudio instance.
* @param id The ID of the channel to be received.
* @param callback Callback invoked on a Link-managed thread when a buffer is received.
* The callback signature is: void (BufferHandle bufferHandle)
*/
template <typename LinkAudio, typename Callback>
LinkAudioSource(LinkAudio& link, ChannelId id, Callback callback);
LinkAudioSource(const LinkAudioSource&) = default;
LinkAudioSource& operator=(const LinkAudioSource&) = default;
LinkAudioSource(LinkAudioSource&&) = default;
LinkAudioSource& operator=(LinkAudioSource&&) = default;
~LinkAudioSource();
/*! @brief Get the channel ID of the corresponding source.
* Thread-safe: yes
* Realtime-safe: yes
*/
ChannelId id() const;
/*! @struct BufferHandle
* @brief Handle to a buffer containing received audio samples.
*/
struct BufferHandle
{
/*! @struct Info
* @brief Metadata about the received audio buffer.
*/
struct Info
{
size_t numChannels; /*!< Number of channels in the buffer. */
size_t numFrames; /*!< Number of frames in the buffer. */
uint32_t sampleRate; /*!< Sample rate in Hz. */
uint64_t count; /*!< Sequence number of the buffer. */
double sessionBeatTime; /*!< Use beginBeats() to map this to local beat time. */
double tempo; /*!< Tempo in beats per minute. */
SessionId sessionId; /*!< ID of the session the buffer belongs to. */
/*! @brief Map the beat time at the begin of the buffer to the local Link session
* state.
* @param sessionState The current Link session state.
* @param quantum Quantum value for beat mapping.
* @return Local beat time at buffer begin. Returns nullopt if the buffer
* originates from a different Link Session.
*/
template <typename SessionState>
std::optional<double> beginBeats(const SessionState&, double quantum) const;
/*! @brief Map the beat time at the end of the buffer to the local Link session
* state.
* @param sessionState The current Link session state.
* @param quantum Quantum value for beat mapping.
* @return Local beat time at buffer end. Returns nullopt if the buffer
* originates from a different Link Session.
*/
template <typename SessionState>
std::optional<double> endBeats(const SessionState&, double quantum) const;
};
int16_t* samples; /*!< Pointer to the buffer of received samples. */
Info info; /*!< Information about the buffer. */
};
private:
std::shared_ptr<link_audio::Source> mpImpl;
};
} // namespace ableton
#include <ableton/LinkAudio.ipp>