AnalogTapeModel

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

commit c228633bce5a7624dd77a8dc6d5fdbc02cc9519d
parent 1dfbfd3ef40be1dc6ba408db6349ebccca4fe3e5
Author: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Date:   Tue, 17 Nov 2020 14:38:23 -0800

Refactor Loss filters to function as a stereo processor

Diffstat:
MCHANGELOG.md | 1+
MPlugin/Source/PluginProcessor.cpp | 16+++++-----------
MPlugin/Source/PluginProcessor.h | 2+-
MPlugin/Source/Presets/PresetConfigs/LoFi.chowpreset | 4++--
MPlugin/Source/Presets/PresetConfigs/TC260.chowpreset | 6+++---
MPlugin/Source/Processors/Loss_Effects/FIRFilter.h | 3++-
MPlugin/Source/Processors/Loss_Effects/LossFilter.cpp | 95++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
MPlugin/Source/Processors/Loss_Effects/LossFilter.h | 12++++++++----
8 files changed, 78 insertions(+), 61 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -8,6 +8,7 @@ this file. - Updated presets menu: now supports saving presets, and managing user preset folder. - Tone section: added transition frequency control, and made bass/treble controls more extreme. - Added bypass switches for plugin sections. +- Fixed loss filter issues at higher sample rates. - Added visualizations for Wow and Flutter. - Added buttons to snap tape speed to conventional values. - Added coloured circle on bottom bar to visualize mix group. diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -39,13 +39,11 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() hysteresis (vts), degrade (vts), chewer (vts), + lossFilter (vts), flutter (vts), onOffManager (vts, this), mixGroupsController (vts, this) -{ - for (int ch = 0; ch < 2; ++ch) - lossFilter[ch].reset (new LossFilter (vts)); - +{ scope = magicState.createAndAddObject<TapeScope> ("scope"); flutter.initialisePlots (magicState); @@ -163,9 +161,7 @@ void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesP hysteresis.prepareToPlay (sampleRate, samplesPerBlock); degrade.prepareToPlay (sampleRate, samplesPerBlock); chewer.prepare (sampleRate, samplesPerBlock); - - for (int ch = 0; ch < 2; ++ch) - lossFilter[ch]->prepare ((float) sampleRate, samplesPerBlock); + lossFilter.prepare ((float) sampleRate, samplesPerBlock); dryDelay.prepare ({ sampleRate, (uint32) samplesPerBlock, 2 }); dryDelay.setDelay (calcLatencySamples()); @@ -189,7 +185,7 @@ void ChowtapeModelAudioProcessor::releaseResources() float ChowtapeModelAudioProcessor::calcLatencySamples() const noexcept { - return lossFilter[0]->getLatencySamples() + hysteresis.getLatencySamples(); + return lossFilter.getLatencySamples() + hysteresis.getLatencySamples(); } #ifndef JucePlugin_PreferredChannelConfigurations @@ -245,9 +241,7 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi chewer.processBlock (buffer); degrade.processBlock (buffer, midiMessages); flutter.processBlock (buffer, midiMessages); - - for (int ch = 0; ch < buffer.getNumChannels(); ++ch) - lossFilter[ch]->processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); + lossFilter.processBlock (buffer); latencyCompensation(); diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -87,7 +87,7 @@ private: HysteresisProcessor hysteresis; DegradeProcessor degrade; ChewProcessor chewer; - std::unique_ptr<LossFilter> lossFilter[2]; + LossFilter lossFilter; Flutter flutter; DryWetProcessor dryWet; dsp::DelayLine<float, dsp::DelayLineInterpolationTypes::Lagrange3rd> dryDelay { 1 << 21 }; diff --git a/Plugin/Source/Presets/PresetConfigs/LoFi.chowpreset b/Plugin/Source/Presets/PresetConfigs/LoFi.chowpreset @@ -20,9 +20,9 @@ <PARAM id="outgain" value="-3.5"/> <PARAM id="rate" value="0.2999999821186066"/> <PARAM id="sat" value="1.0"/> - <PARAM id="spacing" value="0.002999998396262527"/> + <PARAM id="spacing" value="2.999998396262527"/> <PARAM id="speed" value="7.500000476837158"/> - <PARAM id="thick" value="0.004999995231628418"/> + <PARAM id="thick" value="4.999995231628418"/> <PARAM id="width" value="0.5999999642372131"/> <PARAM id="wow_depth" value="0.0"/> <PARAM id="wow_rate" value="0.25"/> diff --git a/Plugin/Source/Presets/PresetConfigs/TC260.chowpreset b/Plugin/Source/Presets/PresetConfigs/TC260.chowpreset @@ -11,7 +11,7 @@ <PARAM id="depth" value="0.199999988079071"/> <PARAM id="drive" value="0.6599999666213989"/> <PARAM id="drywet" value="100.0"/> - <PARAM id="gap" value="1.614829443497001e-6"/> + <PARAM id="gap" value="1.6"/> <PARAM id="h_bass" value="0.0"/> <PARAM id="h_treble" value="0.0"/> <PARAM id="ingain" value="0.0"/> @@ -20,9 +20,9 @@ <PARAM id="outgain" value="-2.0"/> <PARAM id="rate" value="0.199999988079071"/> <PARAM id="sat" value="0.75"/> - <PARAM id="spacing" value="0.0002769171842373908"/> + <PARAM id="spacing" value="0.2769171842373908"/> <PARAM id="speed" value="15.00000095367432"/> - <PARAM id="thick" value="0.004748197738081217"/> + <PARAM id="thick" value="4.748197738081217"/> <PARAM id="width" value="0.3599999845027924"/> <PARAM id="wow_depth" value="0.2"/> <PARAM id="wow_rate" value="0.2"/> diff --git a/Plugin/Source/Processors/Loss_Effects/FIRFilter.h b/Plugin/Source/Processors/Loss_Effects/FIRFilter.h @@ -53,11 +53,12 @@ public: } } - inline void processBypassed (float* buffer, int numSamples) + inline void processBypassed (const float* buffer, int numSamples) { for (int n = 0; n < numSamples; ++n) { z[zPtr] = buffer[n]; + z[zPtr + order] = buffer[n]; zPtr = (zPtr == 0 ? order - 1 : zPtr - 1); } } diff --git a/Plugin/Source/Processors/Loss_Effects/LossFilter.cpp b/Plugin/Source/Processors/Loss_Effects/LossFilter.cpp @@ -9,8 +9,11 @@ LossFilter::LossFilter (AudioProcessorValueTreeState& vts, int order) : gap = vts.getRawParameterValue ("gap"); onOff = vts.getRawParameterValue ("loss_onoff"); - filters.add (new FIRFilter (order)); - filters.add (new FIRFilter (order)); + for (int ch = 0; ch < 2; ++ch) + { + filters[ch].add (new FIRFilter (order)); + filters[ch].add (new FIRFilter (order)); + } currentCoefs.resize (order); } @@ -62,24 +65,29 @@ float LossFilter::getLatencySamples() const noexcept void LossFilter::prepare (float sampleRate, int samplesPerBlock) { fs = sampleRate; - fadeBuffer.setSize (1, samplesPerBlock); + fadeBuffer.setSize (2, samplesPerBlock); fsFactor = (float) fs / 44100.0f; curOrder = int (order * fsFactor); - filters.clear(); - filters.add (new FIRFilter (curOrder)); - filters.add (new FIRFilter (curOrder)); currentCoefs.resize (curOrder); Hcoefs.resize (curOrder); - filters[0]->reset(); - filters[1]->reset(); - bumpFilter[0].prepare ({ (double) sampleRate, (uint32) samplesPerBlock, 1 }); - bumpFilter[1].prepare ({ (double) sampleRate, (uint32) samplesPerBlock, 1 }); + bumpFilter[0].prepare ({ (double) sampleRate, (uint32) samplesPerBlock, 2 }); + bumpFilter[1].prepare ({ (double) sampleRate, (uint32) samplesPerBlock, 2 }); + calcCoefs (bumpFilter[activeFilter]); - calcCoefs(); - filters[0]->setCoefs (currentCoefs.getRawDataPointer()); - filters[1]->setCoefs (currentCoefs.getRawDataPointer()); + for (int ch = 0; ch < 2; ++ch) + { + filters[ch].clear(); + filters[ch].add (new FIRFilter (curOrder)); + filters[ch].add (new FIRFilter (curOrder)); + + filters[ch][0]->reset(); + filters[ch][1]->reset(); + + filters[ch][0]->setCoefs (currentCoefs.getRawDataPointer()); + filters[ch][1]->setCoefs (currentCoefs.getRawDataPointer()); + } prevSpeed = *speed; prevSpacing = *spacing; @@ -89,14 +97,14 @@ void LossFilter::prepare (float sampleRate, int samplesPerBlock) bypass.prepare (samplesPerBlock, bypass.toBool (onOff)); } -static void calcHeadBumpFilter (float speedIps, float gapMeters, double fs, dsp::IIR::Filter<float>& filter) +void LossFilter::calcHeadBumpFilter (float speedIps, float gapMeters, double fs, StereoIIR& filter) { auto bumpFreq = speedIps * 0.0254f / (gapMeters * 500.0f); auto gain = jmax (1.5f * (1000.0f - std::abs (bumpFreq - 100.0f)) / 1000.0f, 1.0f); - filter.coefficients = dsp::IIR::Coefficients<float>::makePeakFilter (fs, bumpFreq, 2.0f, gain); + *filter.state = *dsp::IIR::Coefficients<float>::makePeakFilter (fs, bumpFreq, 2.0f, gain); } -void LossFilter::calcCoefs() +void LossFilter::calcCoefs (StereoIIR& filter) { // Set freq domain multipliers binWidth = fs / (float) curOrder; @@ -127,20 +135,23 @@ void LossFilter::calcCoefs() } // compute head bump filters - calcHeadBumpFilter (*speed, *gap * (float) 1.0e-6, (double) fs, bumpFilter[! activeFilter]); + calcHeadBumpFilter (*speed, *gap * (float) 1.0e-6, (double) fs, filter); } -void LossFilter::processBlock (float* buffer, const int numSamples) +void LossFilter::processBlock (AudioBuffer<float>& buffer) { - AudioBuffer<float> bufferCast (&buffer, 1, numSamples); - if (! bypass.processBlockIn (bufferCast, bypass.toBool (onOff))) + const auto numChannels = buffer.getNumChannels(); + const auto numSamples = buffer.getNumSamples(); + + if (! bypass.processBlockIn (buffer, bypass.toBool (onOff))) return; if ((*speed != prevSpeed || *spacing != prevSpacing || *thickness != prevThickness || *gap != prevGap) && fadeCount == 0) { - calcCoefs(); - filters[! activeFilter]->setCoefs (currentCoefs.getRawDataPointer()); + calcCoefs (bumpFilter[! activeFilter]); + for (int ch = 0; ch < numChannels; ++ch) + filters[ch][! activeFilter]->setCoefs (currentCoefs.getRawDataPointer()); bumpFilter[! activeFilter].reset(); fadeCount = fadeLength; @@ -152,42 +163,48 @@ void LossFilter::processBlock (float* buffer, const int numSamples) if (fadeCount > 0) { - fadeBuffer.setSize (1, numSamples, false, false, true); - fadeBuffer.copyFrom (0, 0, buffer, numSamples); + fadeBuffer.makeCopyOf (buffer, true); } else { - filters[! activeFilter]->processBypassed (buffer, numSamples); + for (int ch = 0; ch < numChannels; ++ch) + filters[ch][! activeFilter]->processBypassed (buffer.getReadPointer (ch), numSamples); } + // normal processing here... { - filters[activeFilter]->process (buffer, numSamples); - dsp::AudioBlock<float> block (&buffer, 1, numSamples); + for (int ch = 0; ch < numChannels; ++ch) + filters[ch][activeFilter]->process (buffer.getWritePointer (ch), numSamples); + + dsp::AudioBlock<float> block (buffer); dsp::ProcessContextReplacing<float> ctx (block); bumpFilter[activeFilter].process (ctx); } if (fadeCount > 0) { - auto* fadePtr = fadeBuffer.getWritePointer (0); - filters[! activeFilter]->process (fadePtr, numSamples); - dsp::AudioBlock<float> block (&fadePtr, 1, numSamples); + for (int ch = 0; ch < numChannels; ++ch) + filters[ch][! activeFilter]->process (fadeBuffer.getWritePointer (ch), numSamples); + + dsp::AudioBlock<float> block (fadeBuffer); dsp::ProcessContextReplacing<float> ctx (block); bumpFilter[! activeFilter].process (ctx); - for (int n = 0; n < numSamples; ++n) - { - float mult = (float) fadeCount / (float) fadeLength; - buffer[n] = buffer[n] * mult + fadePtr[n] * (1.0f - mult); + // fade between buffers + auto startGain = (float) fadeCount / (float) fadeLength; + auto samplesToFade = jmin (fadeCount, numSamples); + fadeCount -= samplesToFade; + auto endGain = (float) fadeCount / (float) fadeLength; + + buffer.applyGainRamp (0, samplesToFade, startGain, endGain); + buffer.applyGain (samplesToFade, numSamples - samplesToFade, endGain); - fadeCount--; - if (fadeCount == 0) - break; - } + for (int ch = 0; ch < numChannels; ++ch) + buffer.addFromWithRamp (ch, 0, fadeBuffer.getReadPointer (ch), samplesToFade, 1.0f - startGain, 1.0f - endGain); if (fadeCount == 0) activeFilter = ! activeFilter; } - bypass.processBlockOut (bufferCast, bypass.toBool (onOff)); + bypass.processBlockOut (buffer, bypass.toBool (onOff)); } diff --git a/Plugin/Source/Processors/Loss_Effects/LossFilter.h b/Plugin/Source/Processors/Loss_Effects/LossFilter.h @@ -13,13 +13,17 @@ public: static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); void prepare (float sampleRate, int samplesPerBlock); - void calcCoefs(); - void processBlock (float* buffer, const int numSamples); + void processBlock (AudioBuffer<float>& buffer); float getLatencySamples() const noexcept; private: - OwnedArray<FIRFilter> filters; - dsp::IIR::Filter<float> bumpFilter[2]; + using StereoIIR = dsp::ProcessorDuplicator<dsp::IIR::Filter<float>, dsp::IIR::Coefficients<float>>; + + void calcCoefs (StereoIIR& filter); + static void calcHeadBumpFilter (float speedIps, float gapMeters, double fs, StereoIIR& filter); + + OwnedArray<FIRFilter> filters[2]; + StereoIIR bumpFilter[2]; int activeFilter = 0; int fadeCount = 0; const int fadeLength = 1024;