AnalogTapeModel

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

commit eac11a0eec119069bb2cfc0c197e5a0089e85466
parent aec6b58133165862305a1d588da4e79bc5e962f1
Author: jatinchowdhury18 <jatinchowdhury18@gmail.com>
Date:   Mon, 25 Apr 2022 21:49:06 +0100

Add stereo balance control (#258)

* Set up stereo balance processing and UI

* Testing stereo balance controls
Diffstat:
MPlugin/Source/GUI/Assets/gui.xml | 32++++++++++++++++++++++----------
MPlugin/Source/GUI/Assets/gui_ios.xml | 28++++++++++++++++++++--------
MPlugin/Source/PluginProcessor.cpp | 2+-
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp | 6+++---
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.h | 2+-
MPlugin/Source/Processors/MidSide/MidSideProcessor.cpp | 52++++++++++++++++++++++++++++++++++++++++++++++++++--
MPlugin/Source/Processors/MidSide/MidSideProcessor.h | 7++++++-
7 files changed, 103 insertions(+), 26 deletions(-)

diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml @@ -31,7 +31,7 @@ </Style> </Styles> <View id="root" resizable="1" resize-corner="1" flex-direction="column" - padding="0" width="640" height="620" background-color="FF8B3232" + padding="0" width="720" height="620" background-color="FF8B3232" background-image="Background_svg" image-placement="stretch"> <View max-height="100" padding="0" margin="0" background-color=""> <View margin="2" padding="" background-color="00000000" flex-direction="column" @@ -47,15 +47,10 @@ plot-decay="0.0" plot-fill-color="FFFFFFFF"/> </View> <View padding="0" margin="" background-color="" lookAndFeel=""> - <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF"> + <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF" + flex-grow="1.5"> <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 (Stereo only)" - margin="5" padding="0" parameter="mid_side" text="Stereo" text_on="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" enabled="plugin:is_stereo"/> <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." @@ -78,6 +73,24 @@ padding="0" button-color="ff595c6b" button-on-color="FFEAA92C" parameter="ifilt_onoff" name="Filters On/Off" tooltip="Turns the pre-processing filters on or off."/> </View> + <View flex-direction="column" tab-color="" background-color="FF31323A" + padding="0" tab-caption="Stereo" enabled="plugin:is_stereo"> + <TextButton tooltip="Toggles between left/right and mid/side processing modes (Stereo only)" + margin="0" padding="5" parameter="mid_side" text="Stereo" text_on="Mid/Side" + flex-grow="0.35" name="Mid/Side" lookAndFeel="LookAndFeel_V3" + button-color="FF33343D" button-on-color="FFB41717" button-off-text="FFFFFFFF" + button-on-text="FFFFFFFF" enabled="plugin:is_stereo"/> + <View margin="0" padding="0" flex-grow="0.2" background-color="00000000"/> + <Slider caption="Balance" parameter="stereo_balance" class="Slider" name="Stereo Balance" + padding="5" margin="0" enabled="plugin:is_stereo" + tooltip="Controls the balance between the two channels (stereo or mid/side)."/> + <View margin="0" padding="0" flex-grow="0.2" background-color="00000000"/> + <TextButton parameter="stereo_makeup" text="Makeup" text_on="Makeup" background-color="00000000" + margin="0" padding="5" button-color="FF33343D" flex-grow="0.35" + button-on-color="FFB41717" lookAndFeel="LookAndFeel_V3" name="Stereo Makeup" + tooltip="Compensates for the stereo balance at the plugin output." + button-off-text="FFFFFFFF" button-on-text="FFFFFFFF" enabled="plugin:is_stereo"/> + </View> </View> <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF" flex-grow="1.5"> @@ -211,8 +224,7 @@ parameter="chew_onoff" name="Chew On/Off" tooltip="Turns the chew processing on or off."/> </View> </View> - <View display="tabbed" padding="0" margin="2" background-color="FF31323A" - lookAndFeel="MyLNF"> + <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF"> <View tab-caption="Flutter" flex-direction="column" background-color="FF31323A"> <FlutterMenu margin="0" padding="0" background-color="00000000" max-height="30" name="Flutter Sync" tooltip="Snaps the flutter rate to a synchronized value."/> diff --git a/Plugin/Source/GUI/Assets/gui_ios.xml b/Plugin/Source/GUI/Assets/gui_ios.xml @@ -78,15 +78,9 @@ plot-decay="0.0" plot-fill-color="FFFFFFFF"/> </View> <ScrollView padding="0" margin="1" background-color="00000000" lookAndFeel="MyLNF"> - <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF"> + <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF" flex-grow="1.5"> <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 (Stereo only)" margin="5" padding="0" - parameter="mid_side" text="Stereo" text_on="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" - enabled="plugin:is_stereo"/> <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"/> @@ -112,6 +106,24 @@ tooltip="Adds the signal cut out by the cut filters back to the processed signal." button-off-text="FFFFFFFF" button-on-text="FFFFFFFF"/> </View> + <View flex-direction="column" tab-color="" background-color="FF31323A" + padding="0" tab-caption="Stereo" enabled="plugin:is_stereo"> + <TextButton id="mid_side" tooltip="Toggles between left/right and mid/side processing modes (Stereo only)" + parameter="mid_side" text="Stereo" text_on="Mid/Side" margin="5" padding="0" + name="Mid/Side" lookAndFeel="LookAndFeel_V3" button-color="FF33343D" flex-grow="0.35" + button-on-color="FFB41717" button-off-text="FFFFFFFF" button-on-text="FFFFFFFF" + enabled="plugin:is_stereo"/> + <View margin="0" padding="0" flex-grow="0.2" background-color="00000000"/> + <Slider caption="Balance" parameter="stereo_balance" class="Slider" name="Stereo Balance" + padding="5" margin="0" enabled="plugin:is_stereo" + tooltip="Controls the balance between the two channels (stereo or mid/side)."/> + <View margin="0" padding="0" flex-grow="0.2" background-color="00000000"/> + <TextButton parameter="stereo_makeup" text="Makeup" text_on="Makeup" background-color="00000000" + margin="0" padding="5" button-color="FF33343D" flex-grow="0.35" + button-on-color="FFB41717" lookAndFeel="LookAndFeel_V3" name="Stereo Makeup" + tooltip="Compensates for the stereo balance at the plugin output." + button-off-text="FFFFFFFF" button-on-text="FFFFFFFF" enabled="plugin:is_stereo"/> + </View> </View> <View margin="0" padding="0" flex-grow="0.07" background-color="00000000"/> <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF" flex-grow="1.5"> @@ -264,7 +276,7 @@ </View> </View> <View margin="0" padding="0" flex-grow="0.07" background-color="00000000"/> - <View display="tabbed" padding="0" margin="2" background-color="FF31323A" lookAndFeel="MyLNF"> + <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF"> <View tab-caption="Flutter" flex-direction="column" background-color="FF31323A"> <PowerButton margin="0" padding="0" background-color="00000000" max-height="32" min-height="20" button-color="ff595c6b" button-on-color="FFEAA92C" diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -165,7 +165,7 @@ void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesP inGain.prepareToPlay (sampleRate, samplesPerBlock); inputFilters.prepareToPlay (sampleRate, samplesPerBlock, numChannels); - midSideController.prepare (sampleRate); + midSideController.prepare (sampleRate, samplesPerBlock); toneControl.prepare (sampleRate, numChannels); compressionProcessor.prepare (sampleRate, samplesPerBlock, numChannels); hysteresis.prepareToPlay (sampleRate, samplesPerBlock, numChannels); diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp @@ -78,16 +78,16 @@ void HysteresisProcessor::setSolver (int newSolver) { case RK2: case RK4: - clipLevel = 10.0; + clipLevel = 10.0f; return; case NR4: case NR8: - clipLevel = 12.5; + clipLevel = 12.5f; return; default: - clipLevel = 20.0; + clipLevel = 20.0f; }; } diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h @@ -67,7 +67,7 @@ private: double biasFreq = 48000.0; std::vector<double> biasAngle; bool wasV1 = false, useV1 = false; - double clipLevel = 20.0; + float clipLevel = 20.0f; AudioBuffer<double> doubleBuffer; BypassProcessor bypass; diff --git a/Plugin/Source/Processors/MidSide/MidSideProcessor.cpp b/Plugin/Source/Processors/MidSide/MidSideProcessor.cpp @@ -1,21 +1,42 @@ #include "MidSideProcessor.h" +namespace +{ +constexpr auto balanceGainDB = 6.0f; +} + MidSideProcessor::MidSideProcessor (AudioProcessorValueTreeState& vts) { // set up parameter handle here midSideParam = vts.getRawParameterValue ("mid_side"); + balanceParam = vts.getRawParameterValue ("stereo_balance"); + makeupParam = vts.getRawParameterValue ("stereo_makeup"); } void MidSideProcessor::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) { - // add parameters here + using namespace chowdsp::ParamUtils; params.push_back (std::make_unique<AudioParameterBool> ("mid_side", "Mid/Side Mode", false)); + params.push_back (std::make_unique<VTSParam> ("stereo_balance", "Stereo Balance", String(), NormalisableRange { -1.0f, 1.0f }, 0.0f, &percentValToString, &stringToPercentVal)); + params.push_back (std::make_unique<AudioParameterBool> ("stereo_makeup", "Stereo Makeup", true)); } -void MidSideProcessor::prepare (double sampleRate) +void MidSideProcessor::prepare (double sampleRate, int samplesPerBlock) { fadeSmooth.reset (sampleRate, 0.04); + for (auto& inGain : inBalanceGain) + { + inGain.prepare ({ sampleRate, (uint32) samplesPerBlock, 2 }); + inGain.setRampDurationSeconds (0.05); + } + + for (auto& outGain : outBalanceGain) + { + outGain.prepare ({ sampleRate, (uint32) samplesPerBlock, 2 }); + outGain.setRampDurationSeconds (0.05); + } + curMS = *midSideParam == 1.0f; prevMS = curMS; } @@ -36,6 +57,18 @@ void MidSideProcessor::processInput (AudioBuffer<float>& buffer) buffer.applyGain (Decibels::decibelsToGain (-3.0f)); // -3 dB Normalization } + + // balance processing + const auto curBalance = balanceParam->load(); + auto&& stereoBlock = dsp::AudioBlock<float> { buffer }; + auto&& leftBlock = stereoBlock.getSingleChannelBlock (0); + auto&& rightBlock = stereoBlock.getSingleChannelBlock (1); + + inBalanceGain[0].setGainDecibels (curBalance * balanceGainDB); + inBalanceGain[0].process (dsp::ProcessContextReplacing<float> { leftBlock }); + + inBalanceGain[1].setGainDecibels (curBalance * -balanceGainDB); + inBalanceGain[1].process (dsp::ProcessContextReplacing<float> { rightBlock }); } void MidSideProcessor::processOutput (AudioBuffer<float>& buffer) @@ -49,6 +82,21 @@ void MidSideProcessor::processOutput (AudioBuffer<float>& buffer) fadeSmooth.setTargetValue (0.0f); } + // inverse balance processing + if (*makeupParam == 1.0f) + { + const auto curBalance = balanceParam->load(); + auto&& stereoBlock = dsp::AudioBlock<float> { buffer }; + auto&& leftBlock = stereoBlock.getSingleChannelBlock (0); + auto&& rightBlock = stereoBlock.getSingleChannelBlock (1); + + outBalanceGain[0].setGainDecibels (curBalance * -balanceGainDB); + outBalanceGain[0].process (dsp::ProcessContextReplacing<float> { leftBlock }); + + outBalanceGain[1].setGainDecibels (curBalance * balanceGainDB); + outBalanceGain[1].process (dsp::ProcessContextReplacing<float> { rightBlock }); + } + //mid - side decoding logic here const auto numSamples = buffer.getNumSamples(); if (curMS) diff --git a/Plugin/Source/Processors/MidSide/MidSideProcessor.h b/Plugin/Source/Processors/MidSide/MidSideProcessor.h @@ -9,18 +9,23 @@ public: static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepare (double sampleRate); + void prepare (double sampleRate, int samplesPerBlock); void processInput (AudioBuffer<float>& buffer); void processOutput (AudioBuffer<float>& buffer); private: std::atomic<float>* midSideParam = nullptr; // parameter handle + std::atomic<float>* balanceParam = nullptr; + std::atomic<float>* makeupParam = nullptr; bool curMS = false; bool prevMS = false; SmoothedValue<float, ValueSmoothingTypes::Linear> fadeSmooth; + dsp::Gain<float> inBalanceGain[2]; + dsp::Gain<float> outBalanceGain[2]; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidSideProcessor) };