AnalogTapeModel

Physical modelling signal processing for analog tape recording
Log | Files | Refs | Submodules | README | LICENSE

commit 4726aa34a9db4934b8b917e221a91146dfda058c
parent 76ac813f6f56cbc2acce5f143cf4afd6138c536c
Author: jatinchowdhury18 <[email protected]>
Date:   Sun, 16 Aug 2020 14:56:57 -0700

Fixing loss filter sliders and tooltip labels (#70)

* Fix loss filter slider ranges and labels

* Fix tooltip names not always showing up

Co-authored-by: jatinchowdhury18 <[email protected]>
Diffstat:
MPlugin/CHOWTapeModel.jucer | 1+
MPlugin/Source/GUI/Assets/gui.xml | 6+++---
MPlugin/Source/GUI/TooltipComp.cpp | 22++++++++++++++++------
MPlugin/Source/GUI/TooltipComp.h | 2+-
APlugin/Source/Processors/Loss_Effects/LossFilter.cpp | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MPlugin/Source/Processors/Loss_Effects/LossFilter.h | 153+++----------------------------------------------------------------------------
6 files changed, 195 insertions(+), 158 deletions(-)

diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer @@ -71,6 +71,7 @@ </GROUP> <GROUP id="{37F4BCFA-28D3-CD4D-17AF-3C696E7EC8DA}" name="Loss_Effects"> <FILE id="gJA2Gi" name="FIRFilter.h" compile="0" resource="0" file="Source/Processors/Loss_Effects/FIRFilter.h"/> + <FILE id="gjr7ub" name="LossFilter.cpp" compile="1" resource="0" file="Source/Processors/Loss_Effects/LossFilter.cpp"/> <FILE id="ZNErgZ" name="LossFilter.h" compile="0" resource="0" file="Source/Processors/Loss_Effects/LossFilter.h"/> </GROUP> <GROUP id="{0C000F30-53FD-3EFB-FAEA-6321B08AE56A}" name="Timing_Effects"> diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml @@ -65,17 +65,17 @@ <View flex-direction="column" tab-caption="Loss" tab-color="" background-color="FF31323A" padding="0" margin="0"> <View flex-grow="0.1" background-color="00000000"/> - <Slider caption="Gap [mm]" parameter="gap" slider-type="linear-horizontal" + <Slider caption="Gap [cm]" parameter="gap" slider-type="linear-horizontal" class="Slider" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" name="Gap" tooltip="Sets the width of the playhead gap. Certain frequencies that resonate with the gap width will be emphasized." slidertext-height="18" caption-placement="top-left"/> <View flex-grow="0.2" background-color="00000000"/> - <Slider caption="Thickness [mm]" parameter="thick" class="Slider" slider-type="linear-horizontal" + <Slider caption="Thickness [cm]" parameter="thick" class="Slider" slider-type="linear-horizontal" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" name="Thickness" tooltip="Sets the thickness of the tape. Thicker tape has a more muted high-frequency response." caption-placement="top-left"/> <View flex-grow="0.2" background-color="00000000"/> - <Slider caption="Spacing [mm]" parameter="spacing" slider-type="linear-horizontal" + <Slider caption="Spacing [cm]" parameter="spacing" slider-type="linear-horizontal" class="Slider" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" name="Spacing" tooltip="Sets the spacing between the tape and the playhead. A larger spacing means more high frequency signal is lost during playback." caption-placement="top-left"/> diff --git a/Plugin/Source/GUI/TooltipComp.cpp b/Plugin/Source/GUI/TooltipComp.cpp @@ -36,17 +36,20 @@ void TooltipComponent::paint (Graphics& g) } } -String TooltipComponent::getTipFor (Component& c) +void TooltipComponent::getTipFor (Component& c, String& newTip, String& newName) { if (Process::isForegroundProcess() && ! ModifierKeys::currentModifiers.isAnyMouseButtonDown()) { if (auto* ttc = dynamic_cast<TooltipClient*> (&c)) + { if (! c.isCurrentlyBlockedByAnotherModalComponent()) - return ttc->getTooltip(); + { + newTip = ttc->getTooltip(); + newName = c.getName(); + } + } } - - return {}; } void TooltipComponent::timerCallback() @@ -59,11 +62,18 @@ void TooltipComponent::timerCallback() bool needsRepaint = false; if (newComp != nullptr) { - auto newTip = getTipFor (*newComp); + String newTip, newName; + getTipFor (*newComp, newTip, newName); needsRepaint = newTip != tip; + if (newTip.isNotEmpty() && newName.isEmpty()) + { + if (auto parent = newComp->getParentComponent()) + newName = parent->getName(); + } + tip = newTip; - name = newComp->getName(); + name = newName; if (! showTip.load()) { diff --git a/Plugin/Source/GUI/TooltipComp.h b/Plugin/Source/GUI/TooltipComp.h @@ -18,7 +18,7 @@ public: void paint (Graphics& g) override; void timerCallback() override; - String getTipFor (Component& c); + void getTipFor (Component& c, String& newTip, String& newName); private: String name, tip; diff --git a/Plugin/Source/Processors/Loss_Effects/LossFilter.cpp b/Plugin/Source/Processors/Loss_Effects/LossFilter.cpp @@ -0,0 +1,169 @@ +#include "LossFilter.h" + +LossFilter::LossFilter (AudioProcessorValueTreeState& vts, int order) : + order (order) +{ + speed = vts.getRawParameterValue ("speed"); + spacing = vts.getRawParameterValue ("spacing"); + thickness = vts.getRawParameterValue ("thick"); + gap = vts.getRawParameterValue ("gap"); + + filters.add (new FIRFilter (order)); + filters.add (new FIRFilter (order)); + + currentCoefs.resize (order); +} + +void LossFilter::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) +{ + constexpr float minDist = (float) 1.0e-6; + constexpr float centreSkew = 5.0f; + + auto valueToString = [] (float value, int) { return String (10.0f * value, 5); }; + auto stringToValue = [] (const String& text) { return text.getFloatValue() / 10.0f; }; + + NormalisableRange<float> speedRange (1.0f, 100.0f); // meters per second + speedRange.setSkewForCentre (15.0f); + + NormalisableRange<float> spaceRange (minDist, 100.0f); + spaceRange.setSkewForCentre (centreSkew); + + NormalisableRange<float> thickRange (minDist, 10.0f); + thickRange.setSkewForCentre (centreSkew); + + NormalisableRange<float> gapRange (minDist, 100.0f); + gapRange.setSkewForCentre (centreSkew); + + params.push_back (std::make_unique<AudioParameterFloat> ("speed", "Speed [ips]", + speedRange, 15.0f, String(), AudioProcessorParameter::genericParameter, + [] (float value, int) { return String (value, 4); })); + + params.push_back (std::make_unique<AudioParameterFloat> ("spacing", "Spacing [cm]", + spaceRange, minDist, String(), AudioProcessorParameter::genericParameter, + valueToString, stringToValue)); + + params.push_back (std::make_unique<AudioParameterFloat> ("thick", "Thickness [cm]", + thickRange, minDist, String(), AudioProcessorParameter::genericParameter, + valueToString, stringToValue)); + + params.push_back (std::make_unique<AudioParameterFloat> ("gap", "Gap", + gapRange, minDist, String(), AudioProcessorParameter::genericParameter, + valueToString, stringToValue)); +} + +void LossFilter::prepare (float sampleRate, int samplesPerBlock) +{ + fs = sampleRate; + fadeBuffer.setSize (1, samplesPerBlock); + + fsFactor = (float) fs / 44100.0f; + const int curOrder = int (order * fsFactor); + filters.clear(); + filters.add (new FIRFilter (curOrder)); + filters.add (new FIRFilter (curOrder)); + currentCoefs.resize (curOrder); + + filters[0]->reset(); + filters[1]->reset(); + + calcCoefs(); + filters[0]->setCoefs (currentCoefs.getRawDataPointer()); + filters[1]->setCoefs (currentCoefs.getRawDataPointer()); + + prevSpeed = *speed; + prevSpacing = *spacing; + prevThickness = *thickness; + prevGap = *gap; + + starting = true; +} + +void LossFilter::calcCoefs() +{ + // Set freq domain multipliers + const int curOrder = int (order * fsFactor); + binWidth = fs / (float) curOrder; + std::unique_ptr<float[]> H (new float[curOrder]); + for (int k = 0; k < curOrder / 2; k++) + { + const auto freq = ((float) k * binWidth); // + (binWidth / 2.0f); + const auto waveNumber = MathConstants<float>::twoPi * jmax (freq, 20.0f) / (*speed * 0.0254f); + const auto thickTimesK = waveNumber * (*thickness * (float) 1.0e-3); + const auto kGapOverTwo = waveNumber * (*gap * (float) 1.0e-3) / 2.0f; + + H[k] = expf (-1.0f * waveNumber * (*spacing * (float) 1.0e-3)); // Spacing loss formula + H[k] *= (1.0f - expf (-thickTimesK)) / thickTimesK; + H[k] *= sinf (kGapOverTwo) / kGapOverTwo; + H[curOrder - k - 1] = H[k]; + } + + // Create time domain filter signals + auto h = currentCoefs.getRawDataPointer(); + for (int n = 0; n < curOrder; n++) + { + for (int k = 0; k < curOrder; k++) + h[n] += H[k] * cosf (MathConstants<float>::twoPi * (float) k * (float) n / (float) curOrder); + + h[n] /= (float) curOrder; + } +} + +void LossFilter::processBlock (float* buffer, const int numSamples) +{ + if (*spacing == (float) 1.0e-6 && *thickness == (float) 1.0e-6 && *gap == (float) 1.0e-6 + && *spacing == prevSpacing && *thickness == prevThickness && *gap == prevGap) + { + filters[activeFilter]->processBypassed (buffer, numSamples); + return; + } + + if ((*speed != prevSpeed || *spacing != prevSpacing || + *thickness != prevThickness || *gap != prevGap) && fadeCount == 0) + { + calcCoefs(); + filters[! activeFilter]->setCoefs (currentCoefs.getRawDataPointer()); + + fadeCount = fadeLength; + prevSpeed = *speed; + prevSpacing = *spacing; + prevThickness = *thickness; + prevGap = *gap; + } + + if (fadeCount > 0) + { + fadeBuffer.setSize (1, numSamples, false, false, true); + fadeBuffer.copyFrom (0, 0, buffer, numSamples); + } + else + { + filters[! activeFilter]->processBypassed (buffer, numSamples); + } + + if (! starting) + filters[activeFilter]->process (buffer, numSamples); + else + { + starting = false; + filters[activeFilter]->processBypassed (buffer, numSamples); + } + + if (fadeCount > 0) + { + auto* fadePtr = fadeBuffer.getWritePointer (0); + filters[! activeFilter]->process (fadePtr, numSamples); + + for (int n = 0; n < numSamples; ++n) + { + float mult = (float) fadeCount / (float) fadeLength; + buffer[n] = buffer[n] * mult + fadePtr[n] * (1.0f - mult); + + fadeCount--; + if (fadeCount == 0) + break; + } + + if (fadeCount == 0) + activeFilter = ! activeFilter; + } +} diff --git a/Plugin/Source/Processors/Loss_Effects/LossFilter.h b/Plugin/Source/Processors/Loss_Effects/LossFilter.h @@ -6,157 +6,14 @@ class LossFilter { public: - LossFilter (AudioProcessorValueTreeState& vts, int order = 100) : - order (order) - { - speed = vts.getRawParameterValue ("speed"); - spacing = vts.getRawParameterValue ("spacing"); - thickness = vts.getRawParameterValue ("thick"); - gap = vts.getRawParameterValue ("gap"); - - filters.add (new FIRFilter (order)); - filters.add (new FIRFilter (order)); - - currentCoefs.resize (order); - } + LossFilter (AudioProcessorValueTreeState& vts, int order = 100); ~LossFilter() {} - static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) - { - NormalisableRange<float> speedRange (1.0f, 100.0f); // meters per second - speedRange.setSkewForCentre (15.0f); - - NormalisableRange<float> spaceRange ((float) 1.0e-6, 100.0f); - spaceRange.setSkewForCentre ((float) 1.0); - - NormalisableRange<float> thickRange ((float) 1.0e-6, 10.0f); - thickRange.setSkewForCentre (0.0001f); - - NormalisableRange<float> gapRange ((float) 1.0e-6, 100.0f); - gapRange.setSkewForCentre ((float) 1.0e-3); - - params.push_back (std::make_unique<AudioParameterFloat> ("speed", "Speed [ips]", speedRange, 15.0f)); - params.push_back (std::make_unique<AudioParameterFloat> ("spacing", "Spacing [mm]", spaceRange, (float) 1.0e-9)); - params.push_back (std::make_unique<AudioParameterFloat> ("thick", "Thickness [mm]", thickRange, (float) 1.0e-9)); - params.push_back (std::make_unique<AudioParameterFloat> ("gap", "Gap", gapRange, (float) 1.0e-9)); - } - - void prepare (float sampleRate, int samplesPerBlock) - { - fs = sampleRate; - fadeBuffer.setSize (1, samplesPerBlock); - - fsFactor = (float) fs / 44100.0f; - const int curOrder = int (order * fsFactor); - filters.clear(); - filters.add (new FIRFilter (curOrder)); - filters.add (new FIRFilter (curOrder)); - currentCoefs.resize (curOrder); - - filters[0]->reset(); - filters[1]->reset(); - - calcCoefs(); - filters[0]->setCoefs (currentCoefs.getRawDataPointer()); - filters[1]->setCoefs (currentCoefs.getRawDataPointer()); - - prevSpeed = *speed; - prevSpacing = *spacing; - prevThickness = *thickness; - prevGap = *gap; - - starting = true; - } - - void calcCoefs() - { - // Set freq domain multipliers - const int curOrder = int (order * fsFactor); - binWidth = fs / (float) curOrder; - std::unique_ptr<float[]> H (new float[curOrder]); - for (int k = 0; k < curOrder / 2; k++) - { - const auto freq = ((float) k * binWidth); // + (binWidth / 2.0f); - const auto waveNumber = MathConstants<float>::twoPi * jmax (freq, 20.0f) / (*speed * 0.0254f); - const auto thickTimesK = waveNumber * (*thickness * (float) 1.0e-3); - const auto kGapOverTwo = waveNumber * (*gap * (float) 1.0e-3) / 2.0f; - - H[k] = expf (-1.0f * waveNumber * (*spacing * (float) 1.0e-3)); // Spacing loss formula - H[k] *= (1.0f - expf (-thickTimesK)) / thickTimesK; - H[k] *= sinf (kGapOverTwo) / kGapOverTwo; - H[curOrder - k - 1] = H[k]; - } - - // Create time domain filter signals - auto h = currentCoefs.getRawDataPointer(); - for (int n = 0; n < curOrder; n++) - { - for (int k = 0; k < curOrder; k++) - h[n] += H[k] * cosf (MathConstants<float>::twoPi * (float) k * (float) n / (float) curOrder); - - h[n] /= (float) curOrder; - } - } - - inline void processBlock (float* buffer, const int numSamples) - { - if (*spacing == (float) 1.0e-6 && *thickness == (float) 1.0e-6 && *gap == (float) 1.0e-6 - && *spacing == prevSpacing && *thickness == prevThickness && *gap == prevGap) - { - filters[activeFilter]->processBypassed (buffer, numSamples); - return; - } - - if ((*speed != prevSpeed || *spacing != prevSpacing || - *thickness != prevThickness || *gap != prevGap) && fadeCount == 0) - { - calcCoefs(); - filters[! activeFilter]->setCoefs (currentCoefs.getRawDataPointer()); - - fadeCount = fadeLength; - prevSpeed = *speed; - prevSpacing = *spacing; - prevThickness = *thickness; - prevGap = *gap; - } + static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - if (fadeCount > 0) - { - fadeBuffer.setSize (1, numSamples, false, false, true); - fadeBuffer.copyFrom (0, 0, buffer, numSamples); - } - else - { - filters[! activeFilter]->processBypassed (buffer, numSamples); - } - - if (! starting) - filters[activeFilter]->process (buffer, numSamples); - else - { - starting = false; - filters[activeFilter]->processBypassed (buffer, numSamples); - } - - if (fadeCount > 0) - { - auto* fadePtr = fadeBuffer.getWritePointer (0); - filters[! activeFilter]->process (fadePtr, numSamples); - - for (int n = 0; n < numSamples; ++n) - { - float mult = (float) fadeCount / (float) fadeLength; - buffer[n] = buffer[n] * mult + fadePtr[n] * (1.0f - mult); - - fadeCount--; - if (fadeCount == 0) - break; - } - - if (fadeCount == 0) - activeFilter = ! activeFilter; - } - } + void prepare (float sampleRate, int samplesPerBlock); + void calcCoefs(); + void processBlock (float* buffer, const int numSamples); private: OwnedArray<FIRFilter> filters;