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:
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;