Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
transport.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "transport.hh"
3#include "utils.hh"
4#include "internal.hh"
5
6namespace Ase {
7
8// == TransportLimits ==
9struct TransportLimits final {
10 static_assert (TRANSPORT_PPQN == 4 * SEMIQUAVER_TICKS);
11 // resolution must be sample accurate @ MINBPM and MAXRATE
12 static_assert (MIN_BPM * TRANSPORT_PPQN / (60.0 * MAX_SAMPLERATE) > 1.0);
13 // double precision tick (2^52) should cover ca 1 year
14 static_assert (4503599627370496.0 / (MAX_BPM * TRANSPORT_PPQN) / (60 * 24) > 363.95);
15};
16
17// == TickSignature ==
18constexpr double INVERSE_SEMIQUAVER = 1.0 / SEMIQUAVER_TICKS;
19
20TickSignature::TickSignature() :
21 TickSignature (60, 4, 4)
22{}
23
24TickSignature::TickSignature (const TickSignature &other) :
25 TickSignature (other.bpm(), other.beats_per_bar(), other.beat_unit()) // other.offset_
26{}
27
28TickSignature::TickSignature (double bpm, uint8 beats_per_bar, uint8 beat_unit)
29{
30 set_signature (beats_per_bar, beat_unit);
31 set_bpm (bpm);
32}
33
35TickSignature::operator= (const TickSignature &src)
36{
37 const double bpm = src.bpm();
38 const uint8 beats_per_bar = src.beats_per_bar();
39 const uint8 beat_unit = src.beat_unit();
40 set_signature (beats_per_bar, beat_unit);
41 set_bpm (bpm);
42 return *this;
43}
44
46void
48{
49 assert_return (samplerate >= MIN_SAMPLERATE && samplerate <= MAX_SAMPLERATE);
50 samplerate_ = samplerate;
52 const double ticks_per_minute_d = TRANSPORT_PPQN * bpm_;
53 ticks_per_sample_ = samplerate_ > 0 ? ticks_per_minute_d / (60.0 * samplerate_) : 0;
54 sample_per_ticks_ = ticks_per_minute_d > 0 ? (60.0 * samplerate_) / ticks_per_minute_d : 0;
55}
56
58void
60{
61 assert_return (bpm >= 0);
62 // offset_ = start_offset;
63 bpm_ = bpm;
64 const double ticks_per_minute_d = TRANSPORT_PPQN * bpm_;
65 ticks_per_minute_ = ticks_per_minute_d;
66 ticks_per_second_ = ticks_per_minute_d * (1.0 / 60.0);
67 inv_ticks_per_second_ = ISLIKELY (bpm_ > 0) ? 1.0 / ticks_per_second_ : 0.0;
68 ticks_per_sample_ = samplerate_ > 0 ? ticks_per_minute_d / (60.0 * samplerate_) : 0;
69 sample_per_ticks_ = ticks_per_minute_d > 0 ? (60.0 * samplerate_) / ticks_per_minute_d : 0;
70}
71
75{
76 Time t;
77 int64 minutereminder;
78 t.minutes = divmod (tick - offset_, ticks_per_minute_, &minutereminder);
79 t.seconds = minutereminder * inv_ticks_per_second_;
80 return t;
81}
82
86{
87 int64 tick = offset_;
88 tick += time.minutes * ticks_per_minute_;
89 tick += time.seconds * ticks_per_second_;
90 return tick;
91}
92
94bool
95TickSignature::set_signature (uint8 beats_per_bar, uint8 beat_unit)
96{
97 const auto old_beats_per_bar_ = beats_per_bar_;
98 const auto old_beat_unit_ = beat_unit_;
99 const auto old_offset_ = offset_;
100 // offset_ = start_offset;
101 beats_per_bar_ = CLAMP (beats_per_bar, 1, 64);
102 if (beat_unit == 1 || beat_unit == 2 || beat_unit == 4 || beat_unit == 8 || beat_unit == 16)
103 beat_unit_ = beat_unit;
104 const int semiquavers_per_beat = 16 / beat_unit_;
105 beat_ticks_ = SEMIQUAVER_TICKS * semiquavers_per_beat; // == 4 * PPQN / beat_unit_
106 bar_ticks_ = beat_ticks_ * beats_per_bar_;
107 return old_beats_per_bar_ != beats_per_bar_ || old_beat_unit_ != beat_unit_ || old_offset_ != offset_;
108}
109
113{
114 Beat b;
115 int64 bar_reminder;
116 b.bar = divmod (tick - offset_, int64 (bar_ticks_), &bar_reminder);
117 int32 beat_reminder;
118 b.beat = divmod (int32 (bar_reminder), beat_ticks_, &beat_reminder);
119 b.semiquaver = beat_reminder * INVERSE_SEMIQUAVER;
120 return b;
121}
122
124int64
126{
127 int64 tick = offset_;
128 tick += beat.bar * int64 (bar_ticks_);
129 tick += beat.beat * int64 (beat_ticks_);
130 tick += beat.semiquaver * SEMIQUAVER_TICKS;
131 return tick;
132}
133
135int32
137{
138 return (tick - offset_) / bar_ticks_;
139}
140
142int64
144{
145 int64 tick = offset_;
146 tick += bar * int64 (bar_ticks_);
147 return tick;
148}
149
150// == SpeakerArrangement ==
151// Count the number of channels described by the SpeakerArrangement.
152uint8
153speaker_arrangement_count_channels (SpeakerArrangement spa)
154{
155 const uint64_t bits = uint64_t (speaker_arrangement_channels (spa));
156 if_constexpr (sizeof (bits) == sizeof (long))
157 return __builtin_popcountl (bits);
158 return __builtin_popcountll (bits);
159}
160
161// Check if the SpeakerArrangement describes auxillary channels.
162bool
163speaker_arrangement_is_aux (SpeakerArrangement spa)
164{
166}
167
168// Retrieve the bitmask describing the SpeakerArrangement channels.
170speaker_arrangement_channels (SpeakerArrangement spa)
171{
172 const uint64_t bits = uint64_t (spa) & uint64_t (speaker_arrangement_channels_mask);
173 return SpeakerArrangement (bits);
174}
175
176const char*
177speaker_arrangement_bit_name (SpeakerArrangement spa)
178{
179 switch (spa)
180 { // https://wikipedia.org/wiki/Surround_sound
181 case SpeakerArrangement::NONE: return "-";
182 // case SpeakerArrangement::MONO: return "Mono";
183 case SpeakerArrangement::FRONT_LEFT: return "FL";
184 case SpeakerArrangement::FRONT_RIGHT: return "FR";
185 case SpeakerArrangement::FRONT_CENTER: return "FC";
186 case SpeakerArrangement::LOW_FREQUENCY: return "LFE";
187 case SpeakerArrangement::BACK_LEFT: return "BL";
188 case SpeakerArrangement::BACK_RIGHT: return "BR";
189 case SpeakerArrangement::AUX: return "AUX";
190 case SpeakerArrangement::STEREO: return "Stereo";
191 case SpeakerArrangement::STEREO_21: return "Stereo-2.1";
192 case SpeakerArrangement::STEREO_30: return "Stereo-3.0";
193 case SpeakerArrangement::STEREO_31: return "Stereo-3.1";
194 case SpeakerArrangement::SURROUND_50: return "Surround-5.0";
195 case SpeakerArrangement::SURROUND_51: return "Surround-5.1";
196#if 0 // TODO: dynamic multichannel support
197 case SpeakerArrangement::FRONT_LEFT_OF_CENTER: return "FLC";
198 case SpeakerArrangement::FRONT_RIGHT_OF_CENTER: return "FRC";
199 case SpeakerArrangement::BACK_CENTER: return "BC";
200 case SpeakerArrangement::SIDE_LEFT: return "SL";
201 case SpeakerArrangement::SIDE_RIGHT: return "SR";
202 case SpeakerArrangement::TOP_CENTER: return "TC";
203 case SpeakerArrangement::TOP_FRONT_LEFT: return "TFL";
204 case SpeakerArrangement::TOP_FRONT_CENTER: return "TFC";
205 case SpeakerArrangement::TOP_FRONT_RIGHT: return "TFR";
206 case SpeakerArrangement::TOP_BACK_LEFT: return "TBL";
207 case SpeakerArrangement::TOP_BACK_CENTER: return "TBC";
208 case SpeakerArrangement::TOP_BACK_RIGHT: return "TBR";
209 case SpeakerArrangement::SIDE_SURROUND_50: return "Side-Surround-5.0";
210 case SpeakerArrangement::SIDE_SURROUND_51: return "Side-Surround-5.1";
211#endif
212 }
213 return nullptr;
214}
215
217speaker_arrangement_desc (SpeakerArrangement spa)
218{
219 const bool isaux = speaker_arrangement_is_aux (spa);
220 const SpeakerArrangement chan = speaker_arrangement_channels (spa);
221 const char *chname = SpeakerArrangement::MONO == chan ? "Mono" : speaker_arrangement_bit_name (chan);
222 std::string s (chname ? chname : "<INVALID>");
223 if (isaux)
224 s = std::string (speaker_arrangement_bit_name (SpeakerArrangement::AUX)) + "(" + s + ")";
225 return s;
226}
227
228// == advance ==
229AudioTransport::AudioTransport (SpeakerArrangement speakerarrangement, uint sample_rate) :
230 samplerate (sample_rate), nyquist (sample_rate / 2),
231 isamplerate (1.0 / sample_rate), inyquist (2.0 / sample_rate),
232 speaker_arrangement (speakerarrangement)
233{
234 tick_sig.set_samplerate (sample_rate);
235 assert_return (sample_rate >= MIN_SAMPLERATE && sample_rate <= MAX_SAMPLERATE);
236 update_current();
237}
238
239void
240AudioTransport::running (bool r)
241{
242 current_bpm = r ? tick_sig.bpm() : 0;
243}
244
245void
246AudioTransport::set_beat (TickSignature::Beat b)
247{
248 const int64 newtick = tick_sig.beat_to_tick (b);
249 return_unless (newtick != current_tick);
250 set_tick (newtick);
251}
252
253void
254AudioTransport::set_tick (int64 newtick)
255{
256 current_tick = newtick;
257 current_tick_d = current_tick;
258 update_current();
259}
260
261void
262AudioTransport::tempo (double newbpm, uint8 numerator, uint8 denominator)
263{
264 tick_sig.set_bpm (CLAMP (newbpm, MIN_BPM, MAX_BPM));
265 tick_sig.set_signature (numerator, denominator);
266 current_bpm = current_bpm ? newbpm : 0;
267 update_current();
268}
269
270void
271AudioTransport::tempo (const TickSignature &ticksignature)
272{
273 tempo (ticksignature.bpm(), ticksignature.beats_per_bar(), ticksignature.beat_unit());
274}
275
276void
277AudioTransport::advance (uint nsamples)
278{
279 current_frame += nsamples;
280 if (ISLIKELY (current_bpm > 0.0))
281 {
282 current_tick_d += nsamples * tick_sig.ticks_per_sample();
283 current_tick = current_tick_d;
284 update_current();
285 }
286}
287
288void
289AudioTransport::update_current ()
290{
291 TickSignature::Beat beat = tick_sig.beat_from_tick (std::max (current_tick, 0l));
292 current_bar = beat.bar;
293 current_beat = beat.beat;
295 const auto old_next = next_bar_tick;
296 current_bar_tick = tick_sig.bar_to_tick (current_bar);
297 next_bar_tick = current_bar_tick + tick_sig.bar_ticks();
298
299 TickSignature::Time time = tick_sig.time_from_tick (current_tick);
300 current_minutes = time.minutes;
301 current_seconds = time.seconds;
302
303 if (old_next != next_bar_tick && false)
304 printerr ("%3d.%2d.%5.2f %02d:%06.3f frame=%d tick=%d next=%d bpm=%d sig=%d/%d ppqn=%d pps=%f rate=%d\n",
307 current_frame, current_tick, next_bar_tick,
308 current_bpm, tick_sig.beats_per_bar(), tick_sig.beat_unit(),
309 TRANSPORT_PPQN, tick_sig.ticks_per_sample(), samplerate);
310}
311
312} // Ase
313
314// == Testing ==
315#include "testing.hh"
316
317namespace { // Anon
318using namespace Ase;
319
320TEST_INTEGRITY (transport_tests);
321
322static void
323transport_tests()
324{
325 static_assert (SEMIQUAVER_TICKS == int32 (SEMIQUAVER_TICKS));
326 static_assert (TRANSPORT_PPQN == int32 (TRANSPORT_PPQN));
327 static_assert (TRANSPORT_PPQN % 16 == 0); // needed for beat_unit and semiquaver calculations
328 static_assert (TRANSPORT_PPQN % SEMIQUAVER_TICKS == 0);
329 const int64 max_semiquavers_per_beat = 16;
330 int64 max_beat_ticks = SEMIQUAVER_TICKS * max_semiquavers_per_beat;
331 int64 max_bar_ticks = max_beat_ticks * 64 / 1;
332 TASSERT (max_bar_ticks < 2147483648); // 2^31
333 static_assert (TRANSPORT_PPQN < 8388608); // 8388608 = 2^31 / (4*64)
334 int64 testtick = 170000000000077;
335 TickSignature ts { 60, 4, 4 };
336 const TickSignature::Time tt = ts.time_from_tick (testtick);
337 int32 hminutes, hours = divmod (tt.minutes, 60, &hminutes);
338 TickSignature::Beat tb = ts.beat_from_tick (testtick);
339 TCMP (ts.bar_from_tick (testtick), ==, tb.bar);
340 if (hours && false)
341 printerr ("%03d.%02d.%06.3f %02d:%02d:%06.3f tick=%d\n",
342 tb.bar, tb.beat, tb.semiquaver,
343 hours, hminutes, tt.seconds, testtick);
344 TCMP (ts.beat_to_tick (tb), ==, testtick);
345 TCMP (ts.time_to_tick (tt), ==, testtick);
346 tb.beat = 0;
347 tb.semiquaver = 0;
348 TCMP (ts.bar_to_tick (tb.bar), ==, ts.beat_to_tick (tb));
349}
350
351} // Anon
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:71
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
Definition internal.hh:58
#define if_constexpr
Indentation helper for editors that cannot (yet) decipher if constexpr
Definition internal.hh:40
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Definition internal.hh:77
#define ISLIKELY(cond)
Hint to the compiler to optimize for cond == TRUE.
Definition internal.hh:61
T max(T... args)
The Anklang C++ API namespace.
Definition api.hh:9
int32_t int32
A 32-bit signed integer.
Definition cxxaux.hh:28
SpeakerArrangement
Definition transport.hh:11
@ FRONT_LEFT
Stereo Left (FL)
@ FRONT_RIGHT
Stereo Right (FR)
@ AUX
Flag for side chain uses.
@ LOW_FREQUENCY
Low Frequency Effects (LFE)
@ MONO
Single Channel (M)
uint8_t uint8
An 8-bit unsigned integer.
Definition cxxaux.hh:22
int64_t int64
A 64-bit unsigned integer, use PRI*64 in format strings.
Definition cxxaux.hh:29
constexpr const int64 TRANSPORT_PPQN
Maximum number of sample frames to calculate in Processor::render().
Definition transport.hh:53
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
typedef uint64_t
int32 current_bar
Bar of current_tick position.
Definition transport.hh:126
double current_semiquaver
The sixteenth with fraction within beat.
Definition transport.hh:128
const uint samplerate
Sample rate (mixing frequency) in Hz used for rendering.
Definition transport.hh:115
double current_seconds
Seconds of current_tick position.
Definition transport.hh:131
int64 current_frame
Number of sample frames processed since playback start.
Definition transport.hh:122
int8 current_beat
Beat within bar of current_tick position.
Definition transport.hh:127
float current_bpm
Running tempo in beats per minute.
Definition transport.hh:129
int32 current_minutes
Minute of current_tick position.
Definition transport.hh:130
Musical time signature and tick conversions.
Definition transport.hh:61
double seconds
Seconds with fraction after the minute.
Definition transport.hh:84
Beat beat_from_tick(int64 tick) const
Calculate beat from tick, requires set_signature().
Definition transport.cc:112
int64 bar_to_tick(int32 bar) const
Calculate tick from bar, requires set_signature().
Definition transport.cc:143
double bpm_
Current tempo in beats per minute.
Definition transport.hh:69
void set_samplerate(uint samplerate)
Assign sample rate.
Definition transport.cc:47
uint8 beats_per_bar_
Upper numeral (numerator), how many beats constitute a bar.
Definition transport.hh:64
uint8 beat_unit_
Lower numeral (denominator in [1 2 4 8 16]), note value that represents one beat.
Definition transport.hh:65
double inv_samplerate_
Precalculated 1.0 / samplerate.
Definition transport.hh:75
int32 minutes
Tick position in minutes.
Definition transport.hh:83
int32 samplerate_
Sample rate (mixing frequency) in Hz.
Definition transport.hh:68
int64 time_to_tick(const Time &time) const
Calculate tick from time, requires set_bpm().
Definition transport.cc:85
int32 bar
Bar of tick position.
Definition transport.hh:78
int64 beat_to_tick(const Beat &beat) const
Calculate tick from beat, requires set_signature().
Definition transport.cc:125
double semiquaver
The sixteenth with fraction within beat.
Definition transport.hh:80
Time time_from_tick(int64 tick) const
Calculate time from tick, requires set_bpm().
Definition transport.cc:74
bool set_signature(uint8 beats_per_bar, uint8 beat_unit)
Assign time signature and offset for the signature to take effect.
Definition transport.cc:95
int32 bar_from_tick(int64 tick) const
Calculate bar from tick, requires set_signature().
Definition transport.cc:136
void set_bpm(double bpm)
Assign tempo in beats per minute.
Definition transport.cc:59
int8 beat
Beat within bar of tick position.
Definition transport.hh:79
#define TASSERT(cond)
Unconditional test assertion, enters breakpoint if not fullfilled.
Definition testing.hh:24
#define TCMP(a, cmp, b)
Compare a and b according to operator cmp, verbose on failiure.
Definition testing.hh:23
time