OpenShot Library | OpenShotAudio 0.2.2
juce_SmoothedValue.h
1
2/** @weakgroup juce_audio_basics-utilities
3 * @{
4 */
5/*
6 ==============================================================================
7
8 This file is part of the JUCE library.
9 Copyright (c) 2017 - ROLI Ltd.
10
11 JUCE is an open source library subject to commercial or open-source
12 licensing.
13
14 The code included in this file is provided under the terms of the ISC license
15 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
16 To use, copy, modify, and/or distribute this software for any purpose with or
17 without fee is hereby granted provided that the above copyright notice and
18 this permission notice appear in all copies.
19
20 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
21 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
22 DISCLAIMED.
23
24 ==============================================================================
25*/
26
27namespace juce
28{
29
30//==============================================================================
31/**
32 A base class for the smoothed value classes.
33
34 This class is used to provide common functionality to the SmoothedValue and
35 dsp::LogRampedValue classes.
36
37 @tags{Audio}
38*/
39template <typename SmoothedValueType>
41{
42private:
43 //==============================================================================
44 template <typename T> struct FloatTypeHelper;
45
46 template <template <typename> class SmoothedValueClass, typename FloatType>
47 struct FloatTypeHelper <SmoothedValueClass <FloatType>>
48 {
49 using Type = FloatType;
50 };
51
52 template <template <typename, typename> class SmoothedValueClass, typename FloatType, typename SmoothingType>
53 struct FloatTypeHelper <SmoothedValueClass <FloatType, SmoothingType>>
54 {
55 using Type = FloatType;
56 };
57
58public:
59 using FloatType = typename FloatTypeHelper<SmoothedValueType>::Type;
60
61 //==============================================================================
62 /** Constructor. */
63 SmoothedValueBase() = default;
64
65 virtual ~SmoothedValueBase() {}
66
67 //==============================================================================
68 /** Returns true if the current value is currently being interpolated. */
69 bool isSmoothing() const noexcept { return countdown > 0; }
70
71 /** Returns the current value of the ramp. */
72 FloatType getCurrentValue() const noexcept { return currentValue; }
73
74 //==============================================================================
75 /** Returns the target value towards which the smoothed value is currently moving. */
76 FloatType getTargetValue() const noexcept { return target; }
77
78 /** Sets the current value and the target value.
79 @param newValue the new value to take
80 */
81 void setCurrentAndTargetValue (FloatType newValue)
82 {
83 target = currentValue = newValue;
84 countdown = 0;
85 }
86
87 //==============================================================================
88 /** Applies a smoothed gain to a stream of samples
89 S[i] *= gain
90 @param samples Pointer to a raw array of samples
91 @param numSamples Length of array of samples
92 */
93 void applyGain (FloatType* samples, int numSamples) noexcept
94 {
95 jassert (numSamples >= 0);
96
97 if (isSmoothing())
98 {
99 for (int i = 0; i < numSamples; ++i)
100 samples[i] *= getNextSmoothedValue();
101 }
102 else
103 {
104 FloatVectorOperations::multiply (samples, target, numSamples);
105 }
106 }
107
108 /** Computes output as a smoothed gain applied to a stream of samples.
109 Sout[i] = Sin[i] * gain
110 @param samplesOut A pointer to a raw array of output samples
111 @param samplesIn A pointer to a raw array of input samples
112 @param numSamples The length of the array of samples
113 */
114 void applyGain (FloatType* samplesOut, const FloatType* samplesIn, int numSamples) noexcept
115 {
116 jassert (numSamples >= 0);
117
118 if (isSmoothing())
119 {
120 for (int i = 0; i < numSamples; ++i)
121 samplesOut[i] = samplesIn[i] * getNextSmoothedValue();
122 }
123 else
124 {
125 FloatVectorOperations::multiply (samplesOut, samplesIn, target, numSamples);
126 }
127 }
128
129 /** Applies a smoothed gain to a buffer */
130 void applyGain (AudioBuffer<FloatType>& buffer, int numSamples) noexcept
131 {
132 jassert (numSamples >= 0);
133
134 if (isSmoothing())
135 {
136 if (buffer.getNumChannels() == 1)
137 {
138 auto* samples = buffer.getWritePointer (0);
139
140 for (int i = 0; i < numSamples; ++i)
141 samples[i] *= getNextSmoothedValue();
142 }
143 else
144 {
145 for (auto i = 0; i < numSamples; ++i)
146 {
147 auto gain = getNextSmoothedValue();
148
149 for (int channel = 0; channel < buffer.getNumChannels(); channel++)
150 buffer.setSample (channel, i, buffer.getSample (channel, i) * gain);
151 }
152 }
153 }
154 else
155 {
156 buffer.applyGain (0, numSamples, target);
157 }
158 }
159
160private:
161 //==============================================================================
162 FloatType getNextSmoothedValue() noexcept
163 {
164 return static_cast <SmoothedValueType*> (this)->getNextValue();
165 }
166
167protected:
168 //==============================================================================
169 FloatType currentValue = 0;
170 FloatType target = currentValue;
171 int countdown = 0;
172};
173
174//==============================================================================
175/**
176 A namespace containing a set of types used for specifying the smoothing
177 behaviour of the SmoothedValue class.
178
179 For example:
180 @code
181 SmoothedValue<float, ValueSmoothingTypes::Multiplicative> frequency (1.0f);
182 @endcode
183*/
184namespace ValueSmoothingTypes
185{
186 /**
187 Used to indicate a linear smoothing between values.
188
189 @tags{Audio}
190 */
191 struct Linear {};
192
193 /**
194 Used to indicate a smoothing between multiplicative values.
195
196 @tags{Audio}
197 */
198 struct Multiplicative {};
199}
200
201//==============================================================================
202/**
203 A utility class for values that need smoothing to avoid audio glitches.
204
205 A ValueSmoothingTypes::Linear template parameter selects linear smoothing,
206 which increments the SmoothedValue linearly towards its target value.
207
208 @code
209 SmoothedValue<float, ValueSmoothingTypes::Linear> yourSmoothedValue;
210 @endcode
211
212 A ValueSmoothingTypes::Multiplicative template parameter selects
213 multiplicative smoothing increments towards the target value.
214
215 @code
216 SmoothedValue<float, ValueSmoothingTypes::Multiplicative> yourSmoothedValue;
217 @endcode
218
219 Multiplicative smoothing is useful when you are dealing with
220 exponential/logarithmic values like volume in dB or frequency in Hz. For
221 example a 12 step ramp from 440.0 Hz (A4) to 880.0 Hz (A5) will increase the
222 frequency with an equal temperament tuning across the octave. A 10 step
223 smoothing from 1.0 (0 dB) to 3.16228 (10 dB) will increase the value in
224 increments of 1 dB.
225
226 Note that when you are using multiplicative smoothing you cannot ever reach a
227 target value of zero!
228
229 @tags{Audio}
230*/
231template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
232class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
233{
234public:
235 //==============================================================================
236 /** Constructor. */
237 SmoothedValue() noexcept
238 : SmoothedValue ((FloatType) (std::is_same<SmoothingType, ValueSmoothingTypes::Linear>::value ? 0 : 1))
239 {
240 }
241
242 /** Constructor. */
243 SmoothedValue (FloatType initialValue) noexcept
244 {
245 // Multiplicative smoothed values cannot ever reach 0!
246 jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && initialValue == 0));
247
248 // Visual Studio can't handle base class initialisation with CRTP
249 this->currentValue = initialValue;
250 this->target = this->currentValue;
251 }
252
253 //==============================================================================
254 /** Reset to a new sample rate and ramp length.
255 @param sampleRate The sample rate
256 @param rampLengthInSeconds The duration of the ramp in seconds
257 */
258 void reset (double sampleRate, double rampLengthInSeconds) noexcept
259 {
260 jassert (sampleRate > 0 && rampLengthInSeconds >= 0);
261 reset ((int) std::floor (rampLengthInSeconds * sampleRate));
262 }
263
264 /** Set a new ramp length directly in samples.
265 @param numSteps The number of samples over which the ramp should be active
266 */
267 void reset (int numSteps) noexcept
268 {
269 stepsToTarget = numSteps;
270 this->setCurrentAndTargetValue (this->target);
271 }
272
273 //==============================================================================
274 /** Set the next value to ramp towards.
275 @param newValue The new target value
276 */
277 void setTargetValue (FloatType newValue) noexcept
278 {
279 if (newValue == this->target)
280 return;
281
282 if (stepsToTarget <= 0)
283 {
284 this->setCurrentAndTargetValue (newValue);
285 return;
286 }
287
288 // Multiplicative smoothed values cannot ever reach 0!
289 jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && newValue == 0));
290
291 this->target = newValue;
292 this->countdown = stepsToTarget;
293
294 setStepSize();
295 }
296
297 //==============================================================================
298 /** Compute the next value.
299 @returns Smoothed value
300 */
301 FloatType getNextValue() noexcept
302 {
303 if (! this->isSmoothing())
304 return this->target;
305
306 --(this->countdown);
307
308 if (this->isSmoothing())
309 setNextValue();
310 else
311 this->currentValue = this->target;
312
313 return this->currentValue;
314 }
315
316 //==============================================================================
317 /** Skip the next numSamples samples.
318 This is identical to calling getNextValue numSamples times. It returns
319 the new current value.
320 @see getNextValue
321 */
322 FloatType skip (int numSamples) noexcept
323 {
324 if (numSamples >= this->countdown)
325 {
326 this->setCurrentAndTargetValue (this->target);
327 return this->target;
328 }
329
330 skipCurrentValue (numSamples);
331
332 this->countdown -= numSamples;
333 return this->currentValue;
334 }
335
336 //==============================================================================
337 /** THIS FUNCTION IS DEPRECATED.
338
339 Use `setTargetValue (float)` and `setCurrentAndTargetValue()` instead:
340
341 lsv.setValue (x, false); -> lsv.setTargetValue (x);
342 lsv.setValue (x, true); -> lsv.setCurrentAndTargetValue (x);
343
344 @param newValue The new target value
345 @param force If true, the value will be set immediately, bypassing the ramp
346 */
347 JUCE_DEPRECATED_WITH_BODY (void setValue (FloatType newValue, bool force = false) noexcept,
348 {
349 if (force)
350 {
351 this->setCurrentAndTargetValue (newValue);
352 return;
353 }
354
355 setTargetValue (newValue);
356 })
357
358private:
359 //==============================================================================
360 template <typename T>
361 using LinearVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Linear>::value, void>::type;
362
363 template <typename T>
364 using MultiplicativeVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Multiplicative>::value, void>::type;
365
366 //==============================================================================
367 template <typename T = SmoothingType>
368 LinearVoid<T> setStepSize() noexcept
369 {
370 step = (this->target - this->currentValue) / (FloatType) this->countdown;
371 }
372
373 template <typename T = SmoothingType>
374 MultiplicativeVoid<T> setStepSize()
375 {
376 step = std::exp ((std::log (std::abs (this->target)) - std::log (std::abs (this->currentValue))) / this->countdown);
377 }
378
379 //==============================================================================
380 template <typename T = SmoothingType>
381 LinearVoid<T> setNextValue() noexcept
382 {
383 this->currentValue += step;
384 }
385
386 template <typename T = SmoothingType>
387 MultiplicativeVoid<T> setNextValue() noexcept
388 {
389 this->currentValue *= step;
390 }
391
392 //==============================================================================
393 template <typename T = SmoothingType>
394 LinearVoid<T> skipCurrentValue (int numSamples) noexcept
395 {
396 this->currentValue += step * (FloatType) numSamples;
397 }
398
399 template <typename T = SmoothingType>
400 MultiplicativeVoid<T> skipCurrentValue (int numSamples)
401 {
402 this->currentValue *= (FloatType) std::pow (step, numSamples);
403 }
404
405 //==============================================================================
406 FloatType step = FloatType();
407 int stepsToTarget = 0;
408};
409
410template <typename FloatType>
411using LinearSmoothedValue = SmoothedValue <FloatType, ValueSmoothingTypes::Linear>;
412
413
414//==============================================================================
415//==============================================================================
416#if JUCE_UNIT_TESTS
417
418template <class SmoothedValueType>
419class CommonSmoothedValueTests : public UnitTest
420{
421public:
422 CommonSmoothedValueTests()
423 : UnitTest ("CommonSmoothedValueTests", UnitTestCategories::smoothedValues)
424 {}
425
426 void runTest() override
427 {
428 beginTest ("Initial state");
429 {
430 SmoothedValueType sv;
431
432 auto value = sv.getCurrentValue();
433 expectEquals (sv.getTargetValue(), value);
434
435 sv.getNextValue();
436 expectEquals (sv.getCurrentValue(), value);
437 expect (! sv.isSmoothing());
438 }
439
440 beginTest ("Resetting");
441 {
442 auto initialValue = 15.0f;
443
444 SmoothedValueType sv (initialValue);
445 sv.reset (3);
446 expectEquals (sv.getCurrentValue(), initialValue);
447
448 auto targetValue = initialValue + 1.0f;
449 sv.setTargetValue (targetValue);
450 expectEquals (sv.getTargetValue(), targetValue);
451 expectEquals (sv.getCurrentValue(), initialValue);
452 expect (sv.isSmoothing());
453
454 auto currentValue = sv.getNextValue();
455 expect (currentValue > initialValue);
456 expectEquals (sv.getCurrentValue(), currentValue);
457 expectEquals (sv.getTargetValue(), targetValue);
458 expect (sv.isSmoothing());
459
460 sv.reset (5);
461
462 expectEquals (sv.getCurrentValue(), targetValue);
463 expectEquals (sv.getTargetValue(), targetValue);
464 expect (! sv.isSmoothing());
465
466 sv.getNextValue();
467 expectEquals (sv.getCurrentValue(), targetValue);
468
469 sv.setTargetValue (1.5f);
470 sv.getNextValue();
471
472 float newStart = 0.2f;
473 sv.setCurrentAndTargetValue (newStart);
474 expectEquals (sv.getNextValue(), newStart);
475 expectEquals (sv.getTargetValue(), newStart);
476 expectEquals (sv.getCurrentValue(), newStart);
477 expect (! sv.isSmoothing());
478 }
479
480 beginTest ("Sample rate");
481 {
482 SmoothedValueType svSamples { 3.0f };
483 auto svTime = svSamples;
484
485 auto numSamples = 12;
486
487 svSamples.reset (numSamples);
488 svTime.reset (numSamples * 2, 1.0);
489
490 for (int i = 0; i < numSamples; ++i)
491 {
492 svTime.skip (1);
493 expectWithinAbsoluteError (svSamples.getNextValue(),
494 svTime.getNextValue(),
495 1.0e-7f);
496 }
497 }
498
499 beginTest ("Block processing");
500 {
501 SmoothedValueType sv (1.0f);
502
503 sv.reset (12);
504 sv.setTargetValue (2.0f);
505
506 const auto numSamples = 15;
507
508 AudioBuffer<float> referenceData (1, numSamples);
509
510 for (int i = 0; i < numSamples; ++i)
511 referenceData.setSample (0, i, sv.getNextValue());
512
513 expect (referenceData.getSample (0, 0) > 0);
514 expect (referenceData.getSample (0, 10) < sv.getTargetValue());
515 expectWithinAbsoluteError (referenceData.getSample (0, 11),
516 sv.getTargetValue(),
517 1.0e-7f);
518
519 auto getUnitData = [] (int numSamplesToGenerate)
520 {
521 AudioBuffer<float> result (1, numSamplesToGenerate);
522
523 for (int i = 0; i < numSamplesToGenerate; ++i)
524 result.setSample (0, i, 1.0f);
525
526 return result;
527 };
528
529 auto compareData = [this](const AudioBuffer<float>& test,
530 const AudioBuffer<float>& reference)
531 {
532 for (int i = 0; i < test.getNumSamples(); ++i)
533 expectWithinAbsoluteError (test.getSample (0, i),
534 reference.getSample (0, i),
535 1.0e-7f);
536 };
537
538 auto testData = getUnitData (numSamples);
539 sv.setCurrentAndTargetValue (1.0f);
540 sv.setTargetValue (2.0f);
541 sv.applyGain (testData.getWritePointer (0), numSamples);
542 compareData (testData, referenceData);
543
544 testData = getUnitData (numSamples);
545 AudioBuffer<float> destData (1, numSamples);
546 sv.setCurrentAndTargetValue (1.0f);
547 sv.setTargetValue (2.0f);
548 sv.applyGain (destData.getWritePointer (0),
549 testData.getReadPointer (0),
550 numSamples);
551 compareData (destData, referenceData);
552 compareData (testData, getUnitData (numSamples));
553
554 testData = getUnitData (numSamples);
555 sv.setCurrentAndTargetValue (1.0f);
556 sv.setTargetValue (2.0f);
557 sv.applyGain (testData, numSamples);
558 compareData (testData, referenceData);
559 }
560
561 beginTest ("Skip");
562 {
563 SmoothedValueType sv;
564
565 sv.reset (12);
566 sv.setCurrentAndTargetValue (1.0f);
567 sv.setTargetValue (2.0f);
568
569 Array<float> reference;
570
571 for (int i = 0; i < 15; ++i)
572 reference.add (sv.getNextValue());
573
574 sv.setCurrentAndTargetValue (1.0f);
575 sv.setTargetValue (2.0f);
576
577 expectWithinAbsoluteError (sv.skip (1), reference[0], 1.0e-6f);
578 expectWithinAbsoluteError (sv.skip (1), reference[1], 1.0e-6f);
579 expectWithinAbsoluteError (sv.skip (2), reference[3], 1.0e-6f);
580 sv.skip (3);
581 expectWithinAbsoluteError (sv.getCurrentValue(), reference[6], 1.0e-6f);
582 expectEquals (sv.skip (300), sv.getTargetValue());
583 expectEquals (sv.getCurrentValue(), sv.getTargetValue());
584 }
585
586 beginTest ("Negative");
587 {
588 SmoothedValueType sv;
589
590 auto numValues = 12;
591 sv.reset (numValues);
592
593 std::vector<std::pair<float, float>> ranges = { { -1.0f, -2.0f },
594 { -100.0f, -3.0f } };
595
596 for (auto range : ranges)
597 {
598 auto start = range.first, end = range.second;
599
600 sv.setCurrentAndTargetValue (start);
601 sv.setTargetValue (end);
602
603 auto val = sv.skip (numValues / 2);
604
605 if (end > start)
606 expect (val > start && val < end);
607 else
608 expect (val < start && val > end);
609
610 auto nextVal = sv.getNextValue();
611 expect (end > start ? (nextVal > val) : (nextVal < val));
612
613 auto endVal = sv.skip (500);
614 expectEquals (endVal, end);
615 expectEquals (sv.getNextValue(), end);
616 expectEquals (sv.getCurrentValue(), end);
617
618 sv.setCurrentAndTargetValue (start);
619 sv.setTargetValue (end);
620
621 SmoothedValueType positiveSv { -start };
622 positiveSv.reset (numValues);
623 positiveSv.setTargetValue (-end);
624
625 for (int i = 0; i < numValues + 2; ++i)
626 expectEquals (sv.getNextValue(), -positiveSv.getNextValue());
627 }
628 }
629 }
630};
631
632#endif
633
634} // namespace juce
635
636/** @}*/
A multi-channel buffer containing floating point audio samples.
static void JUCE_CALLTYPE multiply(float *dest, const float *src, int numValues) noexcept
Multiplies the destination values by the source values.
A base class for the smoothed value classes.
bool isSmoothing() const noexcept
Returns true if the current value is currently being interpolated.
SmoothedValueBase()=default
Constructor.
void applyGain(FloatType *samples, int numSamples) noexcept
Applies a smoothed gain to a stream of samples S[i] *= gain.
void applyGain(AudioBuffer< FloatType > &buffer, int numSamples) noexcept
Applies a smoothed gain to a buffer.
void setCurrentAndTargetValue(FloatType newValue)
Sets the current value and the target value.
FloatType getTargetValue() const noexcept
Returns the target value towards which the smoothed value is currently moving.
FloatType getCurrentValue() const noexcept
Returns the current value of the ramp.
void applyGain(FloatType *samplesOut, const FloatType *samplesIn, int numSamples) noexcept
Computes output as a smoothed gain applied to a stream of samples.
A utility class for values that need smoothing to avoid audio glitches.
FloatType skip(int numSamples) noexcept
Skip the next numSamples samples.
FloatType getNextValue() noexcept
Compute the next value.
(void setValue(FloatType newValue, bool force=false) noexcept, { if(force) { this->setCurrentAndTargetValue(newValue);return;} setTargetValue(newValue);}) private typename std::enable_if< std::is_same< T, ValueSmoothingTypes::Multiplicative >::value, void >::type MultiplicativeVoid
THIS FUNCTION IS DEPRECATED.
SmoothedValue(FloatType initialValue) noexcept
Constructor.
void reset(double sampleRate, double rampLengthInSeconds) noexcept
Reset to a new sample rate and ramp length.
SmoothedValue() noexcept
Constructor.
void reset(int numSteps) noexcept
Set a new ramp length directly in samples.
void setTargetValue(FloatType newValue) noexcept
Set the next value to ramp towards.
Used to indicate a linear smoothing between values.
Used to indicate a smoothing between multiplicative values.