AnalogTapeModel

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

commit 0a8da1dea339a0e8f1f8369aadf3ca314b8394c6
parent 1a3e41a9eb47d6cc5f52ba4ab0c6670d830d8712
Author: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Date:   Mon, 27 Jan 2020 20:14:00 -0800

First attempt at pre/post EQ

Diffstat:
MPlugin/CHOWTapeModel.jucer | 5+++++
MPlugin/Source/GUI Components/MainControls.cpp | 8++++++++
MPlugin/Source/GUI Components/MainControls.h | 5+++++
MPlugin/Source/PluginEditor.cpp | 18++++++++++++++++++
MPlugin/Source/PluginEditor.h | 2++
MPlugin/Source/PluginProcessor.cpp | 15+++++++++++++--
MPlugin/Source/PluginProcessor.h | 2++
MPlugin/Source/Processors/GainProcessor.h | 3++-
APlugin/Source/Processors/PrePostEQ/EQFilters.h | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/PrePostEQ/PrePostEQ.cpp | 46++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/PrePostEQ/PrePostEQ.h | 28++++++++++++++++++++++++++++
11 files changed, 235 insertions(+), 3 deletions(-)

diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer @@ -26,6 +26,11 @@ <FILE id="gFXfuJ" name="MyLNF.h" compile="0" resource="0" file="Source/GUI Extras/MyLNF.h"/> </GROUP> <GROUP id="{43BBFC88-4D0A-01B8-2635-3748470B94F4}" name="Processors"> + <GROUP id="{344B63D7-2DBC-F9D2-ACD7-1B0671D4D024}" name="PrePostEQ"> + <FILE id="wOFjAX" name="EQFilters.h" compile="0" resource="0" file="Source/Processors/PrePostEQ/EQFilters.h"/> + <FILE id="YHmS1B" name="PrePostEQ.cpp" compile="1" resource="0" file="Source/Processors/PrePostEQ/PrePostEQ.cpp"/> + <FILE id="nI7wZD" name="PrePostEQ.h" compile="0" resource="0" file="Source/Processors/PrePostEQ/PrePostEQ.h"/> + </GROUP> <GROUP id="{6052B1B0-83EF-DBFA-991C-FC0B47A949C9}" name="Hysteresis"> <FILE id="Qe4tlV" name="HysteresisProcessing.cpp" compile="1" resource="0" file="Source/Processors/Hysteresis/HysteresisProcessing.cpp"/> diff --git a/Plugin/Source/GUI Components/MainControls.cpp b/Plugin/Source/GUI Components/MainControls.cpp @@ -7,6 +7,9 @@ MainControls::MainControls (ChowtapeModelAudioProcessor& proc) : ChowtapeModelAudioProcessorEditor::createSlider (gainInKnob, proc.getVTS(), "ingain", gainInAttach, *this, myLNF, " dB"); ChowtapeModelAudioProcessorEditor::createSlider (gainOutKnob, proc.getVTS(), "outgain", gainOutAttach, *this, myLNF, " dB"); ChowtapeModelAudioProcessorEditor::createComboBox (oversampling, proc.getVTS(), "os", osAttach, this, StringArray ({"2x", "4x", "8x", "16x"})); + ChowtapeModelAudioProcessorEditor::createButton (prePostEQButton, proc.getVTS(), "prepost", prePostAttach, this, "ON/OFF", + Colours::darkred.withMultipliedBrightness (1.2f)); + // ChowtapeModelAudioProcessorEditor::createComboBox (oversampling, processor.overSampling, this); // ChowtapeModelAudioProcessorEditor::createComboBox (tapeSpeed, processor.tapeSpeed, this); // //ChowtapeModelAudioProcessorEditor::createComboBox (tapeType, processor.tapeType, this); @@ -15,6 +18,8 @@ MainControls::MainControls (ChowtapeModelAudioProcessor& proc) : ChowtapeModelAudioProcessorEditor::createLabel (inGainLabel, gainInKnob.getName(), this); ChowtapeModelAudioProcessorEditor::createLabel (outGainLabel, gainOutKnob.getName(), this); ChowtapeModelAudioProcessorEditor::createLabel (oversampleLabel, oversampling.getName(), this); + ChowtapeModelAudioProcessorEditor::createLabel (prePostEQLabel, prePostEQButton.getName(), this); + // ChowtapeModelAudioProcessorEditor::createLabel (speedLabel, processor.tapeSpeed, this); // //ChowtapeModelAudioProcessorEditor::createLabel (typeLabel, processor.tapeType, this); } @@ -29,6 +34,9 @@ void MainControls::resized() oversampleLabel.setBounds (gainInKnob.getRight() - 7 * xOffset, 2 * labelY + yOffset, tapeWidth, labelHeight); oversampling.setBounds (gainInKnob.getRight(), oversampleLabel.getBottom(), overWidth, boxHeight); + prePostEQLabel.setBounds (oversampleLabel.getX(), oversampling.getBottom() + 5, tapeWidth, labelHeight); + prePostEQButton.setBounds (prePostEQLabel.getX() + 15, prePostEQLabel.getBottom() + 3, overWidth * 4 / 5, boxHeight); + // speedLabel.setBounds (oversampling.getRight(), 2 * labelY + yOffset, tapeWidth, labelHeight); // tapeSpeed.setBounds (oversampling.getRight() + 2 * xOffset, speedLabel.getBottom(), tapeWidth, boxHeight); // diff --git a/Plugin/Source/GUI Components/MainControls.h b/Plugin/Source/GUI Components/MainControls.h @@ -7,6 +7,7 @@ using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment; using ComboBoxAttachment = AudioProcessorValueTreeState::ComboBoxAttachment; +using ButtonAttachment = AudioProcessorValueTreeState::ButtonAttachment; class MainControls : public Component { @@ -30,12 +31,16 @@ private: ComboBox oversampling; std::unique_ptr<ComboBoxAttachment> osAttach; + TextButton prePostEQButton; + std::unique_ptr<ButtonAttachment> prePostAttach; + // ComboBox tapeSpeed; // ComboBox tapeType; Label inGainLabel; Label outGainLabel; Label oversampleLabel; + Label prePostEQLabel; // Label speedLabel; // Label typeLabel; diff --git a/Plugin/Source/PluginEditor.cpp b/Plugin/Source/PluginEditor.cpp @@ -67,6 +67,24 @@ void ChowtapeModelAudioProcessorEditor::createComboBox (ComboBox& box, AudioProc comp->addAndMakeVisible (box); } +void ChowtapeModelAudioProcessorEditor::createButton (TextButton& button, AudioProcessorValueTreeState& vts, String paramID, + std::unique_ptr<ButtonAttachment>& attachment, Component* comp, String text, Colour onColour) +{ + attachment.reset (new ButtonAttachment (vts, paramID, button)); + + button.setName (vts.getParameter (paramID)->name); + button.setButtonText (text); + button.setClickingTogglesState (true); + + button.setColour (TextButton::buttonOnColourId, onColour); + button.setColour (TextButton::buttonColourId, Colours::transparentWhite); + button.setColour (TextButton::textColourOnId, Colours::antiquewhite); + button.setColour (TextButton::textColourOffId, Colours::antiquewhite); + button.setColour (ComboBox::outlineColourId, Colours::antiquewhite); + + comp->addAndMakeVisible (button); +} + void ChowtapeModelAudioProcessorEditor::createLabel (Label& label, String name, Component* comp) { label.setText (name, dontSendNotification); diff --git a/Plugin/Source/PluginEditor.h b/Plugin/Source/PluginEditor.h @@ -48,6 +48,8 @@ public: std::function<String (double)> textFromValue = {}, std::function<double (String)> valueFromText = {}); static void createComboBox (ComboBox& box, AudioProcessorValueTreeState& vts, String paramID, std::unique_ptr<ComboBoxAttachment>& attachment, Component* comp, StringArray choices); + static void createButton (TextButton& button, AudioProcessorValueTreeState& vts, String paramID, + std::unique_ptr<ButtonAttachment>& attachment, Component* comp, String text, Colour onColour); static void createLabel (Label& label, String name, Component* comp); private: diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -39,8 +39,9 @@ AudioProcessorValueTreeState::ParameterLayout ChowtapeModelAudioProcessor::creat { std::vector<std::unique_ptr<RangedAudioParameter>> params; - params.push_back (std::make_unique<AudioParameterFloat> ("ingain", "Input Gain", -30.0f, 6.0f, 0.0f)); + params.push_back (std::make_unique<AudioParameterFloat> ("ingain", "Input Gain", -30.0f, 30.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("outgain", "Output Gain", -30.0f, 30.0f, 0.0f)); + params.push_back (std::make_unique<AudioParameterBool> ("prepost", "Pre/Post EQ", true)); HysteresisProcessor::createParameterLayout (params); LossFilter::createParameterLayout (params); @@ -115,6 +116,7 @@ void ChowtapeModelAudioProcessor::changeProgramName (int index, const String& ne void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { inGain.prepareToPlay (sampleRate, samplesPerBlock); + prePostEQ.prepare (sampleRate, samplesPerBlock); hysteresis.prepareToPlay (sampleRate, samplesPerBlock); for (int ch = 0; ch < 2; ++ch) @@ -157,14 +159,23 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi { ScopedNoDenormals noDenormals; + bool usePrePost = (bool) *vts.getRawParameterValue ("prepost"); + inGain.setGain (Decibels::decibelsToGain (*vts.getRawParameterValue ("ingain"))); outGain.setGain (Decibels::decibelsToGain (*vts.getRawParameterValue ("outgain"))); inGain.processBlock (buffer, midiMessages); + + if (usePrePost) + prePostEQ.processPreBlock (buffer, midiMessages); + hysteresis.processBlock (buffer, midiMessages); + + if (usePrePost) + prePostEQ.processPostBlock (buffer, midiMessages); flutter.processBlock (buffer, midiMessages); - + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) lossFilter[ch]->processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -15,6 +15,7 @@ #include "Processors/Hysteresis/HysteresisProcessor.h" #include "Processors/Loss_Effects/LossFilter.h" #include "Processors/Timing_Effects/Flutter.h" +#include "Processors/PrePostEQ/PrePostEQ.h" //============================================================================== /** @@ -66,6 +67,7 @@ private: AudioProcessorValueTreeState vts; GainProcessor inGain; + PrePostEQ prePostEQ; HysteresisProcessor hysteresis; std::unique_ptr<LossFilter> lossFilter[2]; Flutter flutter; diff --git a/Plugin/Source/Processors/GainProcessor.h b/Plugin/Source/Processors/GainProcessor.h @@ -13,7 +13,8 @@ public: oldGain = 0.0f; } - void processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessages*/) + template <class FloatType> + void processBlock (AudioBuffer<FloatType>& buffer, MidiBuffer& /*midiMessages*/) { if (curGain != oldGain) { diff --git a/Plugin/Source/Processors/PrePostEQ/EQFilters.h b/Plugin/Source/Processors/PrePostEQ/EQFilters.h @@ -0,0 +1,106 @@ +#ifndef EQFILTERS_H_INCLUDED +#define EQFILTERS_H_INCLUDED + +#include "JuceHeader.h" + +/** First order Filter */ +class EQFilter +{ +public: + EQFilter() + { + cutoff.reset (smoothSteps); + } + + void reset (float sampleRate) + { + fs = sampleRate; + + cutoff.skip (smoothSteps); + calcCoefs (cutoff.getCurrentValue()); + } + + void setCutoff (float newFc) + { + if (cutoff.getTargetValue() != newFc) + cutoff.setTargetValue (jlimit (5.0f, fs / 2.0f - 100.0f, newFc)); + } + + template <class FloatType> + void processBlock (FloatType* buffer, const int numSamples) + { + for (int n = 0; n < numSamples; ++n) + { + if (cutoff.isSmoothing()) + calcCoefs (cutoff.getNextValue()); + buffer[n] = process (buffer[n]); + } + } + + template <class FloatType> + inline FloatType process (FloatType x) + { + FloatType y = z[1] + x * b[0]; + z[1] = (float) x * b[1] - (float) y * a[1]; + return y; + } + + virtual void calcCoefs (float /*cutoff*/) = 0; + +protected: + float fs = 48000.0f; + float a[2] = {1.0f, 0.0f}; + float b[2] = {1.0f, 0.0f}; + +private: + SmoothedValue<float, ValueSmoothingTypes::Multiplicative> cutoff = 1.0f; + const int smoothSteps = 200; + + float z[2] = {1.0f, 0.0f}; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EQFilter) +}; + +/** First order LPF */ +class LPF1 : public EQFilter +{ +public: + LPF1() {} + + void calcCoefs (float newFc) + { + float wc = MathConstants<float>::twoPi * newFc / fs; + float c = 1.0f / dsp::FastMathApproximations::tan (wc / 2.0f); + float a0 = c + 1.0f; + + b[0] = 1.0f / a0; + b[1] = b[0]; + a[1] = (1.0f - c) / a0; + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LPF1) +}; + +/** First order HPF */ +class HPF1 : public EQFilter +{ +public: + HPF1() {} + + void calcCoefs (float newFc) + { + float wc = MathConstants<float>::twoPi * newFc / fs; + float c = 1.0f / dsp::FastMathApproximations::tan (wc / 2.0f); + float a0 = c + 1.0f; + + b[0] = c / a0; + b[1] = -b[0]; + a[1] = (1.0f - c) / a0; + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HPF1) +}; + +#endif //EQFILTERS_H_INCLUDED diff --git a/Plugin/Source/Processors/PrePostEQ/PrePostEQ.cpp b/Plugin/Source/Processors/PrePostEQ/PrePostEQ.cpp @@ -0,0 +1,46 @@ +#include "PrePostEQ.h" + +void PrePostEQ::prepare (double sampleRate, int samplesPerBlock) +{ + for (int ch = 0; ch < 2; ++ch) + { + preEQ[ch].setCutoff (20.0f); + preEQ[ch].reset ((float) sampleRate); + + postEQ[ch].setCutoff (20000.0f); + postEQ[ch].reset ((float) sampleRate); + + for (int i = 0; i < 6; ++i) + { + postEQLow[ch][i].setCutoff (12000.0f); + postEQLow[ch][i].reset ((float) sampleRate); + } + } + + preGain.setGain (Decibels::decibelsToGain (18.0f)); + preGain.prepareToPlay (sampleRate, samplesPerBlock); + + postGain.setGain (Decibels::decibelsToGain (54.0f)); + postGain.prepareToPlay (sampleRate, samplesPerBlock); +} + +void PrePostEQ::processPreBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) +{ + preGain.processBlock (buffer, midiMessages); + + for (int ch = 0; ch < 2; ++ch) + preEQ[ch].processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); +} + +void PrePostEQ::processPostBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) +{ + for (int ch = 0; ch < 2; ++ch) + { + for (int i = 0; i < 6; ++i) + postEQLow[ch][i].processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); + + postEQ[ch].processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); + } + + postGain.processBlock (buffer, midiMessages); +} diff --git a/Plugin/Source/Processors/PrePostEQ/PrePostEQ.h b/Plugin/Source/Processors/PrePostEQ/PrePostEQ.h @@ -0,0 +1,28 @@ +#ifndef PREPOSTEQ_H_INCLUDED +#define PREPOSTEQ_H_INCLUDED + +#include "EQFilters.h" +#include "../GainProcessor.h" + +class PrePostEQ +{ +public: + PrePostEQ() {} + ~PrePostEQ() {} + + void prepare (double sampleRate, int samplesPerBlock); + void processPreBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages); + void processPostBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages); + +private: + LPF1 preEQ[2]; + LPF1 postEQLow[2][6]; + HPF1 postEQ[2]; + + GainProcessor preGain; + GainProcessor postGain; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PrePostEQ) +}; + +#endif //PREPOSTEQ_H_INCLUDED