From 4751e9d41a7c733450ff7036f447732187cafa43 Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Mon, 18 Feb 2019 23:43:28 +0000 Subject: [PATCH] Added a new templated SmoothedValue class --- examples/Audio/MPEDemo.h | 2 +- examples/BLOCKS/BlocksSynthDemo.h | 2 +- examples/Plugins/DSPModulePluginDemo.h | 4 +- examples/Plugins/SamplerPluginDemo.h | 8 +- .../juce_audio_basics/juce_audio_basics.cpp | 2 +- modules/juce_audio_basics/juce_audio_basics.h | 2 +- .../juce_audio_basics/utilities/juce_Reverb.h | 2 +- .../utilities/juce_SmoothedValue.cpp | 296 +++++++++++++++++ ..._SmoothedValues.h => juce_SmoothedValue.h} | 313 ++++++++---------- .../utilities/juce_SmoothedValues.cpp | 285 ---------------- modules/juce_dsp/containers/juce_AudioBlock.h | 9 +- .../juce_dsp/frequency/juce_Convolution.cpp | 2 +- modules/juce_dsp/frequency/juce_Convolution.h | 2 +- modules/juce_dsp/juce_dsp.h | 1 + .../juce_dsp/maths/juce_LogRampedValue.cpp | 101 ++++++ modules/juce_dsp/maths/juce_LogRampedValue.h | 190 +++++++++++ modules/juce_dsp/processors/juce_Bias.h | 2 +- modules/juce_dsp/processors/juce_Gain.h | 2 +- .../juce_dsp/processors/juce_LadderFilter.h | 2 +- modules/juce_dsp/processors/juce_Oscillator.h | 2 +- 20 files changed, 743 insertions(+), 486 deletions(-) create mode 100644 modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp rename modules/juce_audio_basics/utilities/{juce_SmoothedValues.h => juce_SmoothedValue.h} (58%) delete mode 100644 modules/juce_audio_basics/utilities/juce_SmoothedValues.cpp create mode 100644 modules/juce_dsp/maths/juce_LogRampedValue.cpp create mode 100644 modules/juce_dsp/maths/juce_LogRampedValue.h diff --git a/examples/Audio/MPEDemo.h b/examples/Audio/MPEDemo.h index 30251011a9..305d829a06 100644 --- a/examples/Audio/MPEDemo.h +++ b/examples/Audio/MPEDemo.h @@ -851,7 +851,7 @@ private: } //============================================================================== - LinearSmoothedValue level, timbre, frequency; + SmoothedValue level, timbre, frequency; double phase = 0.0; double phaseDelta = 0.0; diff --git a/examples/BLOCKS/BlocksSynthDemo.h b/examples/BLOCKS/BlocksSynthDemo.h index b3abc67423..a372918e6a 100644 --- a/examples/BLOCKS/BlocksSynthDemo.h +++ b/examples/BLOCKS/BlocksSynthDemo.h @@ -128,7 +128,7 @@ public: virtual double renderWaveShape (const double currentPhase) = 0; private: - LinearSmoothedValue amplitude, phaseIncrement; + SmoothedValue amplitude, phaseIncrement; double frequency = 0.0; double phasePos = 0.0; diff --git a/examples/Plugins/DSPModulePluginDemo.h b/examples/Plugins/DSPModulePluginDemo.h index 5282b77907..c115f49165 100644 --- a/examples/Plugins/DSPModulePluginDemo.h +++ b/examples/Plugins/DSPModulePluginDemo.h @@ -498,7 +498,7 @@ private: { ScopedNoDenormals noDenormals; - // Input volume applied with a LinearSmoothedValue + // Input volume applied with a SmoothedValue inputVolume.process (context); // Pre-highpass filtering, very useful for distortion audio effects @@ -543,7 +543,7 @@ private: convolution.process (context); context.isBypassed = wasBypassed; - // Output volume applied with a LinearSmoothedValue + // Output volume applied with a SmoothedValue outputVolume.process (context); } diff --git a/examples/Plugins/SamplerPluginDemo.h b/examples/Plugins/SamplerPluginDemo.h index a4f07f9d78..9201bf59a6 100644 --- a/examples/Plugins/SamplerPluginDemo.h +++ b/examples/Plugins/SamplerPluginDemo.h @@ -446,10 +446,10 @@ private: } std::shared_ptr samplerSound; - LinearSmoothedValue level { 0 }; - LinearSmoothedValue frequency { 0 }; - LinearSmoothedValue loopBegin; - LinearSmoothedValue loopEnd; + SmoothedValue level { 0 }; + SmoothedValue frequency { 0 }; + SmoothedValue loopBegin; + SmoothedValue loopEnd; double currentSamplePos { 0 }; double tailOff { 0 }; Direction currentDirection { Direction::forward }; diff --git a/modules/juce_audio_basics/juce_audio_basics.cpp b/modules/juce_audio_basics/juce_audio_basics.cpp index 3f2a661055..c142c6d6c1 100644 --- a/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/modules/juce_audio_basics/juce_audio_basics.cpp @@ -60,7 +60,7 @@ #include "utilities/juce_IIRFilter.cpp" #include "utilities/juce_LagrangeInterpolator.cpp" #include "utilities/juce_CatmullRomInterpolator.cpp" -#include "utilities/juce_SmoothedValues.cpp" +#include "utilities/juce_SmoothedValue.cpp" #include "midi/juce_MidiBuffer.cpp" #include "midi/juce_MidiFile.cpp" #include "midi/juce_MidiKeyboardState.cpp" diff --git a/modules/juce_audio_basics/juce_audio_basics.h b/modules/juce_audio_basics/juce_audio_basics.h index 83fe62270d..c6e952ff19 100644 --- a/modules/juce_audio_basics/juce_audio_basics.h +++ b/modules/juce_audio_basics/juce_audio_basics.h @@ -89,7 +89,7 @@ #include "utilities/juce_IIRFilter.h" #include "utilities/juce_LagrangeInterpolator.h" #include "utilities/juce_CatmullRomInterpolator.h" -#include "utilities/juce_SmoothedValues.h" +#include "utilities/juce_SmoothedValue.h" #include "utilities/juce_Reverb.h" #include "utilities/juce_ADSR.h" #include "midi/juce_MidiMessage.h" diff --git a/modules/juce_audio_basics/utilities/juce_Reverb.h b/modules/juce_audio_basics/utilities/juce_Reverb.h index 2dd94c5c6f..0ed72b80f2 100644 --- a/modules/juce_audio_basics/utilities/juce_Reverb.h +++ b/modules/juce_audio_basics/utilities/juce_Reverb.h @@ -305,7 +305,7 @@ private: CombFilter comb [numChannels][numCombs]; AllPassFilter allPass [numChannels][numAllPasses]; - LinearSmoothedValue damping, feedback, dryGain, wetGain1, wetGain2; + SmoothedValue damping, feedback, dryGain, wetGain1, wetGain2; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) }; diff --git a/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp b/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp new file mode 100644 index 0000000000..595ccad7b6 --- /dev/null +++ b/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp @@ -0,0 +1,296 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2018 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if JUCE_UNIT_TESTS + +template +class CommonSmoothedValueTests : public UnitTest +{ +public: + CommonSmoothedValueTests() + : UnitTest ("CommonSmoothedValueTests", "SmoothedValues") + {} + + void runTest() override + { + beginTest ("Initial state"); + { + SmoothedValueType sv; + + auto value = sv.getCurrentValue(); + expectEquals (sv.getTargetValue(), value); + + sv.getNextValue(); + expectEquals (sv.getCurrentValue(), value); + expect (! sv.isSmoothing()); + } + + beginTest ("Resetting"); + { + auto initialValue = 15.0f; + + SmoothedValueType sv (initialValue); + sv.reset (3); + expectEquals (sv.getCurrentValue(), initialValue); + + auto targetValue = initialValue + 1.0f; + sv.setTargetValue (targetValue); + expectEquals (sv.getTargetValue(), targetValue); + expectEquals (sv.getCurrentValue(), initialValue); + expect (sv.isSmoothing()); + + auto currentValue = sv.getNextValue(); + expect (currentValue > initialValue); + expectEquals (sv.getCurrentValue(), currentValue); + expectEquals (sv.getTargetValue(), targetValue); + expect (sv.isSmoothing()); + + sv.reset (5); + + expectEquals (sv.getCurrentValue(), targetValue); + expectEquals (sv.getTargetValue(), targetValue); + expect (! sv.isSmoothing()); + + sv.getNextValue(); + expectEquals (sv.getCurrentValue(), targetValue); + + sv.setTargetValue (1.5f); + sv.getNextValue(); + + float newStart = 0.2f; + sv.setCurrentAndTargetValue (newStart); + expectEquals (sv.getNextValue(), newStart); + expectEquals (sv.getTargetValue(), newStart); + expectEquals (sv.getCurrentValue(), newStart); + expect (! sv.isSmoothing()); + } + + beginTest ("Sample rate"); + { + SmoothedValueType svSamples { 3.0f }; + auto svTime = svSamples; + + auto numSamples = 12; + + svSamples.reset (numSamples); + svTime.reset (numSamples * 2, 1.0); + + for (int i = 0; i < numSamples; ++i) + { + svTime.skip (1); + expectWithinAbsoluteError (svSamples.getNextValue(), + svTime.getNextValue(), + 1.0e-7f); + } + } + + beginTest ("Block processing"); + { + SmoothedValueType sv (1.0f); + + sv.reset (12); + sv.setTargetValue (2.0f); + + const auto numSamples = 15; + + AudioBuffer referenceData (1, numSamples); + + for (int i = 0; i < numSamples; ++i) + referenceData.setSample (0, i, sv.getNextValue()); + + expect (referenceData.getSample (0, 0) > 0); + expect (referenceData.getSample (0, 10) < sv.getTargetValue()); + expectWithinAbsoluteError (referenceData.getSample (0, 11), + sv.getTargetValue(), + 1.0e-7f); + + auto getUnitData = [] (int numSamplesToGenerate) + { + AudioBuffer result (1, numSamplesToGenerate); + + for (int i = 0; i < numSamplesToGenerate; ++i) + result.setSample (0, i, 1.0f); + + return result; + }; + + auto compareData = [this](const AudioBuffer& test, + const AudioBuffer& reference) + { + for (int i = 0; i < test.getNumSamples(); ++i) + expectWithinAbsoluteError (test.getSample (0, i), + reference.getSample (0, i), + 1.0e-7f); + }; + + auto testData = getUnitData (numSamples); + sv.setCurrentAndTargetValue (1.0f); + sv.setTargetValue (2.0f); + sv.applyGain (testData.getWritePointer (0), numSamples); + compareData (testData, referenceData); + + testData = getUnitData (numSamples); + AudioBuffer destData (1, numSamples); + sv.setCurrentAndTargetValue (1.0f); + sv.setTargetValue (2.0f); + sv.applyGain (destData.getWritePointer (0), + testData.getReadPointer (0), + numSamples); + compareData (destData, referenceData); + compareData (testData, getUnitData (numSamples)); + + testData = getUnitData (numSamples); + sv.setCurrentAndTargetValue (1.0f); + sv.setTargetValue (2.0f); + sv.applyGain (testData, numSamples); + compareData (testData, referenceData); + } + + beginTest ("Skip"); + { + SmoothedValueType sv; + + sv.reset (12); + sv.setCurrentAndTargetValue (1.0f); + sv.setTargetValue (2.0f); + + Array reference; + + for (int i = 0; i < 15; ++i) + reference.add (sv.getNextValue()); + + sv.setCurrentAndTargetValue (1.0f); + sv.setTargetValue (2.0f); + + expectWithinAbsoluteError (sv.skip (1), reference[0], 1.0e-6f); + expectWithinAbsoluteError (sv.skip (1), reference[1], 1.0e-6f); + expectWithinAbsoluteError (sv.skip (2), reference[3], 1.0e-6f); + sv.skip (3); + expectWithinAbsoluteError (sv.getCurrentValue(), reference[6], 1.0e-6f); + expectEquals (sv.skip (300), sv.getTargetValue()); + expectEquals (sv.getCurrentValue(), sv.getTargetValue()); + } + + beginTest ("Negative"); + { + SmoothedValueType sv; + + auto start = -1.0f, end = -2.0f; + auto numValues = 12; + + sv.reset (numValues); + sv.setCurrentAndTargetValue (start); + sv.setTargetValue (end); + + auto val = sv.skip (3); + expect (val < start && val > end); + + auto nextVal = sv.getNextValue(); + expect (nextVal < val); + + auto endVal = sv.skip (500); + expectEquals (endVal, end); + expectEquals (sv.getNextValue(), end); + expectEquals (sv.getCurrentValue(), end); + + sv.setCurrentAndTargetValue (start); + sv.reset (numValues); + sv.setTargetValue (end); + + SmoothedValueType positiveSv { -start }; + positiveSv.reset (numValues); + positiveSv.setTargetValue (-end); + + for (int i = 0; i < numValues + 2; ++i) + expectEquals (sv.getNextValue(), -positiveSv.getNextValue()); + } + } +}; + +static CommonSmoothedValueTests > commonLinearSmoothedValueTests; +static CommonSmoothedValueTests > commonMultiplicativeSmoothedValueTests; + +class SmoothedValueTests : public UnitTest +{ +public: + SmoothedValueTests() + : UnitTest ("SmoothedValueTests", "SmoothedValues") + {} + + void runTest() override + { + beginTest ("Linear moving target"); + { + SmoothedValue sv; + + sv.reset (12); + float initialValue = 0.0f; + sv.setCurrentAndTargetValue (initialValue); + sv.setTargetValue (1.0f); + + auto delta = sv.getNextValue() - initialValue; + + sv.skip (6); + + auto newInitialValue = sv.getCurrentValue(); + sv.setTargetValue (newInitialValue + 2.0f); + auto doubleDelta = sv.getNextValue() - newInitialValue; + + expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f); + } + + beginTest ("Multiplicative curve"); + { + SmoothedValue sv; + + auto numSamples = 12; + AudioBuffer values (2, numSamples + 1); + + sv.reset (numSamples); + sv.setCurrentAndTargetValue (1.0); + sv.setTargetValue (2.0f); + + values.setSample (0, 0, sv.getCurrentValue()); + + for (int i = 1; i < values.getNumSamples(); ++i) + values.setSample (0, i, sv.getNextValue()); + + sv.setTargetValue (1.0f); + values.setSample (1, values.getNumSamples() - 1, sv.getCurrentValue()); + + for (int i = values.getNumSamples() - 2; i >= 0 ; --i) + values.setSample (1, i, sv.getNextValue()); + + for (int i = 0; i < values.getNumSamples(); ++i) + expectWithinAbsoluteError (values.getSample (0, i), values.getSample (1, i), 1.0e-9); + } + } +}; + +static SmoothedValueTests smoothedValueTests; + +#endif + +} // namespace juce diff --git a/modules/juce_audio_basics/utilities/juce_SmoothedValues.h b/modules/juce_audio_basics/utilities/juce_SmoothedValue.h similarity index 58% rename from modules/juce_audio_basics/utilities/juce_SmoothedValues.h rename to modules/juce_audio_basics/utilities/juce_SmoothedValue.h index b874e1aa9f..2875971345 100644 --- a/modules/juce_audio_basics/utilities/juce_SmoothedValues.h +++ b/modules/juce_audio_basics/utilities/juce_SmoothedValue.h @@ -27,16 +27,39 @@ namespace juce /** A base class for the smoothed value classes. + This class is used to provide common functionality to the SmoothedValue and + dsp::LogRampedValue classes. + @tags{Audio} */ -template class SmoothedValueClass, typename FloatType> +template class SmoothedValueBase { +private: + //============================================================================== + template struct FloatTypeHelper; + + template