OpenShot Audio Library | OpenShotAudio  0.6.0
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 
23 namespace juce
24 {
25 
26 //==============================================================================
35 template <typename SmoothedValueType>
37 {
38 private:
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 
54 public:
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 
154 private:
155  //==============================================================================
156  FloatType getNextSmoothedValue() noexcept
157  {
158  return static_cast <SmoothedValueType*> (this)->getNextValue();
159  }
160 
161 protected:
162  //==============================================================================
163  FloatType currentValue = 0;
164  FloatType target = currentValue;
165  int countdown = 0;
166 };
167 
168 //==============================================================================
178 namespace ValueSmoothingTypes
179 {
185  struct Linear {};
186 
192  struct Multiplicative {};
193 }
194 
195 //==============================================================================
225 template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
226 class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
227 {
228 public:
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 
355 private:
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 
403 template <typename FloatType>
404 using LinearSmoothedValue = SmoothedValue <FloatType, ValueSmoothingTypes::Linear>;
405 
406 
407 //==============================================================================
408 //==============================================================================
409 #if JUCE_UNIT_TESTS
410 
411 template <class SmoothedValueType>
412 class CommonSmoothedValueTests : public UnitTest
413 {
414 public:
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