tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_AudioUtilities.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14void sanitiseValues (juce::AudioBuffer<float>& buffer, int start, int numSamps,
15 float maxAbsValue, float minAbsThreshold)
16{
17 for (int j = buffer.getNumChannels(); --j >= 0;)
18 {
19 float* d = buffer.getWritePointer (j, start);
20 jassert (numSamps <= buffer.getNumSamples());
21
22 for (int i = numSamps; --i >= 0;)
23 {
24 const float n = *d;
25
26 if (n >= minAbsThreshold)
27 {
28 if (n > maxAbsValue)
29 *d = maxAbsValue;
30 }
31 else if (n <= -minAbsThreshold)
32 {
33 if (n < -maxAbsValue)
34 *d = -maxAbsValue;
35 }
36 else
37 {
38 *d = 0;
39 }
40
41 ++d;
42 }
43 }
44}
45
46void resetFP() noexcept
47{
48 #if JUCE_WINDOWS
49 _fpreset();
50 #endif
51
52 #if JUCE_MAC && JUCE_INTEL
53 feclearexcept (FE_ALL_EXCEPT);
54 #endif
55}
56
57static unsigned short getFPStatus() noexcept
58{
59 unsigned short sw = 0;
60
61 #if JUCE_INTEL
62 #if JUCE_32BIT
63 #if JUCE_WINDOWS
64 _asm fstsw WORD PTR sw
65 #else
66 asm ("fstsw %%ax" : "=a" (sw));
67 #endif
68 #endif
69 #endif
70
71 return sw;
72}
73
74inline void clearFP() noexcept
75{
76 #if JUCE_WINDOWS
77 _clearfp();
78 #endif
79
80 #if JUCE_MAC && JUCE_INTEL
81 __asm __volatile ("fclex");
82 #endif
83}
84
85bool hasFloatingPointDenormaliseOccurred() noexcept
86{
87 if ((getFPStatus() & 0x02) != 0)
88 {
89 clearFP();
90 return true;
91 }
92
93 return false;
94}
95
96void zeroDenormalisedValuesIfNeeded (juce::AudioBuffer<float>& buffer) noexcept
97{
98 juce::ignoreUnused (buffer);
99
100 #if JUCE_INTEL
101 if (hasFloatingPointDenormaliseOccurred())
102 if (! buffer.hasBeenCleared())
103 sanitiseValues (buffer, 0, buffer.getNumSamples(), 100.0f);
104 #endif
105}
106
107void addAntiDenormalisationNoise (juce::AudioBuffer<float>& buffer, int start, int numSamples) noexcept
108{
109 juce::ignoreUnused (buffer, start, numSamples);
110
111 #if JUCE_INTEL
112 if (buffer.hasBeenCleared())
113 {
114 for (int i = buffer.getNumChannels(); --i >= 0;)
115 juce::FloatVectorOperations::fill (buffer.getWritePointer (i, start), 0.000000045f, numSamples);
116 }
117 else
118 {
119 for (int i = buffer.getNumChannels(); --i >= 0;)
120 juce::FloatVectorOperations::add (buffer.getWritePointer (i, start), 0.000000045f, numSamples);
121 }
122 #endif
123}
124
125//==============================================================================
126float dbToGain (float db) noexcept
127{
128 return std::pow (10.0f, db * (1.0f / 20.0f));
129}
130
131float gainToDb (float gain) noexcept
132{
133 return (gain > 0.0f) ? 20.0f * std::log10 (gain) : -100.0f;
134}
135
136static const float volScaleFactor = 20.0f;
137
138float decibelsToVolumeFaderPosition (float db) noexcept
139{
140 return (db > -100.0f) ? std::exp ((db - 6.0f) * (1.0f / volScaleFactor)) : 0.0f;
141}
142
143float volumeFaderPositionToDB (float pos) noexcept
144{
145 return (pos > 0.0f) ? (volScaleFactor * std::log (pos)) + 6.0f : -100.0f;
146}
147
148float volumeFaderPositionToGain (float pos) noexcept
149{
150 return (pos > 0.0f) ? std::pow (10.0f, ((volScaleFactor * logf (pos)) + 6.0f) * (1.0f / 20.0f)) : 0.0f;
151}
152
153float gainToVolumeFaderPosition (float gain) noexcept
154{
155 return (gain > 0.0f) ? std::exp (((20.0f * std::log10 (gain)) - 6.0f) * (1.0f / volScaleFactor)) : 0.0f;
156}
157
158juce::String gainToDbString (float gain, float infLevel, int decPlaces)
159{
160 return juce::Decibels::toString (gainToDb (gain), decPlaces, infLevel);
161}
162
163float dbStringToDb (const juce::String& dbStr)
164{
165 if (dbStr.contains ("INF"))
166 return -100.0f;
167
168 return dbStr.retainCharacters ("0123456789.-").getFloatValue();
169}
170
171float dbStringToGain (const juce::String& dbStr)
172{
173 if (dbStr.contains ("INF"))
174 return 0.0f;
175
176 return dbToGain (dbStringToDb (dbStr));
177}
178
179juce::String getPanString (float pan)
180{
181 if (std::abs (pan) < 0.001f)
182 return TRANS("Centre");
183
184 const juce::String s (pan, 3);
185
186 if (pan < 0.0f)
187 return s + " " + TRANS("Left");
188
189 return "+" + s + " " + TRANS("Right");
190}
191
192juce::String getSemitonesAsString (double semitones)
193{
194 juce::String t;
195
196 if (semitones > 0)
197 t << "+";
198
199 if (std::abs (semitones - (int) semitones) < 0.01)
200 t << (int) semitones;
201 else
202 t << juce::String (semitones, 2);
203
204 if (std::abs (semitones) != 1)
205 return TRANS("33 semitones").replace ("33", t);
206
207 return TRANS("1 semitone").replace ("1", t);
208}
209
210static bool isNotSilent (float v) noexcept
211{
212 const float zeroThresh = 1.0f / 8000.0f;
213 return v < -zeroThresh || v > zeroThresh;
214}
215
216bool isAudioDataAlmostSilent (const float* data, int num)
217{
218 return ! (isNotSilent (data[0])
219 || isNotSilent (data[num / 2])
220 || isNotSilent (getAudioDataMagnitude (data, num)));
221}
222
223float getAudioDataMagnitude (const float* data, int num)
224{
225 auto range = juce::FloatVectorOperations::findMinAndMax (data, num);
226
227 return std::max (std::abs (range.getStart()),
228 std::abs (range.getEnd()));
229}
230
231//==============================================================================
232void getGainsFromVolumeFaderPositionAndPan (float volSliderPos, float pan, const PanLaw panLaw,
233 float& leftGain, float& rightGain) noexcept
234{
235 const float gain = volumeFaderPositionToGain (volSliderPos);
236
237 if (panLaw == PanLawLinear)
238 {
239 const float pv = pan * gain;
240
241 leftGain = gain - pv;
242 rightGain = gain + pv;
243 }
244 else
245 {
246 const float halfPi = juce::MathConstants<float>::pi * 0.5f;
247
248 pan = (pan + 1.0f) * 0.5f; // Scale to range (0.0f, 1.0f) from (-1.0f, 1.0f) for simplicity
249
250 leftGain = std::sin ((1.0f - pan) * halfPi);
251 rightGain = std::sin (pan * halfPi);
252
253 float ratio = 1.0f;
254
255 switch (panLaw)
256 {
257 case PanLaw3dBCenter: ratio = 1.0f; break;
258 case PanLaw2point5dBCenter: ratio = 2.5f / 3.0f; break;
259 case PanLaw4point5dBCenter: ratio = 1.5f; break;
260 case PanLaw6dBCenter: ratio = 2.0f; break;
261
262 case PanLawDefault:
263 case PanLawLinear:
264 jassertfalse; // should have alread been handled?
265 break;
266
267 default:
268 jassertfalse; // New pan law?
269 break;
270 }
271
272 leftGain = std::pow (leftGain, ratio) * gain;
273 rightGain = std::pow (rightGain, ratio) * gain;
274 }
275}
276
277static PanLaw defaultPanLaw = PanLawLinear;
278
279PanLaw getDefaultPanLaw() noexcept
280{
281 return defaultPanLaw;
282}
283
284void setDefaultPanLaw (const PanLaw panLaw)
285{
286 defaultPanLaw = panLaw;
287}
288
289juce::StringArray getPanLawChoices (bool includeDefault) noexcept
290{
292
293 if (includeDefault)
294 s.add ("(" + TRANS("Use Default") + ")");
295
296 s.add (TRANS("Linear"));
297 s.add (TRANS("-2.5 dB Center"));
298 s.add (TRANS("-3.0 dB Center"));
299 s.add (TRANS("-4.5 dB Center"));
300 s.add (TRANS("-6.0 dB Center"));
301
302 return s;
303}
304
305//==============================================================================
306void convertIntsToFloats (juce::AudioBuffer<float>& buffer)
307{
308 for (int i = buffer.getNumChannels(); --i >= 0;)
309 {
310 float* d = buffer.getWritePointer (i);
311 juce::FloatVectorOperations::convertFixedToFloat (d, (const int*) d, 1.0f / (double) 0x7fffffff, buffer.getNumSamples());
312 }
313}
314
315void convertFloatsToInts (juce::AudioBuffer<float>& buffer)
316{
317 for (int j = buffer.getNumChannels(); --j >= 0;)
318 {
319 int* d = (int*) buffer.getWritePointer(j);
320
321 for (int i = buffer.getNumSamples(); --i >= 0;)
322 {
323 const double samp = *(const float*) d;
324
325 if (samp <= -1.0) *d++ = std::numeric_limits<int>::min();
326 else if (samp >= 1.0) *d++ = std::numeric_limits<int>::max();
327 else *d++ = juce::roundToInt (std::numeric_limits<int>::max() * samp);
328 }
329 }
330}
331
332//==============================================================================
334{
335 float* channel0;
336
337 inline void apply (float value) noexcept
338 {
339 *channel0++ *= value;
340 }
341};
342
344{
345 float* channel0;
346 float* channel1;
347
348 inline void apply (float value) noexcept
349 {
350 *channel0++ *= value;
351 *channel1++ *= value;
352 }
353};
354
355void AudioFadeCurve::applyCrossfadeSection (juce::AudioBuffer<float>& buffer,
356 int channel, int startSample, int numSamples,
357 AudioFadeCurve::Type type, float startAlpha, float endAlpha)
358{
359 jassert (startSample >= 0 && startSample + numSamples <= buffer.getNumSamples());
360
361 if (! buffer.hasBeenCleared())
362 {
363 OneChannelDestination d = { buffer.getWritePointer (channel, startSample) };
364 AudioFadeCurve::renderBlockForType (d, numSamples, startAlpha, endAlpha, type);
365 }
366}
367
368void AudioFadeCurve::applyCrossfadeSection (juce::AudioBuffer<float>& buffer,
369 int startSample, int numSamples, AudioFadeCurve::Type type,
370 float startAlpha, float endAlpha)
371{
372 jassert (startSample >= 0 && startSample + numSamples <= buffer.getNumSamples() && numSamples > 0);
373
374 if (! buffer.hasBeenCleared())
375 {
376 if (buffer.getNumChannels() == 2)
377 {
378 TwoChannelDestination d = { buffer.getWritePointer (0, startSample),
379 buffer.getWritePointer (1, startSample) };
380
381 AudioFadeCurve::renderBlockForType (d, numSamples, startAlpha, endAlpha, type);
382 }
383 else
384 {
385 for (int i = buffer.getNumChannels(); --i >= 0;)
386 applyCrossfadeSection (buffer, i, startSample, numSamples, type, startAlpha, endAlpha);
387 }
388 }
389}
390
392{
393 float* dest;
394 const float* src;
395
396 inline void apply (float value) noexcept
397 {
398 *dest++ += *src++ * value;
399 }
400};
401
402void AudioFadeCurve::addWithCrossfade (juce::AudioBuffer<float>& dest,
403 const juce::AudioBuffer<float>& src,
404 int destChannel, int destStartIndex,
405 int sourceChannel, int sourceStartIndex,
406 const int numSamples,
407 const AudioFadeCurve::Type type,
408 const float startAlpha,
409 const float endAlpha)
410{
411 if (! src.hasBeenCleared())
412 {
413 if (startAlpha == endAlpha)
414 {
415 dest.addFrom (destChannel, destStartIndex, src, sourceChannel, sourceStartIndex, numSamples, endAlpha);
416 }
417 else
418 {
419 AddingFadeApplier d = { dest.getWritePointer (destChannel, destStartIndex),
420 src.getReadPointer (sourceChannel, sourceStartIndex) };
421
422 AudioFadeCurve::renderBlockForType (d, numSamples, startAlpha, endAlpha, type);
423 }
424 }
425}
426
427void AudioFadeCurve::drawFadeCurve (juce::Graphics& g, const AudioFadeCurve::Type fadeType,
428 float x1, float x2, float top, float bottom, juce::Rectangle<int> clip)
429{
430 const float fadeLineThickness = 1.5f;
431
432 juce::Path s;
433
434 if (fadeType == AudioFadeCurve::linear)
435 {
436 s.startNewSubPath (x1, bottom);
437 s.lineTo (x2, top);
438 }
439 else
440 {
441 float left = (clip.getX() & ~3) - 3.0f;
442 float right = clip.getRight() + 5.0f;
443
444 left = std::max (left, std::min (x1, x2));
445 right = std::min (right, std::max (x1, x2));
446
447 s.startNewSubPath (left, x1 < x2 ? bottom : top);
448
449 for (float x = left; x < right; x += 3.0f)
450 s.lineTo (x, bottom - (bottom - top) * AudioFadeCurve::alphaToGainForType (fadeType, (x - x1) / (x2 - x1)));
451
452 s.lineTo (right, x1 > x2 ? bottom : top);
453 }
454
455 g.strokePath (s, juce::PathStrokeType (fadeLineThickness));
456
457 s.lineTo (x1, top);
458 s.closeSubPath();
459
460 g.setOpacity (0.3f);
461 g.fillPath (s);
462}
463
464//==============================================================================
466{
467 juce::AudioBuffer<float> buffer { 2, 41000 };
468 std::atomic<bool> isFree { true };
469};
470
472{
473 BufferList()
474 {
475 for (int i = 8; --i >= 0;)
476 buffers.add (new Buffer());
477 }
478
480 {
481 clearSingletonInstance();
482 }
483
485
486 Buffer* get()
487 {
488 {
489 const std::shared_lock sl (mutex);
490
491 for (auto b : buffers)
492 if (b->isFree.exchange (false, std::memory_order_acq_rel))
493 return b;
494 }
495
496 auto newBuffer = new Buffer();
497 newBuffer->isFree = false;
498
499 const std::unique_lock sl (mutex);
500 buffers.add (newBuffer);
501 return newBuffer;
502 }
503
504 std::shared_mutex mutex;
506
508};
509
511
512AudioScratchBuffer::AudioScratchBuffer (int numChans, int numSamples)
513 : allocatedBuffer (BufferList::getInstance()->get()),
514 buffer (allocatedBuffer->buffer)
515{
516 buffer.setSize (numChans, numSamples, false, false, true);
517}
518
520 : allocatedBuffer (BufferList::getInstance()->get()),
521 buffer (allocatedBuffer->buffer)
522{
523 const int chans = srcBuffer.getNumChannels();
524 const int samps = srcBuffer.getNumSamples();
525
526 buffer.setSize (chans, samps, false, false, true);
527
528 for (int i = 0; i < chans; ++i)
529 buffer.copyFrom (i, 0, srcBuffer.getReadPointer (i), samps);
530}
531
533{
534 allocatedBuffer->isFree = true;
535}
536
538{
539 BufferList::getInstance();
540}
541
542
543//==============================================================================
544//==============================================================================
545#if TRACKTION_UNIT_TESTS && ENGINE_UNIT_TESTS_PAN_LAW
546
547class PanLawTests : public juce::UnitTest
548{
549public:
550 PanLawTests() : juce::UnitTest ("PanLaw", "tracktion_engine") {}
551
552 //==============================================================================
553 void runTest() override
554 {
555 // N.B. Linear pan boosts L/R signals when panned (up to +6dB)
556 // Equal power modes are 0dB hard L/R
557
558 beginTest ("Linear");
559 {
560 testPanLaw (PanLawLinear, 0.0f, 0.0f, 0.0f, 0.0f); // Zero
561 testPanLaw (PanLawLinear, 1.0f, 0.0f, 1.0f, 1.0f); // Unity
562 testPanLaw (PanLawLinear, 1.0f, -1.0f, 2.0f, 0.0f); // Hard left
563 testPanLaw (PanLawLinear, 1.0f, 1.0f, 0.0f, 2.0f); // Hard right
564
565 testPanLaw (PanLawLinear, 0.5f, 0.0f, 0.5f, 0.5f); // Centre -6dB
566 testPanLaw (PanLawLinear, 0.5f, -1.0f, 1.0f, 0.0f); // Hard left -6dB
567 testPanLaw (PanLawLinear, 0.5f, 1.0f, 0.0f, 1.0f); // Hard right -6dB
568
569 testPanLaw (PanLawLinear, 1.0f, -0.5f, 1.5f, 0.5f); // 1/2 left
570 testPanLaw (PanLawLinear, 1.0f, 0.5f, 0.5f, 1.5f); // 1 right
571 }
572
573 beginTest ("-2.5dB");
574 {
575 const auto centreGain = juce::Decibels::decibelsToGain (-2.5f);
576 testPanLaw (PanLaw2point5dBCenter, 0.0f, 0.0f, 0.0f, 0.0f); // Zero
577 testPanLaw (PanLaw2point5dBCenter, 1.0f, 0.0f, centreGain, centreGain); // Unity
578 testPanLaw (PanLaw2point5dBCenter, 1.0f, -1.0f, 1.0f, 0.0f); // Hard left
579 testPanLaw (PanLaw2point5dBCenter, 1.0f, 1.0f, 0.0f, 1.0f); // Hard right
580 }
581
582 beginTest ("-3dB");
583 {
584 const auto centreGain = juce::Decibels::decibelsToGain (-3.0f);
585 testPanLaw (PanLaw3dBCenter, 0.0f, 0.0f, 0.0f, 0.0f); // Zero
586 testPanLaw (PanLaw3dBCenter, 1.0f, 0.0f, centreGain, centreGain); // Unity
587 testPanLaw (PanLaw3dBCenter, 1.0f, -1.0f, 1.0f, 0.0f); // Hard left
588 testPanLaw (PanLaw3dBCenter, 1.0f, 1.0f, 0.0f, 1.0f); // Hard right
589 }
590
591 beginTest ("-4.5dB");
592 {
593 const auto centreGain = juce::Decibels::decibelsToGain (-4.5f);
594 testPanLaw (PanLaw4point5dBCenter, 0.0f, 0.0f, 0.0f, 0.0f); // Zero
595 testPanLaw (PanLaw4point5dBCenter, 1.0f, 0.0f, centreGain, centreGain); // Unity
596 testPanLaw (PanLaw4point5dBCenter, 1.0f, -1.0f, 1.0f, 0.0f); // Hard left
597 testPanLaw (PanLaw4point5dBCenter, 1.0f, 1.0f, 0.0f, 1.0f); // Hard right
598 }
599
600 beginTest ("-6dB");
601 {
602 const auto centreGain = juce::Decibels::decibelsToGain (-6.0f);
603 testPanLaw (PanLaw6dBCenter, 0.0f, 0.0f, 0.0f, 0.0f); // Zero
604 testPanLaw (PanLaw6dBCenter, 1.0f, 0.0f, centreGain, centreGain); // Unity
605 testPanLaw (PanLaw6dBCenter, 1.0f, -1.0f, 1.0f, 0.0f); // Hard left
606 testPanLaw (PanLaw6dBCenter, 1.0f, 1.0f, 0.0f, 1.0f); // Hard right
607 }
608 }
609
610private:
611 void testPanLaw (PanLaw pl, float gain, float pan,
612 float expectedLeftGain, float expectedRightGain)
613 {
614 expectGreaterOrEqual (gain, 0.0f);
615 expectGreaterOrEqual (pan, -1.0f);
616 expectLessOrEqual (pan, 1.0f);
617
618 float leftGain = 0.0;
619 float rightGain = 0.0;
620 getGainsFromVolumeFaderPositionAndPan (gainToVolumeFaderPosition (gain), pan, pl,
621 leftGain, rightGain);
622 expectWithinAbsoluteError (leftGain, expectedLeftGain, 0.01f);
623 expectWithinAbsoluteError (rightGain, expectedRightGain, 0.01f);
624 }
625};
626
627static PanLawTests panLawTests;
628
629#endif // TRACKTION_UNIT_TESTS
630
631}} // namespace tracktion { inline namespace engine
void setSize(int newNumChannels, int newNumSamples, bool keepExistingContent=false, bool clearExtraSpace=false, bool avoidReallocating=false)
Type * getWritePointer(int channelNumber) noexcept
int getNumChannels() const noexcept
int getNumSamples() const noexcept
void copyFrom(int destChannel, int destStartSample, const AudioBuffer &source, int sourceChannel, int sourceStartSample, int numSamples) noexcept
void addFrom(int destChannel, int destStartSample, const AudioBuffer &source, int sourceChannel, int sourceStartSample, int numSamples, Type gainToApplyToSource=Type(1)) noexcept
bool hasBeenCleared() const noexcept
const Type * getReadPointer(int channelNumber) const noexcept
static Type decibelsToGain(Type decibels, Type minusInfinityDb=Type(defaultMinusInfinitydB))
static String toString(Type decibels, int decimalPlaces=2, Type minusInfinityDb=Type(defaultMinusInfinitydB), bool shouldIncludeSuffix=true, StringRef customMinusInfinityString={})
void setOpacity(float newOpacity)
void fillPath(const Path &path) const
void strokePath(const Path &path, const PathStrokeType &strokeType, const AffineTransform &transform={}) const
void startNewSubPath(float startX, float startY)
void closeSubPath()
void lineTo(float endX, float endY)
void add(String stringToAdd)
float getFloatValue() const noexcept
String retainCharacters(StringRef charactersToRetain) const
bool contains(StringRef text) const noexcept
An audio scratch buffer that has pooled storage.
juce::AudioBuffer< float > & buffer
The buffer to use.
AudioScratchBuffer(int numChans, int numSamples)
Creates a buffer for a given number of channels and samples.
static void initialise()
Initialises the internal buffer list.
T exp(T... args)
feclearexcept
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
#define JUCE_IMPLEMENT_SINGLETON(Classname)
#define JUCE_DECLARE_SINGLETON(Classname, doNotRecreateAfterDeletion)
T left(T... args)
typedef int
T log10(T... args)
T log(T... args)
T max(T... args)
T min(T... args)
void ignoreUnused(Types &&...) noexcept
int roundToInt(const FloatType value) noexcept
PanLaw
All laws have been designed to be equal-power, excluding linear respectively.
T pow(T... args)
T sin(T... args)
Type
A enumeration of the curve classes available.
static void renderBlockForType(DestSamplePointer &dest, int numSamples, float startAlpha, float endAlpha, Type type) noexcept
Multiplies a block of samples by the curve shape between two alpha-positions.
static float alphaToGainForType(Type type, float alpha) noexcept
Converts an alpha position along the curve (0 to 1.0) into the gain at that point.