AnalogTapeModel

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

commit 2f2fbadf99ae485258759ce656b96b35a63133a7
parent 484ce2ce29fec1d4572e1c7e5c4fc83d790ff245
Author: jatinchowdhury18 <[email protected]>
Date:   Fri, 12 Nov 2021 15:52:00 +0000

Implement options for mid/side processing (#227)

* ms-implementation

* Tweaks to mid-side implementation

* after rebase from the main repo and trying to avoid clicks

* Tweaks and fixes to mid/side feature

* Bring submodules up-to-date

* Do +/- 3 dB normalization in mid/side mode

* Improve mid/side fading behaviour

* Apply clang-format

Co-authored-by: ChaitanyaPTank <[email protected]>
Co-authored-by: jatinchowdhury18 <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Diffstat:
MPlugin/Source/GUI/Assets/gui.xml | 5+++++
MPlugin/Source/GUI/Assets/gui_ios.xml | 5+++++
MPlugin/Source/PluginProcessor.cpp | 13+++++++------
MPlugin/Source/PluginProcessor.h | 2++
MPlugin/Source/Processors/CMakeLists.txt | 1+
APlugin/Source/Processors/MidSide/MidSideProcessor.cpp | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/MidSide/MidSideProcessor.h | 26++++++++++++++++++++++++++
7 files changed, 122 insertions(+), 6 deletions(-)

diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml @@ -47,6 +47,11 @@ <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF"> <View flex-direction="column" tab-color="" background-color="FF31323A" padding="0" tab-caption="Gain"> + <TextButton id="mid_side" tooltip="Toggles between left/right and mid/side processing modes" margin="5" padding="0" + parameter="mid_side" text="Mid/Side" width="100" height="35" + min-height="30" min-width="100" flex-align-self="center" max-height="40" + name="Mid/Side" lookAndFeel="LookAndFeel_V3" button-color="FF33343D" + button-on-color="FFB41717" button-off-text="FFFFFFFF" button-on-text="FFFFFFFF"/> <Slider caption="Input Gain [dB]" parameter="ingain" class="Slider" name="Input Gain" padding="0" margin="0" tooltip="Sets the input gain to the tape model in Decibels."/> <Slider caption="Dry/Wet" parameter="drywet" class="Slider" tooltip="Sets dry/wet mix of the entire plugin." diff --git a/Plugin/Source/GUI/Assets/gui_ios.xml b/Plugin/Source/GUI/Assets/gui_ios.xml @@ -81,6 +81,11 @@ <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF"> <View flex-direction="column" tab-color="" background-color="FF31323A" padding="0" tab-caption="Gain"> + <TextButton id="mid_side" tooltip="Toggles between left/right and mid/side processing modes" margin="5" padding="0" + parameter="mid_side" text="Mid/Side" width="100" height="35" + min-height="30" min-width="100" flex-align-self="center" max-height="40" + name="Mid/Side" lookAndFeel="LookAndFeel_V3" button-color="FF33343D" + button-on-color="FFB41717" button-off-text="FFFFFFFF" button-on-text="FFFFFFFF"/> <Slider caption="Input Gain [dB]" parameter="ingain" class="Slider" name="Input Gain" padding="0" margin="0" tooltip="Sets the input gain to the tape model in Decibels."/> <View margin="0" padding="0" flex-grow="0.1" background-color="00000000"/> diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -31,6 +31,7 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() : AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo(), true).withOutput ("Output", juce::AudioChannelSet::stereo(), true)), vts (*this, nullptr, Identifier ("Parameters"), createParameterLayout()), inputFilters (vts), + midSideController (vts), toneControl (vts), compressionProcessor (vts), hysteresis (vts, *this), @@ -77,6 +78,7 @@ AudioProcessorValueTreeState::ParameterLayout ChowtapeModelAudioProcessor::creat WowFlutterProcessor::createParameterLayout (params); DegradeProcessor::createParameterLayout (params); ChewProcessor::createParameterLayout (params); + MidSideProcessor::createParameterLayout (params); MixGroupsController::createParameterLayout (params); return { params.begin(), params.end() }; @@ -164,6 +166,7 @@ void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesP inGain.prepareToPlay (sampleRate, samplesPerBlock); inputFilters.prepareToPlay (sampleRate, samplesPerBlock); + midSideController.prepare (sampleRate); toneControl.prepare (sampleRate); compressionProcessor.prepare (sampleRate, samplesPerBlock); hysteresis.prepareToPlay (sampleRate, samplesPerBlock); @@ -246,6 +249,7 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi scope->pushSamplesIO (buffer, TapeScope::AudioType::Input); + midSideController.processInput (buffer); toneControl.processBlockIn (buffer); compressionProcessor.processBlock (buffer); hysteresis.processBlock (buffer, midiMessages); @@ -257,6 +261,7 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi latencyCompensation(); + midSideController.processOutput (buffer); inputFilters.processBlockMakeup (buffer); outGain.processBlock (buffer, midiMessages); dryWet.processBlock (dryBuffer, buffer); @@ -302,13 +307,9 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor() builder->registerFactory ("PowerButton", &PowerButtonItem::factory); builder->registerFactory ("OversamplingMenu", &OversamplingMenu::factory); - builder->registerFactory ("FlutterMenu", [] (foleys::MagicGUIBuilder& b, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem> { - return std::make_unique<WowFlutterMenuItem> (b, node, "Flutter"); - }); + builder->registerFactory ("FlutterMenu", [] (foleys::MagicGUIBuilder& b, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem> { return std::make_unique<WowFlutterMenuItem> (b, node, "Flutter"); }); - builder->registerFactory ("WowMenu", [] (foleys::MagicGUIBuilder& b, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem> { - return std::make_unique<WowFlutterMenuItem> (b, node, "Wow"); - }); + builder->registerFactory ("WowMenu", [] (foleys::MagicGUIBuilder& b, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem> { return std::make_unique<WowFlutterMenuItem> (b, node, "Wow"); }); builder->registerJUCELookAndFeels(); builder->registerLookAndFeel ("MyLNF", std::make_unique<MyLNF>()); diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -24,6 +24,7 @@ #include "Processors/Hysteresis/ToneControl.h" #include "Processors/Input_Filters/InputFilters.h" #include "Processors/Loss_Effects/LossFilter.h" +#include "Processors/MidSide/MidSideProcessor.h" #include "Processors/Timing_Effects/WowFlutterProcessor.h" #include <JuceHeader.h> @@ -91,6 +92,7 @@ private: GainProcessor inGain; InputFilters inputFilters; + MidSideProcessor midSideController; ToneControl toneControl; CompressionProcessor compressionProcessor; HysteresisProcessor hysteresis; diff --git a/Plugin/Source/Processors/CMakeLists.txt b/Plugin/Source/Processors/CMakeLists.txt @@ -13,6 +13,7 @@ target_sources(CHOWTapeModel PRIVATE Input_Filters/InputFilters.cpp Loss_Effects/AzimuthProc.cpp Loss_Effects/LossFilter.cpp + MidSide/MidSideProcessor.cpp Timing_Effects/WowFlutterProcessor.cpp Timing_Effects/FlutterProcess.cpp Timing_Effects/WowProcess.cpp diff --git a/Plugin/Source/Processors/MidSide/MidSideProcessor.cpp b/Plugin/Source/Processors/MidSide/MidSideProcessor.cpp @@ -0,0 +1,76 @@ +#include "MidSideProcessor.h" + +MidSideProcessor::MidSideProcessor (AudioProcessorValueTreeState& vts) +{ + // set up parameter handle here + midSideParam = vts.getRawParameterValue ("mid_side"); +} + +void MidSideProcessor::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) +{ + // add parameters here + params.push_back (std::make_unique<AudioParameterBool> ("mid_side", "Mid/Side Mode", false)); +} + +void MidSideProcessor::prepare (double sampleRate) +{ + fadeSmooth.reset (sampleRate, 0.04); + + curMS = *midSideParam == 1.0f; + prevMS = curMS; +} + +void MidSideProcessor::processInput (AudioBuffer<float>& buffer) +{ + const auto numSamples = buffer.getNumSamples(); + + //mid - side encoding logic here + if (curMS && buffer.getNumChannels() != 1) + { + buffer.addFrom (0, 0, buffer, 1, 0, numSamples); // make channel 0 = left + right = mid + buffer.applyGain (1, 0, numSamples, 2.0f); // make channel 1 = 2 * right + buffer.addFrom (1, 0, buffer, 0, 0, numSamples, -1.0f); // make channel 1 = (2 * right) - (left + right) = right - left + buffer.applyGain (1, 0, numSamples, -1.0f); // make channel 1 = -1 * (right - left) = left - right = side + + buffer.applyGain (Decibels::decibelsToGain (-3.0f)); // -3 dB Normalization + } +} + +void MidSideProcessor::processOutput (AudioBuffer<float>& buffer) +{ + const auto numSamples = buffer.getNumSamples(); + + if (prevMS != (*midSideParam == 1.0f) && ! fadeSmooth.isSmoothing()) + { + fadeSmooth.setCurrentAndTargetValue (1.0f); + fadeSmooth.setTargetValue (0.0f); + } + + //mid - side decoding logic here + if (curMS && buffer.getNumChannels() != 1) + { + buffer.applyGain (Decibels::decibelsToGain (3.0f)); // undo -3 dB Normalization + + buffer.applyGain (1, 0, numSamples, -1.0f); // channel 1 = (L - R) * -1 = R - L + buffer.addFrom (0, 0, buffer, 1, 0, numSamples, -1.0f); // channel 0 = (L + R) - (R - L) = 2L + buffer.applyGain (0, 0, numSamples, 0.5f); // channel 0: 0.5 * (2L) = L + buffer.addFrom (1, 0, buffer, 0, 0, numSamples); // channel 1 = (R - L) + L = R + } + + if (fadeSmooth.isSmoothing()) + { + float startGain = fadeSmooth.getCurrentValue(); + float endGain = fadeSmooth.skip (numSamples); + + buffer.applyGainRamp (0, numSamples, startGain, endGain); + + if (endGain == 0.0f) + { + fadeSmooth.setTargetValue (1.0f); + + // reset curMS at the "bottom" of the fade + curMS = *midSideParam == 1.0f; + prevMS = curMS; + } + } +} diff --git a/Plugin/Source/Processors/MidSide/MidSideProcessor.h b/Plugin/Source/Processors/MidSide/MidSideProcessor.h @@ -0,0 +1,26 @@ +#pragma once + +#include <JuceHeader.h> + +class MidSideProcessor +{ +public: + MidSideProcessor (AudioProcessorValueTreeState& vts); + + static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); + + void prepare (double sampleRate); + + void processInput (AudioBuffer<float>& buffer); + void processOutput (AudioBuffer<float>& buffer); + +private: + std::atomic<float>* midSideParam = nullptr; // parameter handle + + bool curMS = false; + bool prevMS = false; + + SmoothedValue<float, ValueSmoothingTypes::Linear> fadeSmooth; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidSideProcessor) +};