OpenShot Library | libopenshot-audio 0.2.0
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 {
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 /** Used to indicate a linear smoothing between values. */
187 struct Linear {};
188
189 /** Used to indicate a smoothing between multiplicative values. */
190 struct Multiplicative {};
191}
192
193//==============================================================================
194/**
195 A utility class for values that need smoothing to avoid audio glitches.
196
197 A ValueSmoothingTypes::Linear template parameter selects linear smoothing,
198 which increments the SmoothedValue linearly towards its target value.
199
200 @code
201 SmoothedValue<float, ValueSmoothingTypes::Linear> yourSmoothedValue;
202 @endcode
203
204 A ValueSmoothingTypes::Multiplicative template parameter selects
205 multiplicative smoothing increments towards the target value.
206
207 @code
208 SmoothedValue<float, ValueSmoothingTypes::Multiplicative> yourSmoothedValue;
209 @endcode
210
211 Multiplicative smoothing is useful when you are dealing with
212 exponential/logarithmic values like volume in dB or frequency in Hz. For
213 example a 12 step ramp from 440.0 Hz (A4) to 880.0 Hz (A5) will increase the
214 frequency with an equal temperament tuning across the octave. A 10 step
215 smoothing from 1.0 (0 dB) to 3.16228 (10 dB) will increase the value in
216 increments of 1 dB.
217
218 Note that when you are using multiplicative smoothing you cannot ever reach a
219 target value of zero!
220
221 @tags{Audio}
222*/
223template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
224class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
225{
226public:
227 //==============================================================================
228 /** Constructor. */
230 : SmoothedValue ((FloatType) (std::is_same<SmoothingType, ValueSmoothingTypes::Linear>::value ? 0 : 1))
231 {
232 }
233
234 /** Constructor. */
235 SmoothedValue (FloatType initialValue) noexcept
236 {
237 // Multiplicative smoothed values cannot ever reach 0!
238 jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && initialValue == 0));
239
240 // Visual Studio can't handle base class initialisation with CRTP
241 this->currentValue = initialValue;
242 this->target = this->currentValue;
243 }
244
245 //==============================================================================
246 /** Reset to a new sample rate and ramp length.
247 @param sampleRate The sample rate
248 @param rampLengthInSeconds The duration of the ramp in seconds
249 */
250 void reset (double sampleRate, double rampLengthInSeconds) noexcept
251 {
252 jassert (sampleRate > 0 && rampLengthInSeconds >= 0);
253 reset ((int) std::floor (rampLengthInSeconds * sampleRate));
254 }
255
256 /** Set a new ramp length directly in samples.
257 @param numSteps The number of samples over which the ramp should be active
258 */
259 void reset (int numSteps) noexcept
260 {
261 stepsToTarget = numSteps;
262 this->setCurrentAndTargetValue (this->target);
263 }
264
265 //==============================================================================
266 /** Set the next value to ramp towards.
267 @param newValue The new target value
268 */
269 void setTargetValue (FloatType newValue) noexcept
270 {
271 if (newValue == this->target)
272 return;
273
274 if (stepsToTarget <= 0)
275 {
276 this->setCurrentAndTargetValue (newValue);
277 return;
278 }
279
280 // Multiplicative smoothed values cannot ever reach 0!
281 jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && newValue == 0));
282
283 this->target = newValue;
284 this->countdown = stepsToTarget;
285
286 setStepSize();
287 }
288
289 //==============================================================================
290 /** Compute the next value.
291 @returns Smoothed value
292 */
294 {
295 if (! this->isSmoothing())
296 return this->target;
297
298 --(this->countdown);
299
300 if (this->isSmoothing())
301 setNextValue();
302 else
303 this->currentValue = this->target;
304
305 return this->currentValue;
306 }
307
308 //==============================================================================
309 /** Skip the next numSamples samples.
310 This is identical to calling getNextValue numSamples times. It returns
311 the new current value.
312 @see getNextValue
313 */
314 FloatType skip (int numSamples) noexcept
315 {
316 if (numSamples >= this->countdown)
317 {
318 this->setCurrentAndTargetValue (this->target);
319 return this->target;
320 }
321
322 skipCurrentValue (numSamples);
323
324 this->countdown -= numSamples;
325 return this->currentValue;
326 }
327
328 //==============================================================================
329 /** THIS FUNCTION IS DEPRECATED.
330
331 Use `setTargetValue (float)` and `setCurrentAndTargetValue()` instead:
332
333 lsv.setValue (x, false); -> lsv.setTargetValue (x);
334 lsv.setValue (x, true); -> lsv.setCurrentAndTargetValue (x);
335
336 @param newValue The new target value
337 @param force If true, the value will be set immediately, bypassing the ramp
338 */
339 JUCE_DEPRECATED_WITH_BODY (void setValue (FloatType newValue, bool force = false) noexcept,
340 {
341 if (force)
342 {
343 this->setCurrentAndTargetValue (newValue);
344 return;
345 }
346
347 setTargetValue (newValue);
348 })
349
350private:
351 //==============================================================================
352 template <typename T>
353 using LinearVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Linear>::value, void>::type;
354
355 template <typename T>
356 using MultiplicativeVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Multiplicative>::value, void>::type;
357
358 //==============================================================================
359 template <typename T = SmoothingType>
360 LinearVoid<T> setStepSize() noexcept
361 {
362 step = (this->target - this->currentValue) / (FloatType) this->countdown;
363 }
364
365 template <typename T = SmoothingType>
366 MultiplicativeVoid<T> setStepSize()
367 {
368 step = std::exp ((std::log (std::abs (this->target)) - std::log (std::abs (this->currentValue))) / this->countdown);
369 }
370
371 //==============================================================================
372 template <typename T = SmoothingType>
373 LinearVoid<T> setNextValue() noexcept
374 {
375 this->currentValue += step;
376 }
377
378 template <typename T = SmoothingType>
379 MultiplicativeVoid<T> setNextValue() noexcept
380 {
381 this->currentValue *= step;
382 }
383
384 //==============================================================================
385 template <typename T = SmoothingType>
386 LinearVoid<T> skipCurrentValue (int numSamples) noexcept
387 {
388 this->currentValue += step * (FloatType) numSamples;
389 }
390
391 template <typename T = SmoothingType>
392 MultiplicativeVoid<T> skipCurrentValue (int numSamples)
393 {
394 this->currentValue *= (FloatType) std::pow (step, numSamples);
395 }
396
397 //==============================================================================
398 FloatType step = FloatType();
399 int stepsToTarget = 0;
400};
401
402template <typename FloatType>
403using LinearSmoothedValue = SmoothedValue <FloatType, ValueSmoothingTypes::Linear>;
404
405
406//==============================================================================
407//==============================================================================
408#if JUCE_UNIT_TESTS
409
410template <class SmoothedValueType>
411class CommonSmoothedValueTests : public UnitTest
412{
413public:
415 : UnitTest ("CommonSmoothedValueTests", "SmoothedValues")
416 {}
417
418 void runTest() override
419 {
420 beginTest ("Initial state");
421 {
423
424 auto value = sv.getCurrentValue();
425 expectEquals (sv.getTargetValue(), value);
426
427 sv.getNextValue();
428 expectEquals (sv.getCurrentValue(), value);
429 expect (! sv.isSmoothing());
430 }
431
432 beginTest ("Resetting");
433 {
434 auto initialValue = 15.0f;
435
437 sv.reset (3);
438 expectEquals (sv.getCurrentValue(), initialValue);
439
440 auto targetValue = initialValue + 1.0f;
441 sv.setTargetValue (targetValue);
442 expectEquals (sv.getTargetValue(), targetValue);
443 expectEquals (sv.getCurrentValue(), initialValue);
444 expect (sv.isSmoothing());
445
446 auto currentValue = sv.getNextValue();
447 expect (currentValue > initialValue);
448 expectEquals (sv.getCurrentValue(), currentValue);
449 expectEquals (sv.getTargetValue(), targetValue);
450 expect (sv.isSmoothing());
451
452 sv.reset (5);
453
454 expectEquals (sv.getCurrentValue(), targetValue);
455 expectEquals (sv.getTargetValue(), targetValue);
456 expect (! sv.isSmoothing());
457
458 sv.getNextValue();
459 expectEquals (sv.getCurrentValue(), targetValue);
460
461 sv.setTargetValue (1.5f);
462 sv.getNextValue();
463
464 float newStart = 0.2f;
465 sv.setCurrentAndTargetValue (newStart);
466 expectEquals (sv.getNextValue(), newStart);
467 expectEquals (sv.getTargetValue(), newStart);
468 expectEquals (sv.getCurrentValue(), newStart);
469 expect (! sv.isSmoothing());
470 }
471
472 beginTest ("Sample rate");
473 {
475 auto svTime = svSamples;
476
477 auto numSamples = 12;
478
479 svSamples.reset (numSamples);
480 svTime.reset (numSamples * 2, 1.0);
481
482 for (int i = 0; i < numSamples; ++i)
483 {
484 svTime.skip (1);
485 expectWithinAbsoluteError (svSamples.getNextValue(),
486 svTime.getNextValue(),
487 1.0e-7f);
488 }
489 }
490
491 beginTest ("Block processing");
492 {
493 SmoothedValueType sv (1.0f);
494
495 sv.reset (12);
496 sv.setTargetValue (2.0f);
497
498 const auto numSamples = 15;
499
500 AudioBuffer<float> referenceData (1, numSamples);
501
502 for (int i = 0; i < numSamples; ++i)
503 referenceData.setSample (0, i, sv.getNextValue());
504
505 expect (referenceData.getSample (0, 0) > 0);
506 expect (referenceData.getSample (0, 10) < sv.getTargetValue());
507 expectWithinAbsoluteError (referenceData.getSample (0, 11),
508 sv.getTargetValue(),
509 1.0e-7f);
510
511 auto getUnitData = [] (int numSamplesToGenerate)
512 {
513 AudioBuffer<float> result (1, numSamplesToGenerate);
514
515 for (int i = 0; i < numSamplesToGenerate; ++i)
516 result.setSample (0, i, 1.0f);
517
518 return result;
519 };
520
521 auto compareData = [this](const AudioBuffer<float>& test,
522 const AudioBuffer<float>& reference)
523 {
524 for (int i = 0; i < test.getNumSamples(); ++i)
525 expectWithinAbsoluteError (test.getSample (0, i),
526 reference.getSample (0, i),
527 1.0e-7f);
528 };
529
530 auto testData = getUnitData (numSamples);
531 sv.setCurrentAndTargetValue (1.0f);
532 sv.setTargetValue (2.0f);
533 sv.applyGain (testData.getWritePointer (0), numSamples);
535
536 testData = getUnitData (numSamples);
537 AudioBuffer<float> destData (1, numSamples);
538 sv.setCurrentAndTargetValue (1.0f);
539 sv.setTargetValue (2.0f);
540 sv.applyGain (destData.getWritePointer (0),
541 testData.getReadPointer (0),
542 numSamples);
544 compareData (testData, getUnitData (numSamples));
545
546 testData = getUnitData (numSamples);
547 sv.setCurrentAndTargetValue (1.0f);
548 sv.setTargetValue (2.0f);
549 sv.applyGain (testData, numSamples);
551 }
552
553 beginTest ("Skip");
554 {
556
557 sv.reset (12);
558 sv.setCurrentAndTargetValue (1.0f);
559 sv.setTargetValue (2.0f);
560
561 Array<float> reference;
562
563 for (int i = 0; i < 15; ++i)
564 reference.add (sv.getNextValue());
565
566 sv.setCurrentAndTargetValue (1.0f);
567 sv.setTargetValue (2.0f);
568
569 expectWithinAbsoluteError (sv.skip (1), reference[0], 1.0e-6f);
570 expectWithinAbsoluteError (sv.skip (1), reference[1], 1.0e-6f);
571 expectWithinAbsoluteError (sv.skip (2), reference[3], 1.0e-6f);
572 sv.skip (3);
573 expectWithinAbsoluteError (sv.getCurrentValue(), reference[6], 1.0e-6f);
574 expectEquals (sv.skip (300), sv.getTargetValue());
575 expectEquals (sv.getCurrentValue(), sv.getTargetValue());
576 }
577
578 beginTest ("Negative");
579 {
581
582 auto numValues = 12;
583 sv.reset (numValues);
584
585 std::vector<std::pair<float, float>> ranges = { { -1.0f, -2.0f },
586 { -100.0f, -3.0f } };
587
588 for (auto range : ranges)
589 {
590 auto start = range.first, end = range.second;
591
592 sv.setCurrentAndTargetValue (start);
593 sv.setTargetValue (end);
594
595 auto val = sv.skip (numValues / 2);
596
597 if (end > start)
598 expect (val > start && val < end);
599 else
600 expect (val < start && val > end);
601
602 auto nextVal = sv.getNextValue();
603 expect (end > start ? (nextVal > val) : (nextVal < val));
604
605 auto endVal = sv.skip (500);
606 expectEquals (endVal, end);
607 expectEquals (sv.getNextValue(), end);
608 expectEquals (sv.getCurrentValue(), end);
609
610 sv.setCurrentAndTargetValue (start);
611 sv.setTargetValue (end);
612
613 SmoothedValueType positiveSv { -start };
614 positiveSv.reset (numValues);
615 positiveSv.setTargetValue (-end);
616
617 for (int i = 0; i < numValues + 2; ++i)
618 expectEquals (sv.getNextValue(), -positiveSv.getNextValue());
619 }
620 }
621 }
622};
623
624#endif
625
626} // namespace juce
627
628/** @}*/
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
Array()=default
Creates an empty array.
ElementType * end() const noexcept
Returns a pointer to the element which follows the last element in the array.
Definition juce_Array.h:317
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.