AnalogTapeModel

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

commit 7e88326dcabccbfc702d942a58e510fabf647302
parent f4e01fbc061c7f9403e42bb3f2c92bdd24662cb5
Author: jatinchowdhury18 <[email protected]>
Date:   Sat,  9 Feb 2019 19:56:48 -0800

GUI updates and processor updates

Diffstat:
MPlugin/CHOWTapeModel.jucer | 19+++++++++++++++----
APlugin/Source/GUI Extras/MyLNF.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
MPlugin/Source/PluginEditor.cpp | 52++++++++++++++++++++++++++++++++++++++++++++--------
MPlugin/Source/PluginEditor.h | 38+++++++++++++++++++++++++++++---------
MPlugin/Source/PluginProcessor.cpp | 63++++++++++++++++++++++++++++-----------------------------------
MPlugin/Source/PluginProcessor.h | 17++++++++++-------
APlugin/Source/Processors/GainProcessor.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/Hysteresis/HysteresisProcessing.h | 38++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/Hysteresis/HysteresisProcessor.h | 29+++++++++++++++++++++++++++++
DPlugin/Source/Processors/HysteresisProcessing.cpp | 76----------------------------------------------------------------------------
DPlugin/Source/Processors/HysteresisProcessing.h | 38--------------------------------------
APlugin/Source/Processors/ProcessorBase.h | 37+++++++++++++++++++++++++++++++++++++
14 files changed, 463 insertions(+), 177 deletions(-)

diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer @@ -4,11 +4,22 @@ pluginFormats="buildAU,buildStandalone,buildVST,buildVST3"> <MAINGROUP id="oKBSK1" name="CHOWTapeModel"> <GROUP id="{7659EE80-6477-48A0-C7B6-CD8900735F10}" name="Source"> + <GROUP id="{7867F016-80C7-7749-B604-ED042C040FC7}" name="GUI Extras"> + <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"> - <FILE id="SSdMYR" name="HysteresisProcessing.cpp" compile="1" resource="0" - file="Source/Processors/HysteresisProcessing.cpp"/> - <FILE id="vvuNZS" name="HysteresisProcessing.h" compile="0" resource="0" - file="Source/Processors/HysteresisProcessing.h"/> + <GROUP id="{E8FF20D6-35DC-19EF-2A0D-F8363CDBE1AE}" name="Hysteresis"> + <FILE id="sjUZ6l" name="HysteresisProcessing.cpp" compile="1" resource="0" + file="Source/Processors/Hysteresis/HysteresisProcessing.cpp"/> + <FILE id="DrklCU" name="HysteresisProcessing.h" compile="0" resource="0" + file="Source/Processors/Hysteresis/HysteresisProcessing.h"/> + <FILE id="S8e9Mq" name="HysteresisProcessor.cpp" compile="1" resource="0" + file="Source/Processors/Hysteresis/HysteresisProcessor.cpp"/> + <FILE id="zCtaDA" name="HysteresisProcessor.h" compile="0" resource="0" + file="Source/Processors/Hysteresis/HysteresisProcessor.h"/> + </GROUP> + <FILE id="kdFIkd" name="GainProcessor.h" compile="0" resource="0" file="Source/Processors/GainProcessor.h"/> + <FILE id="eGTZ2w" name="ProcessorBase.h" compile="0" resource="0" file="Source/Processors/ProcessorBase.h"/> </GROUP> <FILE id="y330sp" name="PluginProcessor.cpp" compile="1" resource="0" file="Source/PluginProcessor.cpp"/> diff --git a/Plugin/Source/GUI Extras/MyLNF.h b/Plugin/Source/GUI Extras/MyLNF.h @@ -0,0 +1,48 @@ +#ifndef MYLNF_H_INCLUDED +#define MYLNF_H_INCLUDED + +#include "JuceHeader.h" + +class MyLNF : public LookAndFeel_V4 +{ +public: + MyLNF() + { + setColour (ComboBox::outlineColourId, Colours::darkorange); + } + + Font getTextButtonFont (TextButton& button, int buttonHeight) override + { + return LookAndFeel_V4::getTextButtonFont (button, buttonHeight).boldened(); + } + + void drawRotarySlider (Graphics& g, int x, int y, int width, int height, float sliderPos, + float rotaryStartAngle, float rotaryEndAngle, Slider& slider) override + { + auto outline = slider.findColour (Slider::rotarySliderOutlineColourId); + auto fill = slider.isEnabled() ? slider.findColour (Slider::rotarySliderFillColourId) : Colours::grey; + + auto bounds = Rectangle<int> (x, y, width, height).toFloat().reduced (10); + + auto radius = jmin (bounds.getWidth(), bounds.getHeight()) / 2.0f; + auto toAngle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); + auto lineW = jmin (5.0f, radius * 0.1f); + auto arcRadius = radius - lineW; + + g.setColour (outline); + g.fillEllipse (Rectangle<float> (radius * 2.0f, radius * 2.0f).withCentre (bounds.getCentre())); + + g.setColour (fill); + g.fillEllipse (Rectangle<float> (arcRadius * 2.0f, arcRadius * 2.0f).withCentre (bounds.getCentre())); + + Point<float> thumbPoint (bounds.getCentreX() + arcRadius * std::cos (toAngle - MathConstants<float>::halfPi), + bounds.getCentreY() + arcRadius * std::sin (toAngle - MathConstants<float>::halfPi)); + g.setColour (slider.findColour (Slider::thumbColourId)); + g.drawLine (bounds.getCentreX(), bounds.getCentreY(), thumbPoint.x, thumbPoint.y, lineW); + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyLNF) +}; + +#endif //MYLNF_H_INCLUDED diff --git a/Plugin/Source/PluginEditor.cpp b/Plugin/Source/PluginEditor.cpp @@ -28,8 +28,8 @@ ChowtapeModelAudioProcessorEditor::ChowtapeModelAudioProcessorEditor (ChowtapeMo // editor's size to whatever you need it to be. setSize (width, height); - createSlider (gainInKnob, processor.inGain); - createSlider (gainOutKnob, processor.outGain); + createSlider (gainInKnob, processor.inGain, String ("dB")); + createSlider (gainOutKnob, processor.outGain, String ("dB")); createComboBox (oversampling, processor.overSampling); createComboBox (tapeSpeed, processor.tapeSpeed); @@ -39,19 +39,25 @@ ChowtapeModelAudioProcessorEditor::~ChowtapeModelAudioProcessorEditor() { } -void ChowtapeModelAudioProcessorEditor::createSlider(Slider& slide, AudioParameterFloat* param, float step){ +void ChowtapeModelAudioProcessorEditor::createSlider (ChowSlider& slide, AudioParameterFloat* param, String suffix, float step){ slide.setName(param->name); slide.setRange(param->range.start, param->range.end, step); + slide.setDefaultValue (param->convertFrom0to1 (dynamic_cast<AudioProcessorParameterWithID*> (param)->getDefaultValue())); + slide.setValue(*param); + + slide.setLookAndFeel (&myLNF); slide.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); - slide.setColour(Slider::rotarySliderFillColourId, Colours::darkred); - slide.setColour(Slider::rotarySliderOutlineColourId, Colours::black); - slide.setColour(Slider::thumbColourId, Colours::navajowhite); + slide.setColour(Slider::rotarySliderFillColourId, Colours::black); + slide.setColour(Slider::rotarySliderOutlineColourId, Colours::darkred); + slide.setColour(Slider::thumbColourId, Colours::antiquewhite); slide.setTextBoxStyle(Slider::TextBoxBelow, false, 80, 20); slide.setColour(Slider::textBoxTextColourId, Colours::antiquewhite); slide.setColour(Slider::textBoxOutlineColourId, Colours::antiquewhite); - slide.setValue(*param); - slide.addListener(this); + if (suffix.isNotEmpty()) + slide.setTextValueSuffix (" " + suffix); + + slide.addListener(this); addAndMakeVisible (slide); } @@ -92,3 +98,33 @@ void ChowtapeModelAudioProcessorEditor::resized() gainOutKnob.setBounds (tapeSpeed.getRight(), sliderY, sliderWidth, sliderWidth); } + +AudioParameterFloat* ChowtapeModelAudioProcessorEditor::getParamForSlider (Slider* slider) +{ + if (processor.inGain->name == slider->getName()) + return processor.inGain; + else if (processor.outGain->name == slider->getName()) + return processor.outGain; + else + return nullptr; +} + +void ChowtapeModelAudioProcessorEditor::sliderValueChanged (Slider* slider) +{ + if (AudioParameterFloat* param = getParamForSlider(slider)){ + *param = (float) slider->getValue(); + } +} + +void ChowtapeModelAudioProcessorEditor::sliderDragStarted(Slider* slider) +{ + if (AudioParameterFloat* param = getParamForSlider(slider)) + param->beginChangeGesture(); +} + +void ChowtapeModelAudioProcessorEditor::sliderDragEnded(Slider* slider) +{ + if (AudioParameterFloat* param = getParamForSlider(slider)) + param->endChangeGesture(); + +} diff --git a/Plugin/Source/PluginEditor.h b/Plugin/Source/PluginEditor.h @@ -2,10 +2,26 @@ #include "../JuceLibraryCode/JuceHeader.h" #include "PluginProcessor.h" +#include "GUI Extras/MyLNF.h" + +class ChowSlider : public Slider +{ +public: + ChowSlider() {} + + void setDefaultValue (const float value) { defaultValue = value; } + + void mouseDoubleClick (const MouseEvent&) override + { + setValue (defaultValue); + } + +private: + float defaultValue = 0.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChowSlider) +}; -//============================================================================== -/** -*/ class ChowtapeModelAudioProcessorEditor : public AudioProcessorEditor, public Slider::Listener, public ComboBox::Listener @@ -18,21 +34,25 @@ public: void paint (Graphics&) override; void resized() override; - void sliderValueChanged (Slider* slider) override {} void comboBoxChanged (ComboBox* box) override {} private: - // This reference is provided as a quick way for your editor to - // access the processor object that created it. + AudioParameterFloat* getParamForSlider (Slider* slider); + void sliderValueChanged (Slider* slider) override; + void sliderDragStarted (Slider* slider) override; + void sliderDragEnded (Slider* slider) override; + + MyLNF myLNF; + ChowtapeModelAudioProcessor& processor; - Slider gainInKnob; - Slider gainOutKnob; + ChowSlider gainInKnob; + ChowSlider gainOutKnob; ComboBox oversampling; ComboBox tapeSpeed; - void createSlider(Slider& slide, AudioParameterFloat* param, float step = 0.1f); + void createSlider (ChowSlider& slide, AudioParameterFloat* param, String suffix = String(), float step = 0.1f); void createComboBox (ComboBox& box, AudioParameterChoice* choice); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChowtapeModelAudioProcessorEditor) diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -15,22 +15,32 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() #endif { addParameter (inGain = new AudioParameterFloat (String ("inGain"), String ("Input Gain"), -30.0f, 30.0f, 0.0f)); + inGain->addListener (this); + addParameter (outGain = new AudioParameterFloat (String ("outGain"), String ("Output Gain"), -30.0f, 30.0f, 0.0f)); + outGain->addListener (this); addParameter (overSampling = new AudioParameterChoice (String ("overSampling"), String ("Oversampling"), StringArray ({ "2x", "4x", "8x" }), 0)); addParameter (tapeSpeed = new AudioParameterChoice (String ("tapeSpeed"), String ("Tape Speed"), StringArray ({ "3.75 ips", "7.5 ips", "15 ips" }), 1)); - - - overSample.reset (new dsp::Oversampling<float> (2, 1, dsp::Oversampling<float>::FilterType::filterHalfBandFIREquiripple)); } ChowtapeModelAudioProcessor::~ChowtapeModelAudioProcessor() { } +void ChowtapeModelAudioProcessor::parameterValueChanged (int paramIndex, float newValue) +{ + if (paramIndex == inGain->getParameterIndex()) + inGainProc.setGain (Decibels::decibelsToGain (inGain->convertFrom0to1 (newValue))); + else if (paramIndex == outGain->getParameterIndex()) + outGainProc.setGain (Decibels::decibelsToGain (outGain->convertFrom0to1 (newValue))); + else if (paramIndex == overSampling->getParameterIndex()) + hysteresis.setOverSamplingFactor (*overSampling); +} + //============================================================================== const String ChowtapeModelAudioProcessor::getName() const { @@ -80,35 +90,32 @@ int ChowtapeModelAudioProcessor::getCurrentProgram() return 0; } -void ChowtapeModelAudioProcessor::setCurrentProgram (int index) +void ChowtapeModelAudioProcessor::setCurrentProgram (int /*index*/) { } -const String ChowtapeModelAudioProcessor::getProgramName (int index) +const String ChowtapeModelAudioProcessor::getProgramName (int /*index*/) { return {}; } -void ChowtapeModelAudioProcessor::changeProgramName (int index, const String& newName) +void ChowtapeModelAudioProcessor::changeProgramName (int /*index*/, const String& /*newName*/) { } //============================================================================== void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { - // Use this method as the place to do any pre-playback - // initialisation that you need.. - hProcs[0].setSampleRate ((float) (sampleRate * overSamplingFactor)); - hProcs[1].setSampleRate ((float) (sampleRate * overSamplingFactor)); - - overSample->factorOversampling = overSamplingFactor; - overSample->initProcessing (samplesPerBlock); + inGainProc.prepareToPlay (sampleRate, samplesPerBlock); + hysteresis.prepareToPlay (sampleRate, samplesPerBlock); + outGainProc.prepareToPlay (sampleRate, samplesPerBlock); } void ChowtapeModelAudioProcessor::releaseResources() { - // When playback stops, you can use this as an opportunity to free up any - // spare memory, etc. + inGainProc.releaseResources(); + hysteresis.releaseResources(); + outGainProc.releaseResources(); } #ifndef JucePlugin_PreferredChannelConfigurations @@ -138,25 +145,11 @@ bool ChowtapeModelAudioProcessor::isBusesLayoutSupported (const BusesLayout& lay void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) { ScopedNoDenormals noDenormals; - auto totalNumInputChannels = getTotalNumInputChannels(); - auto totalNumOutputChannels = getTotalNumOutputChannels(); - - dsp::AudioBlock<float> block (buffer); - dsp::AudioBlock<float> osBlock = overSample->processSamplesUp(block); - - float* ptrArray[] = { osBlock.getChannelPointer(0), osBlock.getChannelPointer(1) }; - AudioBuffer<float> osBuffer (ptrArray, 2, static_cast<int> (osBlock.getNumSamples())); - - for (int channel = 0; channel < totalNumInputChannels; ++channel) - { - auto* x = osBuffer.getWritePointer (channel); - for (int n = 0; n < osBuffer.getNumSamples(); n++) - { - x[n] = hProcs[channel].process (((float) 1e5) * x[n]); - } - } + + inGainProc.processBlock (buffer, midiMessages); + hysteresis.processBlock (buffer, midiMessages); - overSample->processSamplesDown(block); + outGainProc.processBlock (buffer, midiMessages); } //============================================================================== @@ -171,14 +164,14 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor() } //============================================================================== -void ChowtapeModelAudioProcessor::getStateInformation (MemoryBlock& destData) +void ChowtapeModelAudioProcessor::getStateInformation (MemoryBlock& /*destData*/) { // You should use this method to store your parameters in the memory block. // You could do that either as raw data, or use the XML or ValueTree classes // as intermediaries to make it easy to save and load complex data. } -void ChowtapeModelAudioProcessor::setStateInformation (const void* data, int sizeInBytes) +void ChowtapeModelAudioProcessor::setStateInformation (const void* /*data*/, int /*sizeInBytes*/) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -1,12 +1,14 @@ #pragma once #include "../JuceLibraryCode/JuceHeader.h" -#include "Processors/HysteresisProcessing.h" +#include "Processors/Hysteresis/HysteresisProcessor.h" +#include "Processors/GainProcessor.h" //============================================================================== /** */ -class ChowtapeModelAudioProcessor : public AudioProcessor +class ChowtapeModelAudioProcessor : public AudioProcessor, + public AudioProcessorParameter::Listener { public: //============================================================================== @@ -51,13 +53,14 @@ public: AudioParameterChoice* overSampling; AudioParameterChoice* tapeSpeed; -private: - HysteresisProcessor hProcs[2]; - std::unique_ptr<dsp::Oversampling<float>> overSample; + void parameterValueChanged (int paramIndex, float newValue) override; + void parameterGestureChanged (int /*paramIndex*/, bool /*gestureIsStarting*/) override {} - int overSamplingFactor = 8; +private: + HysteresisProcessor hysteresis; - int n_t[2] = { 0, 0 }; + GainProcessor inGainProc; + GainProcessor outGainProc; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChowtapeModelAudioProcessor) diff --git a/Plugin/Source/Processors/GainProcessor.h b/Plugin/Source/Processors/GainProcessor.h @@ -0,0 +1,48 @@ +#ifndef GAINPROCESSOR_H_INCLUDED +#define GAINPROCESSOR_H_INCLUDED + +#include "ProcessorBase.h" + +class GainProcessor : public ProcessorBase +{ +public: + GainProcessor() : ProcessorBase (String ("Gain Processor")) {} + + void prepareToPlay (double sampleRate, int maximumExpectedSamplesPerBlock) override + { + setRateAndBufferSizeDetails (sampleRate, maximumExpectedSamplesPerBlock); + oldGain = 0.0f; + } + + void releaseResources() override {} + void processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessages*/) override + { + if (curGain != oldGain) + { + buffer.applyGainRamp (0, buffer.getNumSamples(), oldGain, curGain); + oldGain = curGain; + return; + } + + buffer.applyGain (curGain); + } + + void setGain (float gain) + { + if (gain == curGain) + return; + + oldGain = curGain; + curGain = gain; + } + + float getGain() const { return curGain; } + +private: + float curGain = 1.0f; + float oldGain = 0.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainProcessor) +}; + +#endif //GAINPROCESSOR_H_INCLUDED diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp @@ -0,0 +1,76 @@ +#include "HysteresisProcessing.h" +#include <math.h> + +HysteresisProcessing::HysteresisProcessing() +{ + +} + +float HysteresisProcessing::langevin (float x) +{ + if (std::abs (x) > (float) (10e-4)) + return (1.0f / (float) tanh (x)) - (1.0f / x); + else + return (x / 3.0f); +} + +float HysteresisProcessing::langevinD (float x) +{ + if (std::abs (x) > (float) (10e-4)) + { + float tanhRecip = 1.0f / tanh (x); + return (1.0f / (x * x)) - (tanhRecip * tanhRecip) + 1.0f; + } + else + return (1.0f / 3.0f); +} + +float HysteresisProcessing::deriv (float x_n, float x_n1, float x_d_n1) +{ + return ((2.0f * fs) * (x_n - x_n1)) - x_d_n1; +} + +float HysteresisProcessing::hysteresisFunc (float M, float H, float H_d) +{ + const float Q = (H + alpha * M) / a; + const float M_diff = M_s * langevin (Q) - M; + + const float delta = H_d > 0 ? 1.0f : -1.0f; + const float delta_M = signbit (delta) == signbit (M_diff) ? 1.0f : 0.0f; + + const float L_prime = langevinD (Q); + + const float denominator = 1 - (c * alpha * (M_s / a) * L_prime); + + const float t1_num = (1 - c) * delta_M * M_diff; + const float t1_den = ((1 - c) * delta * k) - (alpha * M_diff); + const float t1 = (t1_num / t1_den) * H_d; + + const float t2 = c * (M_s / a) * H_d * L_prime; + + return (t1 + t2) / denominator; +} + +float HysteresisProcessing::M_n (float prevM, float k1, float k2, float k3, float k4) +{ + return prevM + (k1 / 6.0f) + (k2 / 3.0f) + (k3 / 3.0f) + (k4 / 6.0f); +} + +float HysteresisProcessing::process (float H) +{ + const float H_d = deriv (H, H_n1, H_d_n1); + + const float T = (1.0f / fs); + const float k1 = T * hysteresisFunc (M_n1, H_n1, H_d_n1); + const float k2 = T * hysteresisFunc (M_n1 + (k1 / 2.0f), (H + H_n1) / 2.0f, (H_d + H_d_n1) / 2.0f); + const float k3 = T * hysteresisFunc (M_n1 + (k2 / 2.0f), (H + H_n1) / 2.0f, (H_d + H_d_n1) / 2.0f); + const float k4 = T * hysteresisFunc (M_n1 + k3, H, H_d); + + const float M = M_n (M_n1, k1, k2, k3, k4); + + M_n1 = M; + H_n1 = H; + H_d_n1 = H_d; + + return M / M_s; +} diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.h @@ -0,0 +1,38 @@ +#ifndef HYSTERESISPROCESSING_H_INCLUDED +#define HYSTERESISPROCESSING_H_INCLUDED + +#include "JuceHeader.h" + +class HysteresisProcessing +{ +public: + HysteresisProcessing(); + + float process (float H); + + void setSampleRate (float newSR) { fs = newSR; } + +private: + + float langevin (float x); + float langevinD (float x); + float deriv (float x_n, float x_n1, float x_d_n1); + + float hysteresisFunc (float M, float H, float H_d); + float M_n (float M_n1, float k1, float k2, float k3, float k4); + + float fs = 48000.0f; + const float M_s = 350000.0f; + const float a = (float) 2.2e4; + const float alpha = (float) 1.6e-3; + const float k = (float) 27.0e3; + const float c = (float) 1.7e-1; + + float M_n1 = 0.0f; + float H_n1 = 0.0f; + float H_d_n1 = 0.0f; + + //JUCE_DECLARE_NONCOPYABLE_WITH_LEAK_DETECTOR (HysteresisProcessing) +}; + +#endif diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp @@ -0,0 +1,61 @@ +#include "HysteresisProcessor.h" + +HysteresisProcessor::HysteresisProcessor() : ProcessorBase ("HysteresisProcessor") +{ + overSample.reset (new dsp::Oversampling<float> (2, 1, dsp::Oversampling<float>::filterHalfBandFIREquiripple)); +} + +void HysteresisProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +{ + setRateAndBufferSizeDetails (sampleRate, samplesPerBlock); + + hProcs[0].setSampleRate ((float) (sampleRate * overSamplingFactor)); + hProcs[1].setSampleRate ((float) (sampleRate * overSamplingFactor)); + + overSample->factorOversampling = overSamplingFactor; + overSample->initProcessing (samplesPerBlock); +} + +void HysteresisProcessor::releaseResources() +{ + overSample->reset(); +} + +void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midi*/) +{ + auto totalNumInputChannels = getTotalNumInputChannels(); + + dsp::AudioBlock<float> block (buffer); + dsp::AudioBlock<float> osBlock = overSample->processSamplesUp(block); + + float* ptrArray[] = { osBlock.getChannelPointer(0), osBlock.getChannelPointer(1) }; + AudioBuffer<float> osBuffer (ptrArray, 2, static_cast<int> (osBlock.getNumSamples())); + + for (int channel = 0; channel < totalNumInputChannels; ++channel) + { + auto* x = osBuffer.getWritePointer (channel); + for (int n = 0; n < osBuffer.getNumSamples(); n++) + { + x[n] = hProcs[channel].process (((float) 1e5) * x[n]); + } + } + + overSample->processSamplesDown(block); +} + +void HysteresisProcessor::setOverSamplingFactor (String osFactor) +{ + int factor = overSamplingFactor; + + if (osFactor == "2x") + factor = 2; + else if (osFactor == "4x") + factor = 4; + else if (osFactor == "8x") + factor = 8; + + overSamplingFactor = factor; + overSample->reset(); + overSample->factorOversampling = overSamplingFactor; + overSample->initProcessing (getBlockSize()); +} diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h @@ -0,0 +1,29 @@ +#ifndef HYSTERESISPROCESSOR_H_INCLUDED +#define HYSTERESISPROCESSOR_H_INCLUDED + +#include "../ProcessorBase.h" +#include "HysteresisProcessing.h" + +class HysteresisProcessor : public ProcessorBase +{ +public: + HysteresisProcessor(); + + void prepareToPlay (double sampleRate, int samplesPerBlock) override; + void releaseResources() override; + void processBlock (AudioBuffer<float>&, MidiBuffer&) override; + + double getTailLengthSeconds() const override { return overSample->getLatencyInSamples() * getSampleRate(); } + + void setOverSamplingFactor (String osFactor); + +private: + HysteresisProcessing hProcs[2]; + std::unique_ptr<dsp::Oversampling<float>> overSample; + + int overSamplingFactor = 8; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HysteresisProcessor) +}; + +#endif //HYSTERESISPROCESSOR_H_INCLUDED diff --git a/Plugin/Source/Processors/HysteresisProcessing.cpp b/Plugin/Source/Processors/HysteresisProcessing.cpp @@ -1,76 +0,0 @@ -#include "HysteresisProcessing.h" -#include <math.h> - -HysteresisProcessor::HysteresisProcessor() -{ - -} - -float HysteresisProcessor::langevin (float x) -{ - if (std::abs (x) > (float) (10e-4)) - return (1.0f / (float) tanh (x)) - (1.0f / x); - else - return (x / 3.0f); -} - -float HysteresisProcessor::langevinD (float x) -{ - if (std::abs (x) > (float) (10e-4)) - { - float tanhRecip = 1.0f / tanh (x); - return (1.0f / (x * x)) - (tanhRecip * tanhRecip) + 1.0f; - } - else - return (1.0f / 3.0f); -} - -float HysteresisProcessor::deriv (float x_n, float x_n1, float x_d_n1) -{ - return ((2.0f * fs) * (x_n - x_n1)) - x_d_n1; -} - -float HysteresisProcessor::hysteresisFunc (float M, float H, float H_d) -{ - const float Q = (H + alpha * M) / a; - const float M_diff = M_s * langevin (Q) - M; - - const float delta = H_d > 0 ? 1.0f : -1.0f; - const float delta_M = signbit (delta) == signbit (M_diff) ? 1.0f : 0.0f; - - const float L_prime = langevinD (Q); - - const float denominator = 1 - (c * alpha * (M_s / a) * L_prime); - - const float t1_num = (1 - c) * delta_M * M_diff; - const float t1_den = ((1 - c) * delta * k) - (alpha * M_diff); - const float t1 = (t1_num / t1_den) * H_d; - - const float t2 = c * (M_s / a) * H_d * L_prime; - - return (t1 + t2) / denominator; -} - -float HysteresisProcessor::M_n (float prevM, float k1, float k2, float k3, float k4) -{ - return prevM + (k1 / 6.0f) + (k2 / 3.0f) + (k3 / 3.0f) + (k4 / 6.0f); -} - -float HysteresisProcessor::process (float H) -{ - const float H_d = deriv (H, H_n1, H_d_n1); - - const float T = (1.0f / fs); - const float k1 = T * hysteresisFunc (M_n1, H_n1, H_d_n1); - const float k2 = T * hysteresisFunc (M_n1 + (k1 / 2.0f), (H + H_n1) / 2.0f, (H_d + H_d_n1) / 2.0f); - const float k3 = T * hysteresisFunc (M_n1 + (k2 / 2.0f), (H + H_n1) / 2.0f, (H_d + H_d_n1) / 2.0f); - const float k4 = T * hysteresisFunc (M_n1 + k3, H, H_d); - - const float M = M_n (M_n1, k1, k2, k3, k4); - - M_n1 = M; - H_n1 = H; - H_d_n1 = H_d; - - return M / M_s; -} diff --git a/Plugin/Source/Processors/HysteresisProcessing.h b/Plugin/Source/Processors/HysteresisProcessing.h @@ -1,38 +0,0 @@ -#ifndef HYSTERESISPROCESSOR_H_INCLUDED -#define HYSTERESISPROCESSOR_H_INCLUDED - -#include "JuceHeader.h" - -class HysteresisProcessor -{ -public: - HysteresisProcessor(); - - float process (float H); - - void setSampleRate (float newSR) { fs = newSR; } - -private: - - float langevin (float x); - float langevinD (float x); - float deriv (float x_n, float x_n1, float x_d_n1); - - float hysteresisFunc (float M, float H, float H_d); - float M_n (float M_n1, float k1, float k2, float k3, float k4); - - float fs = 48000.0f; - const float M_s = 350000.0f; - const float a = (float) 2.2e4; - const float alpha = (float) 1.6e-3; - const float k = (float) 27.0e3; - const float c = (float) 1.7e-1; - - float M_n1 = 0.0f; - float H_n1 = 0.0f; - float H_d_n1 = 0.0f; - - //JUCE_DECLARE_NONCOPYABLE_WITH_LEAK_DETECTOR (HysteresisProcessor) -}; - -#endif diff --git a/Plugin/Source/Processors/ProcessorBase.h b/Plugin/Source/Processors/ProcessorBase.h @@ -0,0 +1,37 @@ +#ifndef PROCESSORBASE_H_INCLUDED +#define PROCESSORBASE_H_INCLUDED + +#include "JuceHeader.h" + +class ProcessorBase : public AudioProcessor +{ +public: + ProcessorBase (String name) : name (name) {} + + const String getName() const override { return name; } + + double getTailLengthSeconds() const override { return 0.0; } + + bool acceptsMidi() const override { return false; } + bool producesMidi() const override { return false; } + + AudioProcessorEditor* createEditor() override { return nullptr; }; + bool hasEditor() const override { return false; } + + int getNumPrograms() override { return 0; } + void setCurrentProgram (int /*index*/) override {} + int getCurrentProgram() override { return 0; } + + const String getProgramName (int /*index*/) override { return {}; } + void changeProgramName (int /*index*/, const String& /*newName*/) override {} + + void getStateInformation (MemoryBlock& /*destData*/) override {} + void setStateInformation (const void* /*data*/, int /*sizeInBytes*/) override {} + +private: + String name; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorBase) +}; + +#endif //PROCESSORBASE_H_INCLUDED