OpenShot Audio Library | OpenShotAudio 0.4.0
Loading...
Searching...
No Matches
juce_SmoothedValue.h
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26//==============================================================================
35template <typename SmoothedValueType>
37{
38private:
39 //==============================================================================
40 template <typename T> struct FloatTypeHelper;
41
42 template <template <typename> class SmoothedValueClass, typename FloatType>
43 struct FloatTypeHelper <SmoothedValueClass <FloatType>>
44 {
45 using Type = FloatType;
46 };
47
48 template <template <typename, typename> class SmoothedValueClass, typename FloatType, typename SmoothingType>
49 struct FloatTypeHelper <SmoothedValueClass <FloatType, SmoothingType>>
50 {
51 using Type = FloatType;
52 };
53
54public:
55 using FloatType = typename FloatTypeHelper<SmoothedValueType>::Type;
56
57 //==============================================================================
59 SmoothedValueBase() = default;
60
61 //==============================================================================
63 bool isSmoothing() const noexcept { return countdown > 0; }
64
66 FloatType getCurrentValue() const noexcept { return currentValue; }
67
68 //==============================================================================
70 FloatType getTargetValue() const noexcept { return target; }
71
75 void setCurrentAndTargetValue (FloatType newValue)
76 {
77 target = currentValue = newValue;
78 countdown = 0;
79 }
80
81 //==============================================================================
87 void applyGain (FloatType* samples, int numSamples) noexcept
88 {
89 jassert (numSamples >= 0);
90
91 if (isSmoothing())
92 {
93 for (int i = 0; i < numSamples; ++i)
94 samples[i] *= getNextSmoothedValue();
95 }
96 else
97 {
98 FloatVectorOperations::multiply (samples, target, numSamples);
99 }
100 }
101
108 void applyGain (FloatType* samplesOut, const FloatType* samplesIn, int numSamples) noexcept
109 {
110 jassert (numSamples >= 0);
111
112 if (isSmoothing())
113 {
114 for (int i = 0; i < numSamples; ++i)
115 samplesOut[i] = samplesIn[i] * getNextSmoothedValue();
116 }
117 else
118 {
119 FloatVectorOperations::multiply (samplesOut, samplesIn, target, numSamples);
120 }
121 }
122
124 void applyGain (AudioBuffer<FloatType>& buffer, int numSamples) noexcept
125 {
126 jassert (numSamples >= 0);
127
128 if (isSmoothing())
129 {
130 if (buffer.getNumChannels() == 1)
131 {
132 auto* samples = buffer.getWritePointer (0);
133
134 for (int i = 0; i < numSamples; ++i)
135 samples[i] *= getNextSmoothedValue();
136 }
137 else
138 {
139 for (auto i = 0; i < numSamples; ++i)
140 {
141 auto gain = getNextSmoothedValue();
142
143 for (int channel = 0; channel < buffer.getNumChannels(); channel++)
144 buffer.setSample (channel, i, buffer.getSample (channel, i) * gain);
145 }
146 }
147 }
148 else
149 {
150 buffer.applyGain (0, numSamples, target);
151 }
152 }
153
154private:
155 //==============================================================================
156 FloatType getNextSmoothedValue() noexcept
157 {
158 return static_cast <SmoothedValueType*> (this)->getNextValue();
159 }
160
161protected:
162 //==============================================================================
163 FloatType currentValue = 0;
164 FloatType target = currentValue;
165 int countdown = 0;
166};
167
168//==============================================================================
178namespace ValueSmoothingTypes
179{
185 struct Linear {};
186
192 struct Multiplicative {};
193}
194
195//==============================================================================
225template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
226class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
227{
228public:
229 //==============================================================================
231 SmoothedValue() noexcept
232 : SmoothedValue ((FloatType) (std::is_same_v<SmoothingType, ValueSmoothingTypes::Linear> ? 0 : 1))
233 {
234 }
235
237 SmoothedValue (FloatType initialValue) noexcept
238 {
239 // Multiplicative smoothed values cannot ever reach 0!
240 jassert (! (std::is_same_v<SmoothingType, ValueSmoothingTypes::Multiplicative>
241 && approximatelyEqual (initialValue, (FloatType) 0)));
242
243 // Visual Studio can't handle base class initialisation with CRTP
244 this->currentValue = initialValue;
245 this->target = this->currentValue;
246 }
247
248 //==============================================================================
253 void reset (double sampleRate, double rampLengthInSeconds) noexcept
254 {
255 jassert (sampleRate > 0 && rampLengthInSeconds >= 0);
256 reset ((int) std::floor (rampLengthInSeconds * sampleRate));
257 }
258
262 void reset (int numSteps) noexcept
263 {
264 stepsToTarget = numSteps;
265 this->setCurrentAndTargetValue (this->target);
266 }
267
268 //==============================================================================
272 void setTargetValue (FloatType newValue) noexcept
273 {
274 if (approximatelyEqual (newValue, this->target))
275 return;
276
277 if (stepsToTarget <= 0)
278 {
279 this->setCurrentAndTargetValue (newValue);
280 return;
281 }
282
283 // Multiplicative smoothed values cannot ever reach 0!
284 jassert (! (std::is_same_v<SmoothingType, ValueSmoothingTypes::Multiplicative>
285 && approximatelyEqual (newValue, (FloatType) 0)));
286
287 this->target = newValue;
288 this->countdown = stepsToTarget;
289
290 setStepSize();
291 }
292
293 //==============================================================================
297 FloatType getNextValue() noexcept
298 {
299 if (! this->isSmoothing())
300 return this->target;
301
302 --(this->countdown);
303
304 if (this->isSmoothing())
305 setNextValue();
306 else
307 this->currentValue = this->target;
308
309 return this->currentValue;
310 }
311
312 //==============================================================================
318 FloatType skip (int numSamples) noexcept
319 {
320 if (numSamples >= this->countdown)
321 {
322 this->setCurrentAndTargetValue (this->target);
323 return this->target;
324 }
325
326 skipCurrentValue (numSamples);
327
328 this->countdown -= numSamples;
329 return this->currentValue;
330 }
331
332 //==============================================================================
333 #ifndef DOXYGEN
342 [[deprecated ("Use setTargetValue and setCurrentAndTargetValue instead.")]]
343 void setValue (FloatType newValue, bool force = false) noexcept
344 {
345 if (force)
346 {
347 this->setCurrentAndTargetValue (newValue);
348 return;
349 }
350
351 setTargetValue (newValue);
352 }
353 #endif
354
355private:
356 //==============================================================================
357 template <typename T = SmoothingType>
358 void setStepSize() noexcept
359 {
360 if constexpr (std::is_same_v<T, ValueSmoothingTypes::Linear>)
361 {
362 step = (this->target - this->currentValue) / (FloatType) this->countdown;
363 }
364 else if constexpr (std::is_same_v<T, ValueSmoothingTypes::Multiplicative>)
365 {
366 step = std::exp ((std::log (std::abs (this->target)) - std::log (std::abs (this->currentValue))) / (FloatType) this->countdown);
367 }
368 }
369
370 //==============================================================================
371 template <typename T = SmoothingType>
372 void setNextValue() noexcept
373 {
374 if constexpr (std::is_same_v<T, ValueSmoothingTypes::Linear>)
375 {
376 this->currentValue += step;
377 }
378 else if constexpr (std::is_same_v<T, ValueSmoothingTypes::Multiplicative>)
379 {
380 this->currentValue *= step;
381 }
382 }
383
384 //==============================================================================
385 template <typename T = SmoothingType>
386 void skipCurrentValue (int numSamples) noexcept
387 {
388 if constexpr (std::is_same_v<T, ValueSmoothingTypes::Linear>)
389 {
390 this->currentValue += step * (FloatType) numSamples;
391 }
392 else if constexpr (std::is_same_v<T, ValueSmoothingTypes::Multiplicative>)
393 {
394 this->currentValue *= (FloatType) std::pow (step, numSamples);
395 }
396 }
397
398 //==============================================================================
399 FloatType step = FloatType();
400 int stepsToTarget = 0;
401};
402
403template <typename FloatType>
404using LinearSmoothedValue = SmoothedValue <FloatType, ValueSmoothingTypes::Linear>;
405
406
407//==============================================================================
408//==============================================================================
409#if JUCE_UNIT_TESTS
410
411template <class SmoothedValueType>
412class CommonSmoothedValueTests : public UnitTest
413{
414public:
415 CommonSmoothedValueTests()
416 : UnitTest ("CommonSmoothedValueTests", UnitTestCategories::smoothedValues)
417 {}
418
419 void runTest() override
420 {
421 beginTest ("Initial state");
422 {
423 SmoothedValueType sv;
424
425 auto value = sv.getCurrentValue();
426 expectEquals (sv.getTargetValue(), value);
427
428 sv.getNextValue();
429 expectEquals (sv.getCurrentValue(), value);
430 expect (! sv.isSmoothing());
431 }
432
433 beginTest ("Resetting");
434 {
435 auto initialValue = 15.0f;
436
437 SmoothedValueType sv (initialValue);
438 sv.reset (3);
439 expectEquals (sv.getCurrentValue(), initialValue);
440
441 auto targetValue = initialValue + 1.0f;
442 sv.setTargetValue (targetValue);
443 expectEquals (sv.getTargetValue(), targetValue);
444 expectEquals (sv.getCurrentValue(), initialValue);
445 expect (sv.isSmoothing());
446
447 auto currentValue = sv.getNextValue();
448 expect (currentValue > initialValue);
449 expectEquals (sv.getCurrentValue(), currentValue);
450 expectEquals (sv.getTargetValue(), targetValue);
451 expect (sv.isSmoothing());
452
453 sv.reset (5);
454
455 expectEquals (sv.getCurrentValue(), targetValue);
456 expectEquals (sv.getTargetValue(), targetValue);
457 expect (! sv.isSmoothing());
458
459 sv.getNextValue();
460 expectEquals (sv.getCurrentValue(), targetValue);
461
462 sv.setTargetValue (1.5f);
463 sv.getNextValue();
464
465 float newStart = 0.2f;
466 sv.setCurrentAndTargetValue (newStart);
467 expectEquals (sv.getNextValue(), newStart);
468 expectEquals (sv.getTargetValue(), newStart);
469 expectEquals (sv.getCurrentValue(), newStart);
470 expect (! sv.isSmoothing());
471 }
472
473 beginTest ("Sample rate");
474 {
475 SmoothedValueType svSamples { 3.0f };
476 auto svTime = svSamples;
477
478 auto numSamples = 12;
479
480 svSamples.reset (numSamples);
481 svTime.reset (numSamples * 2, 1.0);
482
483 for (int i = 0; i < numSamples; ++i)
484 {
485 svTime.skip (1);
486 expectWithinAbsoluteError (svSamples.getNextValue(),
487 svTime.getNextValue(),
488 1.0e-7f);
489 }
490 }
491
492 beginTest ("Block processing");
493 {
494 SmoothedValueType sv (1.0f);
495
496 sv.reset (12);
497 sv.setTargetValue (2.0f);
498
499 const auto numSamples = 15;
500
501 AudioBuffer<float> referenceData (1, numSamples);
502
503 for (int i = 0; i < numSamples; ++i)
504 referenceData.setSample (0, i, sv.getNextValue());
505
506 expect (referenceData.getSample (0, 0) > 0);
507 expect (referenceData.getSample (0, 10) < sv.getTargetValue());
508 expectWithinAbsoluteError (referenceData.getSample (0, 11),
509 sv.getTargetValue(),
510 2.0e-7f);
511
512 auto getUnitData = [] (int numSamplesToGenerate)
513 {
514 AudioBuffer<float> result (1, numSamplesToGenerate);
515
516 for (int i = 0; i < numSamplesToGenerate; ++i)
517 result.setSample (0, i, 1.0f);
518
519 return result;
520 };
521
522 auto compareData = [this] (const AudioBuffer<float>& test,
523 const AudioBuffer<float>& reference)
524 {
525 for (int i = 0; i < test.getNumSamples(); ++i)
526 expectWithinAbsoluteError (test.getSample (0, i),
527 reference.getSample (0, i),
528 2.0e-7f);
529 };
530
531 auto testData = getUnitData (numSamples);
532 sv.setCurrentAndTargetValue (1.0f);
533 sv.setTargetValue (2.0f);
534 sv.applyGain (testData.getWritePointer (0), numSamples);
535 compareData (testData, referenceData);
536
537 testData = getUnitData (numSamples);
538 AudioBuffer<float> destData (1, numSamples);
539 sv.setCurrentAndTargetValue (1.0f);
540 sv.setTargetValue (2.0f);
541 sv.applyGain (destData.getWritePointer (0),
542 testData.getReadPointer (0),
543 numSamples);
544 compareData (destData, referenceData);
545 compareData (testData, getUnitData (numSamples));
546
547 testData = getUnitData (numSamples);
548 sv.setCurrentAndTargetValue (1.0f);
549 sv.setTargetValue (2.0f);
550 sv.applyGain (testData, numSamples);
551 compareData (testData, referenceData);
552 }
553
554 beginTest ("Skip");
555 {
556 SmoothedValueType sv;
557
558 sv.reset (12);
559 sv.setCurrentAndTargetValue (1.0f);
560 sv.setTargetValue (2.0f);
561
562 Array<float> reference;
563
564 for (int i = 0; i < 15; ++i)
565 reference.add (sv.getNextValue());
566
567 sv.setCurrentAndTargetValue (1.0f);
568 sv.setTargetValue (2.0f);
569
570 expectWithinAbsoluteError (sv.skip (1), reference[0], 1.0e-6f);
571 expectWithinAbsoluteError (sv.skip (1), reference[1], 1.0e-6f);
572 expectWithinAbsoluteError (sv.skip (2), reference[3], 1.0e-6f);
573 sv.skip (3);
574 expectWithinAbsoluteError (sv.getCurrentValue(), reference[6], 1.0e-6f);
575 expectEquals (sv.skip (300), sv.getTargetValue());
576 expectEquals (sv.getCurrentValue(), sv.getTargetValue());
577 }
578
579 beginTest ("Negative");
580 {
581 SmoothedValueType sv;
582
583 auto numValues = 12;
584 sv.reset (numValues);
585
586 std::vector<std::pair<float, float>> ranges = { { -1.0f, -2.0f },
587 { -100.0f, -3.0f } };
588
589 for (auto range : ranges)
590 {
591 auto start = range.first, end = range.second;
592
593 sv.setCurrentAndTargetValue (start);
594 sv.setTargetValue (end);
595
596 auto val = sv.skip (numValues / 2);
597
598 if (end > start)
599 expect (val > start && val < end);
600 else
601 expect (val < start && val > end);
602
603 auto nextVal = sv.getNextValue();
604 expect (end > start ? (nextVal > val) : (nextVal < val));
605
606 auto endVal = sv.skip (500);
607 expectEquals (endVal, end);
608 expectEquals (sv.getNextValue(), end);
609 expectEquals (sv.getCurrentValue(), end);
610
611 sv.setCurrentAndTargetValue (start);
612 sv.setTargetValue (end);
613
614 SmoothedValueType positiveSv { -start };
615 positiveSv.reset (numValues);
616 positiveSv.setTargetValue (-end);
617
618 for (int i = 0; i < numValues + 2; ++i)
619 expectEquals (sv.getNextValue(), -positiveSv.getNextValue());
620 }
621 }
622 }
623};
624
625#endif
626
627} // namespace juce
bool isSmoothing() const noexcept
void applyGain(FloatType *samples, int numSamples) noexcept
void applyGain(AudioBuffer< FloatType > &buffer, int numSamples) noexcept
void setCurrentAndTargetValue(FloatType newValue)
FloatType getTargetValue() const noexcept
FloatType getCurrentValue() const noexcept
void applyGain(FloatType *samplesOut, const FloatType *samplesIn, int numSamples) noexcept
FloatType skip(int numSamples) noexcept
FloatType getNextValue() noexcept
void setValue(FloatType newValue, bool force=false) noexcept
SmoothedValue(FloatType initialValue) noexcept
void reset(double sampleRate, double rampLengthInSeconds) noexcept
void reset(int numSteps) noexcept
void setTargetValue(FloatType newValue) noexcept