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 
27 namespace 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 */
39 template <typename SmoothedValueType>
41 {
42 private:
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 
58 public:
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 
160 private:
161  //==============================================================================
162  FloatType getNextSmoothedValue() noexcept
163  {
164  return static_cast <SmoothedValueType*> (this)->getNextValue();
165  }
166 
167 protected:
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 */
184 namespace 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 */
223 template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
224 class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
225 {
226 public:
227  //==============================================================================
228  /** Constructor. */
229  SmoothedValue() noexcept
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  */
293  FloatType getNextValue() noexcept
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 
350 private:
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 
402 template <typename FloatType>
404 
405 
406 //==============================================================================
407 //==============================================================================
408 #if JUCE_UNIT_TESTS
409 
410 template <class SmoothedValueType>
411 class CommonSmoothedValueTests : public UnitTest
412 {
413 public:
414  CommonSmoothedValueTests()
415  : UnitTest ("CommonSmoothedValueTests", "SmoothedValues")
416  {}
417 
418  void runTest() override
419  {
420  beginTest ("Initial state");
421  {
422  SmoothedValueType sv;
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 
436  SmoothedValueType sv (initialValue);
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  {
474  SmoothedValueType svSamples { 3.0f };
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);
534  compareData (testData, referenceData);
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);
543  compareData (destData, referenceData);
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);
550  compareData (testData, referenceData);
551  }
552 
553  beginTest ("Skip");
554  {
555  SmoothedValueType sv;
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  {
580  SmoothedValueType sv;
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 /** @}*/
FloatType getNextValue() noexcept
Compute the next value.
FloatType getTargetValue() const noexcept
Returns the target value towards which the smoothed value is currently moving.
Used to indicate a linear smoothing between values.
A utility class for values that need smoothing to avoid audio glitches.
FloatType getCurrentValue() const noexcept
Returns the current value of the ramp.
void reset(int numSteps) noexcept
Set a new ramp length directly in samples.
FloatType skip(int numSamples) noexcept
Skip the next numSamples samples.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition: juce_Array.h:375
void reset(double sampleRate, double rampLengthInSeconds) noexcept
Reset to a new sample rate and ramp length.
STL namespace.
Type getSample(int channel, int sampleIndex) const noexcept
Returns a sample from the buffer.
static void JUCE_CALLTYPE multiply(float *dest, const float *src, int numValues) noexcept
Multiplies the destination values by the source values.
SmoothedValueBase()=default
Constructor.
bool isSmoothing() const noexcept
Returns true if the current value is currently being interpolated.
This is a base class for classes that perform a unit test.
Definition: juce_UnitTest.h:73
A multi-channel buffer containing floating point audio samples.
Used to indicate a smoothing between multiplicative values.
A base class for the smoothed value classes.
SmoothedValue() noexcept
Constructor.
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:59
SmoothedValue(FloatType initialValue) noexcept
Constructor.
void applyGain(AudioBuffer< FloatType > &buffer, int numSamples) noexcept
Applies a smoothed gain to a buffer.
void setTargetValue(FloatType newValue) noexcept
Set the next value to ramp towards.
void setCurrentAndTargetValue(FloatType newValue)
Sets the current value and the target value.
Type * getWritePointer(int channelNumber) noexcept
Returns a writeable pointer to one of the buffer&#39;s channels.
void setSample(int destChannel, int destSample, Type newValue) noexcept
Sets a sample in the buffer.
int getNumSamples() const noexcept
Returns the number of samples allocated in each of the buffer&#39;s channels.
void applyGain(FloatType *samplesOut, const FloatType *samplesIn, int numSamples) noexcept
Computes output as a smoothed gain applied to a stream of samples.
void applyGain(FloatType *samples, int numSamples) noexcept
Applies a smoothed gain to a stream of samples S[i] *= gain.
(void setValue(float 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.