AnalogTapeModel

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

commit e64ee92001449b9595d6847405aaa2f2621a18b5
parent 6d2a46aa1be134f957422d5283ea7839081ae063
Author: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Date:   Sun, 24 Feb 2019 21:29:59 -0800

Add timing effects to plugin

Diffstat:
MPlugin/CHOWTapeModel.jucer | 9+++++++++
APlugin/Source/GUI Components/TimingControls.cpp | 45+++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/GUI Components/TimingControls.h | 32++++++++++++++++++++++++++++++++
MPlugin/Source/PluginEditor.cpp | 7+++++++
MPlugin/Source/PluginEditor.h | 4+++-
MPlugin/Source/PluginProcessor.cpp | 14++++++++++++++
MPlugin/Source/PluginProcessor.h | 4++++
APlugin/Source/Processors/Timing Effects/TimingEffect.cpp | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/Timing Effects/TimingEffect.h | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MSimulations/TimingEffects/AnalyzeTiming.py | 1+
DSimulations/TimingEffects/test.py | 5-----
11 files changed, 322 insertions(+), 6 deletions(-)

diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer @@ -14,12 +14,21 @@ <FILE id="S8Xybc" name="MainControls.cpp" compile="1" resource="0" file="Source/GUI Components/MainControls.cpp"/> <FILE id="zXwghi" name="MainControls.h" compile="0" resource="0" file="Source/GUI Components/MainControls.h"/> + <FILE id="E0NC0D" name="TimingControls.cpp" compile="1" resource="0" + file="Source/GUI Components/TimingControls.cpp"/> + <FILE id="vA4R0k" name="TimingControls.h" compile="0" resource="0" + file="Source/GUI Components/TimingControls.h"/> </GROUP> <GROUP id="{7867F016-80C7-7749-B604-ED042C040FC7}" name="GUI Extras"> <FILE id="xd7cQs" name="ChowSlider.h" compile="0" resource="0" file="Source/GUI Extras/ChowSlider.h"/> <FILE id="mQFlPP" name="MyLNF.h" compile="0" resource="0" file="Source/GUI Extras/MyLNF.h"/> </GROUP> <GROUP id="{D2422983-A0E9-6A14-2092-2381CB1F3E7F}" name="Processors"> + <GROUP id="{6480D4F8-23CE-4932-1E51-CF3CF4FCC37F}" name="Timing Effects"> + <FILE id="sF9JlS" name="TimingEffect.cpp" compile="1" resource="0" + file="Source/Processors/Timing Effects/TimingEffect.cpp"/> + <FILE id="vDhRoW" name="TimingEffect.h" compile="0" resource="0" file="Source/Processors/Timing Effects/TimingEffect.h"/> + </GROUP> <GROUP id="{99069F32-1399-FF98-7C4A-19F8E855C2AA}" name="Loss Effects"> <FILE id="aEdcW7" name="LossEffects.cpp" compile="1" resource="0" file="Source/Processors/Loss Effects/LossEffects.cpp"/> <FILE id="TFnfhT" name="LossEffects.h" compile="0" resource="0" file="Source/Processors/Loss Effects/LossEffects.h"/> diff --git a/Plugin/Source/GUI Components/TimingControls.cpp b/Plugin/Source/GUI Components/TimingControls.cpp @@ -0,0 +1,45 @@ +#include "TimingControls.h" +#include "../PluginEditor.h" + +TimingControls::TimingControls (ChowtapeModelAudioProcessor& proc) : + processor (proc) +{ + ChowtapeModelAudioProcessorEditor::createSlider (flutterDepthSlide, processor.flutterDepth, myLNF, this); + flutterDepthSlide.setSkewFactorFromMidPoint (1.0); + + ChowtapeModelAudioProcessorEditor::createLabel (flutterDepthLabel, processor.flutterDepth, this); +} + +void TimingControls::paint (Graphics& g) +{ + g.setColour (Colours::antiquewhite); + g.setFont (Font ((float) nameHeight).boldened()); + + g.drawFittedText ("Timing Parameters:", Rectangle<int> (xOffset, yOffset, width, labelHeight), + Justification::centredLeft, 1); +} + +void TimingControls::resized() +{ + flutterDepthLabel.setBounds (0, 2 * yOffset + labelY, sliderWidth, labelHeight); + flutterDepthSlide.setBounds (0, 2 * yOffset + sliderY, sliderWidth, sliderWidth); +} + +void TimingControls::sliderValueChanged (Slider* slider) +{ + if (AudioParameterFloat* param = ChowtapeModelAudioProcessorEditor::getParamForSlider (slider, processor)) + *param = (float) slider->getValue(); +} + +void TimingControls::sliderDragStarted(Slider* slider) +{ + if (AudioParameterFloat* param = ChowtapeModelAudioProcessorEditor::getParamForSlider (slider, processor)) + param->beginChangeGesture(); +} + +void TimingControls::sliderDragEnded(Slider* slider) +{ + if (AudioParameterFloat* param = ChowtapeModelAudioProcessorEditor::getParamForSlider (slider, processor)) + param->endChangeGesture(); +} + diff --git a/Plugin/Source/GUI Components/TimingControls.h b/Plugin/Source/GUI Components/TimingControls.h @@ -0,0 +1,32 @@ +#ifndef TIMINGCONTROLS_H_INCLUDED +#define TIMINGCONTROLS_H_INCLUDED + +#include "../PluginProcessor.h" +#include "../GUI Extras/ChowSlider.h" +#include "../GUI Extras/MyLNF.h" + +class TimingControls : public Component, + public Slider::Listener +{ +public: + TimingControls (ChowtapeModelAudioProcessor& proc); + + void paint (Graphics&) override; + void resized() override; + +private: + void sliderValueChanged (Slider* slider) override; + void sliderDragStarted (Slider* slider) override; + void sliderDragEnded (Slider* slider) override; + + MyLNF myLNF; + + ChowtapeModelAudioProcessor& processor; + + ChowSlider flutterDepthSlide; + Label flutterDepthLabel; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimingControls) +}; + +#endif //TIMINGCONTROLS_H_INCLUDED diff --git a/Plugin/Source/PluginEditor.cpp b/Plugin/Source/PluginEditor.cpp @@ -14,6 +14,9 @@ ChowtapeModelAudioProcessorEditor::ChowtapeModelAudioProcessorEditor (ChowtapeMo lossControls.reset (new LossControls (processor)); addAndMakeVisible (lossControls.get()); + timingControls.reset (new TimingControls (processor)); + addAndMakeVisible (timingControls.get()); + setSize (width, height); } @@ -80,6 +83,7 @@ void ChowtapeModelAudioProcessorEditor::paint (Graphics& g) g.drawHorizontalLine (sectionHeight, 0, width); g.drawHorizontalLine (2 * sectionHeight, 0, width); + g.drawHorizontalLine (3 * sectionHeight, 0, width); } void ChowtapeModelAudioProcessorEditor::resized() @@ -87,6 +91,7 @@ void ChowtapeModelAudioProcessorEditor::resized() mainControls->setBounds (0, 0, width, sectionHeight); biasControls->setBounds (0, sectionHeight, width, sectionHeight); lossControls->setBounds (0, 2 * sectionHeight, width, sectionHeight); + timingControls->setBounds (0, 3 * sectionHeight, width, sectionHeight); } AudioParameterFloat* ChowtapeModelAudioProcessorEditor::getParamForSlider (Slider* slider, ChowtapeModelAudioProcessor& proc) @@ -105,6 +110,8 @@ AudioParameterFloat* ChowtapeModelAudioProcessorEditor::getParamForSlider (Slide return proc.tapeThickness; else if (proc.gapWidth->name == slider->getName()) return proc.gapWidth; + else if (proc.flutterDepth->name == slider->getName()) + return proc.flutterDepth; else return nullptr; } diff --git a/Plugin/Source/PluginEditor.h b/Plugin/Source/PluginEditor.h @@ -6,12 +6,13 @@ #include "GUI Components/BiasControls.h" #include "GUI Components/MainControls.h" #include "GUI Components/LossControls.h" +#include "GUI Components/TimingControls.h" enum { width = 375, sectionHeight = 150, - height = 3 * sectionHeight, + height = 4 * sectionHeight, nameHeight = 20, @@ -57,6 +58,7 @@ private: std::unique_ptr<MainControls> mainControls; std::unique_ptr<BiasControls> biasControls; std::unique_ptr<LossControls> lossControls; + std::unique_ptr<TimingControls> timingControls; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChowtapeModelAudioProcessorEditor) }; diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -50,9 +50,14 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() addParameter (gapWidth = new AudioParameterFloat (String ("gapWidth"), String ("Gap Width"), 2.5f, 12.0f, 3.0f)); gapWidth->addListener (this); + //Timing Controls + addParameter (flutterDepth = new AudioParameterFloat (String ("flutterDepth"), String ("Flutter Depth"), 0.0f, 5.0f, 1.0f)); + flutterDepth->addListener (this); + lossEffects.setSpeed (*tapeSpeed); hysteresis.setOverSamplingFactor (*overSampling); hysteresis.setBiasFreq (*biasFreq); + timingEffect.setDepth (*flutterDepth); } ChowtapeModelAudioProcessor::~ChowtapeModelAudioProcessor() @@ -68,7 +73,10 @@ void ChowtapeModelAudioProcessor::parameterValueChanged (int paramIndex, float n else if (paramIndex == overSampling->getParameterIndex()) hysteresis.setOverSamplingFactor (*overSampling); else if (paramIndex == tapeSpeed->getParameterIndex()) + { lossEffects.setSpeed (*tapeSpeed); + timingEffect.setTapeSpeed (*tapeSpeed); + } else if (paramIndex == tapeType->getParameterIndex()) return; //@TODO else if (paramIndex == biasFreq->getParameterIndex()) @@ -81,6 +89,8 @@ void ChowtapeModelAudioProcessor::parameterValueChanged (int paramIndex, float n lossEffects.setThickness (tapeThickness->convertFrom0to1 (newValue)); else if (paramIndex == gapWidth->getParameterIndex()) lossEffects.setGap (gapWidth->convertFrom0to1 (newValue)); + else if (paramIndex == flutterDepth->getParameterIndex()) + timingEffect.setDepth (flutterDepth->convertFrom0to1 (newValue)); } //============================================================================== @@ -150,6 +160,7 @@ void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesP { inGainProc.prepareToPlay (sampleRate, samplesPerBlock); hysteresis.prepareToPlay (sampleRate, samplesPerBlock); + timingEffect.prepareToPlay (sampleRate, samplesPerBlock); lossEffects.prepareToPlay (sampleRate, samplesPerBlock); outGainProc.prepareToPlay (sampleRate, samplesPerBlock); } @@ -158,6 +169,7 @@ void ChowtapeModelAudioProcessor::releaseResources() { inGainProc.releaseResources(); hysteresis.releaseResources(); + timingEffect.releaseResources(); lossEffects.releaseResources(); outGainProc.releaseResources(); } @@ -194,6 +206,8 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi hysteresis.processBlock (buffer, midiMessages); + timingEffect.processBlock (buffer, midiMessages); + lossEffects.processBlock (buffer, midiMessages); outGainProc.processBlock (buffer, midiMessages); diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -4,6 +4,7 @@ #include "Processors/Hysteresis/HysteresisProcessor.h" #include "Processors/GainProcessor.h" #include "Processors/Loss Effects/LossEffectsFilter.h" +#include "Processors/Timing Effects/TimingEffect.h" //============================================================================== /** @@ -62,12 +63,15 @@ public: AudioParameterFloat* tapeThickness; AudioParameterFloat* gapWidth; + AudioParameterFloat* flutterDepth; + void parameterValueChanged (int paramIndex, float newValue) override; void parameterGestureChanged (int /*paramIndex*/, bool /*gestureIsStarting*/) override {} private: HysteresisProcessor hysteresis; LossEffectsFilter lossEffects; + TimingEffect timingEffect; GainProcessor inGainProc; GainProcessor outGainProc; diff --git a/Plugin/Source/Processors/Timing Effects/TimingEffect.cpp b/Plugin/Source/Processors/Timing Effects/TimingEffect.cpp @@ -0,0 +1,145 @@ +#include "TimingEffect.h" + +void TimingEffect::DelayChannel::setReadPtr (int maxLength) +{ + readPtr++; + if (readPtr >= maxLength) //wrap + readPtr = 0; +} + +TimingEffect::TimingEffect() : ProcessorBase (String ("Timing Processor")) +{ + delayBuffer.setSize (2, maxDelaySamples); +} + +double TimingEffect::getTailLengthSeconds() const +{ + int maxLengthSamples = 0; + for (auto& ch : dChannels) + maxLengthSamples = jmax ((int) ch.length.getTargetValue(), maxLengthSamples); + + return (double) maxLengthSamples / getSampleRate(); +} + +void TimingEffect::setLength (int channel, int lengthSamples) +{ + int newLength = jmax (0, jmin (lengthSamples, (int) maxDelaySamples)); + dChannels[channel].length.setValue ((float) newLength); +} + +int TimingEffect::getLength (int channel) const +{ + return roundToInt (dChannels[channel].length.getTargetValue()); +} + +void TimingEffect::setDepth (float newDepth) +{ + setLength (0, roundToInt (newDepth * dChannels[0].length.getTargetValue() / depth)); + setLength (1, roundToInt (newDepth * dChannels[1].length.getTargetValue() / depth)); + + depth = newDepth; +} + +void TimingEffect::prepareToPlay (double sampleRate, int maximumExpectedSamplesPerBlock) +{ + setRateAndBufferSizeDetails (sampleRate, maximumExpectedSamplesPerBlock); + + delayBuffer.clear(); + + resetDelay(); +} + +void TimingEffect::releaseResources() +{ + delayBuffer.clear(); + + for (auto& ch : dChannels) + ch.resetPtrs(); +} + +void TimingEffect::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiBuffer*/) +{ + countFreq += buffer.getNumSamples(); + + if (n >= periodLen) + n = 0; + + if (countFreq > numSamplesFreq) + { + countFreq = 0; + n++; + + updateDelay(); + } + + for (int channel = 0; channel < buffer.getNumChannels(); channel++) + { + float* x = buffer.getWritePointer(channel); + for (int samp = 0; samp < buffer.getNumSamples(); samp++) + x[samp] = delay (channel, x[samp]); + } +} + +float TimingEffect::delay (int channel, float x) +{ + auto& ch = dChannels[channel]; + + // Erase head + const int erasePtr = negativeAwareModulo(ch.readPtr - 1, (int) maxDelaySamples); + delayBuffer.setSample (channel, erasePtr, 0.0f); + + // Write head + const float len = ch.length.getNextValue(); + const float fractionSample = len - (int) len; + const int writePtr = (ch.readPtr + (int) floorf (len)) % (int) maxDelaySamples; + + delayBuffer.addSample (channel, writePtr, x * (1.0f - fractionSample)); + delayBuffer.addSample (channel, (writePtr + 1) % (int) maxDelaySamples, x * fractionSample); + + // Read head + float y = delayBuffer.getSample (channel, ch.readPtr); + + //update pointers + ch.setReadPtr ((int) maxDelaySamples); + + return y; +} + +void TimingEffect::updateDelay() +{ + float t = 0; + + for (int i = 0; i < polyOrder+1; i++) + t += coefs[i] * (float) pow (n, i); + + setLength (0, roundToInt (depth * t)); + setLength (1, roundToInt (depth * t)); +} + +void TimingEffect::resetDelay() +{ + n = 0; + countFreq = 0; + numSamplesFreq = roundToInt<double> ((1.0f / freq) * getSampleRate()); + + for (auto& ch : dChannels) + ch.length.reset (getSampleRate(), 1.0 / freq); //(1 / freq) seconds to interpolate +} + +void TimingEffect::setTapeSpeed (String tapeSpeed) +{ + auto newFreq = freq; + + if (tapeSpeed == "3.75 ips") + newFreq = 5.0f; + else if (tapeSpeed == "7.5 ips") + newFreq = 10.0f; + else if (tapeSpeed == "15 ips") + newFreq = 20.0f; + + if (newFreq != freq) + { + freq = newFreq; + resetDelay(); + } +} diff --git a/Plugin/Source/Processors/Timing Effects/TimingEffect.h b/Plugin/Source/Processors/Timing Effects/TimingEffect.h @@ -0,0 +1,62 @@ +#ifndef TIMINGEFFECT_H_INCLUDED +#define TIMINGEFFECT_H_INCLUDED + +#include "../ProcessorBase.h" + +class TimingEffect : public ProcessorBase +{ +public: + TimingEffect(); + + void prepareToPlay (double sampleRate, int maxExpectedBlockSize) override; + void releaseResources() override; + void processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiBuffer*/) override; + + void setLength (int channel, int length); + int getLength (int channel) const; + + void setDepth (float newDepth); + void setTapeSpeed (String tapeSpeed); + + double getTailLengthSeconds() const override; + +private: + enum + { + maxDelaySamples = 600 * 10, + polyOrder = 10, + periodLen = 490, + }; + + struct DelayChannel + { + LinearSmoothedValue<float> length = 0.0f; + int readPtr = 0; + + void resetPtrs() { readPtr = 0; } + void setReadPtr (int maxLength); + }; + + DelayChannel dChannels[2]; + + AudioBuffer<float> delayBuffer; + + float delay (int channel, float x); + void updateDelay(); + void resetDelay(); + + float coefs[polyOrder+1] = { 5.06485313f, 2.45320771f, (float) 5.90645745e-2, + (float) -3.05750415e-4, (float) -1.16380361e-5, (float) 1.95163044e-7, + (float) -1.36154581e-9, (float) 5.11840498e-12, (float) -1.08010214e-14, + (float) 1.20503554e-17, (float) -5.53696539e-21 }; + int n = 0; + int numSamplesFreq = 0; + int countFreq = 0; + + float freq = 10.0f; + float depth = 1.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimingEffect) +}; + +#endif //TIMINGEFFECT_H_INCLUDED diff --git a/Simulations/TimingEffects/AnalyzeTiming.py b/Simulations/TimingEffects/AnalyzeTiming.py @@ -104,6 +104,7 @@ t = np.arange (len (diffs_pos)) p = np.polyfit (t, diffs_pos, 10) print (p) +print (len (diffs_pos)) y = np.poly1d (p) diff --git a/Simulations/TimingEffects/test.py b/Simulations/TimingEffects/test.py @@ -1,5 +0,0 @@ -x = [1, 2, 3] -x2 = [3, 2, 1] - -x3 = x + x2 -print (x3)