AnalogTapeModel

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

commit 867318b9ea9368648e17ab49e9973436366c2ffc
parent 24d65b2dbd8d1d1a94e1631522627f0f68371c0e
Author: jatinchowdhury18 <jatinchowdhury18@gmail.com>
Date:   Sun, 17 Apr 2022 23:04:48 +0100

Add multi-channel support (#255)

* Disable mid/side and azimuth controls when not processing stereo channel layout

* Super basic multi-channel support

* Working multi-channel support for InputFilter and ToneControl

* Implement unit test for multi-channel support, and get compression processor working in multi-channel

* Implement multi-channel processing for everything except Hysteresis processing

* Early start on multi-channel hysteresis

* Finish implementing multi-channel hysteresis processing

* Update CHANGELOG

* Apply clang-format

* Fix silly little bug in channel interleaving

* Fix head bump filter coefficients stuff

* Fix mossing vector resize in Dropout

* Fix weird static initialization thing in OnOffManager and enable CI debugging

* Apply clang-format

* Fix Chew processor multi-channel filters

* Turn off debugging CI

* Apply clang-format

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Diffstat:
M.github/workflows/cmake.yml | 6+++---
MCHANGELOG.md | 1+
MPlugin/Source/GUI/Assets/gui.xml | 24++++++++++++++----------
MPlugin/Source/GUI/Assets/gui_ios.xml | 9+++++----
MPlugin/Source/GUI/OnOff/OnOffManager.cpp | 45+++++++++++++++++++++++----------------------
MPlugin/Source/GUI/OnOff/OnOffManager.h | 2++
MPlugin/Source/GUI/Visualizers/TapeScope.cpp | 4+---
MPlugin/Source/GUI/Visualizers/TapeScope.h | 7++++---
MPlugin/Source/Headless/CMakeLists.txt | 1+
APlugin/Source/Headless/UnitTests/MultiChannelTest.cpp | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MPlugin/Source/PluginProcessor.cpp | 74+++++++++++++++++++++++++++++++++++---------------------------------------
MPlugin/Source/PluginProcessor.h | 6++++--
MPlugin/Source/Processors/BypassProcessor.h | 4++--
MPlugin/Source/Processors/Chew/ChewProcessor.cpp | 36+++++++++++++++++++++---------------
MPlugin/Source/Processors/Chew/ChewProcessor.h | 4++--
MPlugin/Source/Processors/Chew/Dropout.h | 33++++++++++++++++-----------------
MPlugin/Source/Processors/Compression/CompressionProcessor.cpp | 27++++++++++++++++++---------
MPlugin/Source/Processors/Compression/CompressionProcessor.h | 12++++++------
MPlugin/Source/Processors/Degrade/DegradeFilter.h | 2+-
MPlugin/Source/Processors/Degrade/DegradeNoise.h | 4++--
MPlugin/Source/Processors/Degrade/DegradeProcessor.cpp | 39+++++++++++++++++++++------------------
MPlugin/Source/Processors/Degrade/DegradeProcessor.h | 6+++---
MPlugin/Source/Processors/Hysteresis/DCBlocker.h | 2++
MPlugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp | 12++++++------
MPlugin/Source/Processors/Hysteresis/HysteresisProcessing.h | 3++-
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp | 170++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.h | 22+++++++++++-----------
MPlugin/Source/Processors/Hysteresis/HysteresisSTN.h | 1+
MPlugin/Source/Processors/Hysteresis/STNModel.h | 1+
MPlugin/Source/Processors/Hysteresis/ToneControl.cpp | 54+++++++++++++++++++++++++++---------------------------
MPlugin/Source/Processors/Hysteresis/ToneControl.h | 9++++-----
MPlugin/Source/Processors/Input_Filters/InputFilters.cpp | 16++++++++--------
MPlugin/Source/Processors/Input_Filters/InputFilters.h | 6+++---
MPlugin/Source/Processors/Input_Filters/LinkwitzRileyFilter.h | 7++++---
MPlugin/Source/Processors/Loss_Effects/FIRFilter.h | 2++
MPlugin/Source/Processors/Loss_Effects/LossFilter.cpp | 63++++++++++++++++++++++++++-------------------------------------
MPlugin/Source/Processors/Loss_Effects/LossFilter.h | 12++++++------
MPlugin/Source/Processors/MidSide/MidSideProcessor.cpp | 12++++++++----
MPlugin/Source/Processors/Timing_Effects/FlutterProcess.cpp | 33+++++++++++++++++----------------
MPlugin/Source/Processors/Timing_Effects/FlutterProcess.h | 18+++++++++---------
MPlugin/Source/Processors/Timing_Effects/OHProcess.h | 22++++++++++++----------
MPlugin/Source/Processors/Timing_Effects/WowFlutterProcessor.cpp | 26++++++++++++++------------
MPlugin/Source/Processors/Timing_Effects/WowFlutterProcessor.h | 4++--
MPlugin/Source/Processors/Timing_Effects/WowProcess.cpp | 29++++++++++++++++++-----------
MPlugin/Source/Processors/Timing_Effects/WowProcess.h | 15++++++++-------
45 files changed, 560 insertions(+), 409 deletions(-)

diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml @@ -41,9 +41,9 @@ jobs: with: submodules: recursive - # - name: Setup debug session - # if: runner.os == 'Linux' - # uses: mxschmitt/action-tmate@v3 +# - name: Setup debug session +# if: runner.os == 'Linux' +# uses: mxschmitt/action-tmate@v3 - name: Configure shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. ## [UNRELEASED] +- Added multi-channel processing support. - Added CLAP plugin format support. - Added option to enable/disable OpenGL on Windows/Linux systems with sufficient OpenGL support. - Fixed V1 mode creating high-pitched tone with linear phase oversampling. diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml @@ -25,6 +25,9 @@ <XYDragComponent border="0" margin="0" padding="0" background-color="00000000" radius="0"/> </Types> + <Palettes> + <default/> + </Palettes> </Style> </Styles> <View id="root" resizable="1" resize-corner="1" flex-direction="column" @@ -47,11 +50,12 @@ <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="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"/> + <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." @@ -135,8 +139,8 @@ caption-placement="top-left"/> <Slider caption="Azimuth [degrees]" parameter="azimuth" slider-type="linear-horizontal" class="Slider" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" - name="Azimuth" tooltip="Sets the azimuth angle between the playhead and the tape. This can create a stereo widening effect at higher tape speeds." - caption-placement="top-left"/> + name="Azimuth" tooltip="Sets the azimuth angle between the playhead and the tape. This can create a stereo widening effect at higher tape speeds. (Stereo only)" + caption-placement="top-left" enabled="plugin:is_stereo"/> <Slider caption="Speed [ips]" parameter="speed" slider-type="linear-horizontal" class="Slider" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" name="Speed" tooltip="Sets the speed of the tape as it affects the playhead loss effects. Note that this control does not affect the wow/flutter processing." @@ -255,9 +259,9 @@ <View max-height="35" margin="0" padding="0" background-color="FF31323A" flex-grow="0.1"> <View background-color="00000000" flex-grow="0.05"/> - <OversamplingMenu caption="Oversampling" class="Slider" flex-grow="1.2" caption-size="0" padding="0" - combo-text="FFEAA92C" menu-accent="FFEAA92C" combo-background="00000000" max-height="100" - margin="0" lookAndFeel="ComboBoxLNF" name="Oversampling" + <OversamplingMenu caption="Oversampling" class="Slider" flex-grow="1.2" caption-size="0" + padding="0" combo-text="FFEAA92C" menu-accent="FFEAA92C" combo-background="00000000" + max-height="100" margin="0" lookAndFeel="ComboBoxLNF" name="Oversampling" tooltip="Sets the amount of oversampling used for the hysteresis processing. More oversampling will reduce aliasing artifacts, but requires more CPU resources."/> <ComboBox lookAndFeel="ComboBoxLNF" padding="0" border="0" background-color="00000000" name="Hysteresis Mode" caption="Hysteresis Mode" caption-size="0" diff --git a/Plugin/Source/GUI/Assets/gui_ios.xml b/Plugin/Source/GUI/Assets/gui_ios.xml @@ -81,11 +81,12 @@ <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" + <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"/> + 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"/> @@ -188,8 +189,8 @@ <View margin="0" padding="0" flex-grow="0.1" background-color="00000000"/> <Slider caption="Azimuth [degrees]" parameter="azimuth" slider-type="linear-horizontal" class="Slider" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" - name="Azimuth" tooltip="Sets the azimuth angle between the playhead and the tape. This can create a stereo widening effect at higher tape speeds." - caption-placement="top-left" max-height="75"/> + name="Azimuth" tooltip="Sets the azimuth angle between the playhead and the tape. This can create a stereo widening effect at higher tape speeds. (Stereo only)" + caption-placement="top-left" max-height="75" enabled="plugin:is_stereo"/> <View margin="0" padding="0" flex-grow="0.1" background-color="00000000"/> <Slider caption="Speed [ips]" parameter="speed" slider-type="linear-horizontal" class="Slider" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" diff --git a/Plugin/Source/GUI/OnOff/OnOffManager.cpp b/Plugin/Source/GUI/OnOff/OnOffManager.cpp @@ -3,24 +3,24 @@ namespace { -const std::unordered_map<String, StringArray> triggerMap { - { String ("ifilt_onoff"), StringArray ({ "Low Cut", "High Cut", "Makeup" }) }, - { String ("hyst_onoff"), StringArray ({ "Bias", "Saturation", "Drive" }) }, - { String ("tone_onoff"), StringArray ({ "Bass", "Treble", "Transition Frequency" }) }, - { String ("loss_onoff"), StringArray ({ "Gap", "Thickness", "Spacing", "Speed", "3.75 ips", "7.5 ips", "15 ips", "30 ips" }) }, - { String ("chew_onoff"), StringArray ({ "Chew Depth", "Chew Frequency", "Chew Variance" }) }, - { String ("deg_onoff"), StringArray ({ "Depth", "Amount", "Variance", "Envelope", "0.1x" }) }, - { String ("flutter_onoff"), StringArray ({ "Flutter Depth", "Flutter Rate", "Wow Depth", "Wow Rate", "Wow Variance", "Wow Drift" }) }, - { String ("comp_onoff"), StringArray ({ "Compression Amount", "Compression Attack", "Compression Release" }) }, -}; +std::unordered_map<String, StringArray> createTriggerMap() +{ + return { + { String ("ifilt_onoff"), StringArray ({ "Low Cut", "High Cut", "Makeup" }) }, + { String ("hyst_onoff"), StringArray ({ "Bias", "Saturation", "Drive" }) }, + { String ("tone_onoff"), StringArray ({ "Bass", "Treble", "Transition Frequency" }) }, + { String ("loss_onoff"), StringArray ({ "Gap", "Thickness", "Spacing", "Speed", "3.75 ips", "7.5 ips", "15 ips", "30 ips" }) }, + { String ("chew_onoff"), StringArray ({ "Chew Depth", "Chew Frequency", "Chew Variance" }) }, + { String ("deg_onoff"), StringArray ({ "Depth", "Amount", "Variance", "Envelope", "0.1x" }) }, + { String ("flutter_onoff"), StringArray ({ "Flutter Depth", "Flutter Rate", "Wow Depth", "Wow Rate", "Wow Variance", "Wow Drift" }) }, + { String ("comp_onoff"), StringArray ({ "Compression Amount", "Compression Attack", "Compression Release" }) }, + }; +} void toggleEnableDisable (Component* root, StringArray& compNames, bool shouldBeEnabled) { if (root == nullptr) - { - jassertfalse; return; - } if (compNames.isEmpty()) return; @@ -42,9 +42,10 @@ void toggleEnableDisable (Component* root, StringArray& compNames, bool shouldBe } // namespace OnOffManager::OnOffManager (AudioProcessorValueTreeState& vts, const AudioProcessor* proc) : vts (vts), - proc (proc) + proc (proc), + triggerMap (createTriggerMap()) { - for (auto& trigger : triggerMap) + for (const auto& trigger : triggerMap) vts.addParameterListener (trigger.first, this); } @@ -56,19 +57,19 @@ OnOffManager::~OnOffManager() void OnOffManager::setOnOffForNewEditor (AudioProcessorEditor* editor) { - for (auto& trigger : triggerMap) + for (const auto& trigger : triggerMap) { auto paramValue = vts.getRawParameterValue (trigger.first)->load(); - StringArray compNames (trigger.second); + StringArray compNames { trigger.second }; toggleEnableDisable (editor, compNames, (bool) paramValue); } } void OnOffManager::parameterChanged (const String& paramID, float newValue) { - if (triggerMap.find (paramID) == triggerMap.end()) // unknown trigger key - return; - - StringArray compNames (triggerMap.at (paramID)); - toggleEnableDisable (proc->getActiveEditor(), compNames, (bool) newValue); + if (const auto triggerMapIter = triggerMap.find (paramID); triggerMapIter != triggerMap.end()) + { + StringArray compNames { triggerMapIter->second }; + toggleEnableDisable (proc->getActiveEditor(), compNames, (bool) newValue); + } } diff --git a/Plugin/Source/GUI/OnOff/OnOffManager.h b/Plugin/Source/GUI/OnOff/OnOffManager.h @@ -17,6 +17,8 @@ private: AudioProcessorValueTreeState& vts; const AudioProcessor* proc; + const std::unordered_map<String, StringArray> triggerMap; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnOffManager) }; diff --git a/Plugin/Source/GUI/Visualizers/TapeScope.cpp b/Plugin/Source/GUI/Visualizers/TapeScope.cpp @@ -8,9 +8,7 @@ constexpr int rmsMS = 5000; // rms window milliseconds constexpr float xPad = 3.0f; } // namespace -TapeScope::TapeScope (int numChannels) : numChannels (numChannels) -{ -} +void TapeScope::setNumChannels (int newNumChannels) { numChannels = newNumChannels; } void TapeScope::prepareToPlay (double newSampleRate, int samplesPerBlockExpected) { diff --git a/Plugin/Source/GUI/Visualizers/TapeScope.h b/Plugin/Source/GUI/Visualizers/TapeScope.h @@ -6,8 +6,8 @@ class TapeScope : public foleys::MagicOscilloscope { public: - TapeScope (int numChannels); - ~TapeScope() = default; + TapeScope() = default; + ~TapeScope() override = default; enum AudioType { @@ -15,6 +15,7 @@ public: Output, }; + void setNumChannels (int newNumChannels); void prepareToPlay (double sampleRate, int samplesPerBlockExpected) override; void pushSamplesIO (const AudioBuffer<float>& buffer, AudioType type); @@ -24,7 +25,7 @@ private: foleys::MagicLevelSource inputSource; foleys::MagicLevelSource outputSource; - const int numChannels; + int numChannels = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TapeScope) }; diff --git a/Plugin/Source/Headless/CMakeLists.txt b/Plugin/Source/Headless/CMakeLists.txt @@ -18,6 +18,7 @@ target_sources(ChowTapeModel_Headless PRIVATE UnitTests/UnitTests.cpp UnitTests/HysteresisOpsTest.cpp UnitTests/MixGroupsTest.cpp + UnitTests/MultiChannelTest.cpp UnitTests/SpeedTest.cpp UnitTests/STNTest.cpp ) diff --git a/Plugin/Source/Headless/UnitTests/MultiChannelTest.cpp b/Plugin/Source/Headless/UnitTests/MultiChannelTest.cpp @@ -0,0 +1,84 @@ +#include "PluginProcessor.h" + +class MultiChannelTest : public UnitTest +{ + using Proc = ChowtapeModelAudioProcessor; + +public: + MultiChannelTest() : UnitTest ("MultiChannelTest") {} + + using Layout = AudioProcessor::BusesLayout; + Layout getLayout (AudioChannelSet channelSet) + { + Layout layout; + layout.inputBuses.add (channelSet); + layout.outputBuses.add (channelSet); + return layout; + } + + std::unique_ptr<Proc> createPlugin() + { + auto proc = createPluginFilterOfType (AudioProcessor::WrapperType::wrapperType_Standalone); + std::unique_ptr<Proc> plugin (dynamic_cast<Proc*> (proc)); + return std::move (plugin); + } + + void multiChannelTest (const Layout& layout) + { + auto&& plugin = createPlugin(); + const auto ableToSetBusesLayout = plugin->setBusesLayout (layout); + expect (ableToSetBusesLayout, "Unable to set buses layout: " + layout.getMainInputChannelSet().getDescription()); + + const auto& params = plugin->getParameters(); + for (auto* param : params) + { + if (auto* rangedParam = dynamic_cast<RangedAudioParameter*> (param)) + { + if (rangedParam->getParameterID().contains ("onoff")) + { + std::cout << "Turning on parameter: " << rangedParam->getName (1024) << std::endl; + rangedParam->setValueNotifyingHost (1.0f); + } + } + } + + MessageManager::getInstance()->runDispatchLoopUntil (250); + + constexpr double sampleRate = 48000.0; + constexpr int blockSize = 512; + const auto numChannels = layout.getMainInputChannels(); + std::cout << "Running plugin processing with channel count: " << numChannels << std::endl; + + AudioBuffer<float> buffer (numChannels, blockSize); + MidiBuffer midi; + + plugin->prepareToPlay (sampleRate, blockSize); + plugin->processBlock (buffer, midi); + } + + void runTest() override + { + beginTest ("Mono Test"); + multiChannelTest (getLayout (AudioChannelSet::mono())); + + beginTest ("Stereo Test"); + multiChannelTest (getLayout (AudioChannelSet::stereo())); + + beginTest ("Quad Test"); + multiChannelTest (getLayout (AudioChannelSet::quadraphonic())); + + beginTest ("5.0 Test"); + multiChannelTest (getLayout (AudioChannelSet::create5point0())); + + beginTest ("5.1 Test"); + multiChannelTest (getLayout (AudioChannelSet::create5point1())); + + beginTest ("7.0 Test"); + multiChannelTest (getLayout (AudioChannelSet::create7point0())); + + beginTest ("7.1 Test"); + multiChannelTest (getLayout (AudioChannelSet::create7point1())); + } +}; + +MultiChannelTest multiChannelTest; diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -24,14 +24,23 @@ namespace { constexpr int maxNumPresets = 999; - const String settingsFilePath = "ChowdhuryDSP/ChowTape/.plugin_settings.json"; + +//constexpr std::initializer_list<const short[2]> channelLayoutList = {{1, 1}, {2, 2}}; +const String isStereoTag = "plugin:is_stereo"; + +const String inGainTag = "ingain"; +const String outGainTag = "outgain"; +const String dryWetTag = "drywet"; } // namespace //============================================================================== ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() : AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo(), true).withOutput ("Output", juce::AudioChannelSet::stereo(), true)), vts (*this, nullptr, Identifier ("Parameters"), createParameterLayout()), + inGainDBParam (vts.getRawParameterValue (inGainTag)), + outGainDBParam (vts.getRawParameterValue (outGainTag)), + dryWetParam (vts.getRawParameterValue (dryWetTag)), inputFilters (vts), midSideController (vts), toneControl (vts), @@ -49,7 +58,7 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() positionInfo.bpm = 120.0; positionInfo.timeSigNumerator = 4; - scope = magicState.createAndAddObject<TapeScope> ("scope", getMainBusNumInputChannels()); + scope = magicState.createAndAddObject<TapeScope> ("scope"); flutter.initialisePlots (magicState); LookAndFeel::setDefaultLookAndFeel (&myLNF); @@ -69,9 +78,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> ("outgain", "Output Gain", -30.0f, 30.0f, 0.0f)); - params.push_back (std::make_unique<AudioParameterFloat> ("drywet", "Dry/Wet", 0.0f, 100.0f, 100.0f)); + params.push_back (std::make_unique<AudioParameterFloat> (inGainTag, "Input Gain", -30.0f, 6.0f, 0.0f)); + params.push_back (std::make_unique<AudioParameterFloat> (outGainTag, "Output Gain", -30.0f, 30.0f, 0.0f)); + params.push_back (std::make_unique<AudioParameterFloat> (dryWetTag, "Dry/Wet", 0.0f, 100.0f, 100.0f)); params.push_back (std::make_unique<AudioParameterInt> ("preset", "Preset", 0, maxNumPresets, 0)); InputFilters::createParameterLayout (params); @@ -166,31 +175,34 @@ void ChowtapeModelAudioProcessor::changeProgramName (int index, const String& ne //============================================================================== void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { + const auto numChannels = getTotalNumInputChannels(); setRateAndBufferSizeDetails (sampleRate, samplesPerBlock); inGain.prepareToPlay (sampleRate, samplesPerBlock); - inputFilters.prepareToPlay (sampleRate, samplesPerBlock); + inputFilters.prepareToPlay (sampleRate, samplesPerBlock, numChannels); midSideController.prepare (sampleRate); - toneControl.prepare (sampleRate); - compressionProcessor.prepare (sampleRate, samplesPerBlock); - hysteresis.prepareToPlay (sampleRate, samplesPerBlock); - degrade.prepareToPlay (sampleRate, samplesPerBlock); - chewer.prepare (sampleRate, samplesPerBlock); - lossFilter.prepare ((float) sampleRate, samplesPerBlock); - - dryDelay.prepare ({ sampleRate, (uint32) samplesPerBlock, 2 }); + toneControl.prepare (sampleRate, numChannels); + compressionProcessor.prepare (sampleRate, samplesPerBlock, numChannels); + hysteresis.prepareToPlay (sampleRate, samplesPerBlock, numChannels); + degrade.prepareToPlay (sampleRate, samplesPerBlock, numChannels); + chewer.prepare (sampleRate, samplesPerBlock, numChannels); + lossFilter.prepare ((float) sampleRate, samplesPerBlock, numChannels); + + dryDelay.prepare ({ sampleRate, (uint32) samplesPerBlock, (uint32) numChannels }); dryDelay.setDelay (calcLatencySamples()); - flutter.prepareToPlay (sampleRate, samplesPerBlock); + flutter.prepareToPlay (sampleRate, samplesPerBlock, numChannels); outGain.prepareToPlay (sampleRate, samplesPerBlock); + scope->setNumChannels (numChannels); scope->prepareToPlay (sampleRate, samplesPerBlock); dryWet.setDryWet (*vts.getRawParameterValue ("drywet") / 100.0f); dryWet.reset(); - dryBuffer.setSize (2, samplesPerBlock); + dryBuffer.setSize (numChannels, samplesPerBlock); setLatencySamples (roundToInt (calcLatencySamples())); + magicState.getPropertyAsValue (isStereoTag).setValue (numChannels == 2); } void ChowtapeModelAudioProcessor::releaseResources() @@ -203,29 +215,13 @@ float ChowtapeModelAudioProcessor::calcLatencySamples() const noexcept return lossFilter.getLatencySamples() + hysteresis.getLatencySamples() + compressionProcessor.getLatencySamples(); } -#ifndef JucePlugin_PreferredChannelConfigurations bool ChowtapeModelAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { -#if JucePlugin_IsMidiEffect - ignoreUnused (layouts); - return true; -#else - // This is the place where you check if the layout is supported. - // In this template code we only support mono or stereo. - if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono() - && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo()) - return false; - - // This checks if the input layout matches the output layout -#if ! JucePlugin_IsSynth - if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) - return false; -#endif - - return true; -#endif + return ((! layouts.getMainInputChannelSet().isDiscreteLayout()) + && (! layouts.getMainOutputChannelSet().isDiscreteLayout()) + && (layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet()) + && (! layouts.getMainInputChannelSet().isDisabled())); } -#endif void ChowtapeModelAudioProcessor::processBlockBypassed (AudioBuffer<float>& buffer, MidiBuffer&) { @@ -243,9 +239,9 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi if (auto playhead = getPlayHead()) playhead->getCurrentPosition (positionInfo); - inGain.setGain (Decibels::decibelsToGain (vts.getRawParameterValue ("ingain")->load())); - outGain.setGain (Decibels::decibelsToGain (vts.getRawParameterValue ("outgain")->load())); - dryWet.setDryWet (*vts.getRawParameterValue ("drywet") / 100.0f); + inGain.setGain (Decibels::decibelsToGain (inGainDBParam->load())); + outGain.setGain (Decibels::decibelsToGain (outGainDBParam->load())); + dryWet.setDryWet (dryWetParam->load() / 100.0f); dryBuffer.makeCopyOf (buffer, true); inGain.processBlock (buffer, midiMessages); diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -53,9 +53,7 @@ public: void prepareToPlay (double sampleRate, int samplesPerBlock) override; void releaseResources() override; -#ifndef JucePlugin_PreferredChannelConfigurations bool isBusesLayoutSupported (const BusesLayout& layouts) const override; -#endif void processBlock (AudioBuffer<float>&, MidiBuffer&) override; void processBlockBypassed (AudioBuffer<float>&, MidiBuffer&) override; @@ -102,6 +100,10 @@ private: chowdsp::SharedPluginSettings pluginSettings; AudioProcessorValueTreeState vts; + std::atomic<float>* inGainDBParam = nullptr; + std::atomic<float>* outGainDBParam = nullptr; + std::atomic<float>* dryWetParam = nullptr; + GainProcessor inGain; InputFilters inputFilters; MidSideProcessor midSideController; diff --git a/Plugin/Source/Processors/BypassProcessor.h b/Plugin/Source/Processors/BypassProcessor.h @@ -14,10 +14,10 @@ public: return static_cast<bool> (param->load()); } - void prepare (int samplesPerBlock, bool onOffParam) + void prepare (int samplesPerBlock, int numChannels, bool onOffParam) { prevOnOffParam = onOffParam; - fadeBuffer.setSize (2, samplesPerBlock); + fadeBuffer.setSize (numChannels, samplesPerBlock); bufferCopied = false; } diff --git a/Plugin/Source/Processors/Chew/ChewProcessor.cpp b/Plugin/Source/Processors/Chew/ChewProcessor.cpp @@ -16,19 +16,21 @@ void ChewProcessor::createParameterLayout (std::vector<std::unique_ptr<RangedAud params.push_back (std::make_unique<AudioParameterFloat> ("chew_var", "Chew Variance", 0.0f, 1.0f, 0.0f)); } -void ChewProcessor::prepare (double sr, int samplesPerBlock) +void ChewProcessor::prepare (double sr, int samplesPerBlock, int numChannels) { sampleRate = (float) sr; - dropout.prepare (sr); - filt[0].reset (sampleRate, int (sr * 0.02)); - filt[1].reset (sampleRate, int (sr * 0.02)); + dropout.prepare (sr, numChannels); + + filt.resize ((size_t) numChannels); + for (auto& filter : filt) + filter.reset (sampleRate, int (sr * 0.02)); isCrinkled = false; samplesUntilChange = getDryTime(); sampleCounter = 0; - bypass.prepare (samplesPerBlock, bypass.toBool (onOff)); + bypass.prepare (samplesPerBlock, numChannels, bypass.toBool (onOff)); } void ChewProcessor::processBlock (AudioBuffer<float>& buffer) @@ -76,15 +78,16 @@ void ChewProcessor::processShortBlock (AudioBuffer<float>& buffer) if (*freq == 0.0f) { mix = 0.0f; - filt[0].setFreq (highFreq); - filt[1].setFreq (highFreq); + for (auto& filter : filt) + filter.setFreq (highFreq); } else if (*freq == 1.0f) { mix = 1.0f; power = 3.0f * *depth; - filt[0].setFreq (highFreq - freqChange * *depth); - filt[1].setFreq (highFreq - freqChange * *depth); + const auto filterFreq = highFreq - freqChange * *depth; + for (auto& filter : filt) + filter.setFreq (filterFreq); } else if (sampleCounter >= samplesUntilChange) { @@ -95,15 +98,17 @@ void ChewProcessor::processShortBlock (AudioBuffer<float>& buffer) { mix = 1.0f; power = (1.0f + 2.0f * random.nextFloat()) * *depth; - filt[0].setFreq (highFreq - freqChange * *depth); - filt[1].setFreq (highFreq - freqChange * *depth); + const auto filterFreq = highFreq - freqChange * *depth; + for (auto& filter : filt) + filter.setFreq (filterFreq); + samplesUntilChange = getWetTime(); } else // end crinkle { mix = 0.0f; - filt[0].setFreq (highFreq); - filt[1].setFreq (highFreq); + for (auto& filter : filt) + filter.setFreq (highFreq); samplesUntilChange = getDryTime(); } } @@ -112,8 +117,9 @@ void ChewProcessor::processShortBlock (AudioBuffer<float>& buffer) power = (1.0f + 2.0f * random.nextFloat()) * *depth; if (isCrinkled) { - filt[0].setFreq (highFreq - freqChange * *depth); - filt[1].setFreq (highFreq - freqChange * *depth); + const auto filterFreq = highFreq - freqChange * *depth; + for (auto& filter : filt) + filter.setFreq (filterFreq); } } diff --git a/Plugin/Source/Processors/Chew/ChewProcessor.h b/Plugin/Source/Processors/Chew/ChewProcessor.h @@ -12,7 +12,7 @@ public: static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepare (double sr, int samplesPerBlock); + void prepare (double sr, int samplesPerBlock, int numChannels); void processBlock (AudioBuffer<float>& buffer); void processShortBlock (AudioBuffer<float>& buffer); @@ -25,7 +25,7 @@ private: float power = 0.0f; Dropout dropout; - DegradeFilter filt[2]; + std::vector<DegradeFilter> filt; Random random; int samplesUntilChange = 1000; diff --git a/Plugin/Source/Processors/Chew/Dropout.h b/Plugin/Source/Processors/Chew/Dropout.h @@ -10,26 +10,25 @@ public: void setMix (float newMix) { - for (int ch = 0; ch < 2; ++ch) - mixSmooth[ch].setTargetValue (newMix); + for (auto& mix : mixSmooth) + mix.setTargetValue (newMix); } void setPower (float newPow) { - for (int ch = 0; ch < 2; ++ch) - powerSmooth[ch].setTargetValue (newPow); + for (auto& power : powerSmooth) + power.setTargetValue (newPow); } - void prepare (double sr) + void prepare (double sr, int numChannels) { - for (int ch = 0; ch < 2; ++ch) - { - mixSmooth[ch].reset (sr, 0.01); - mixSmooth[ch].setCurrentAndTargetValue (mixSmooth[ch].getTargetValue()); + mixSmooth.resize ((size_t) numChannels); + for (auto& mix : mixSmooth) + mix.reset (sr, 0.01); - powerSmooth[ch].reset (sr, 0.005); - powerSmooth[ch].setCurrentAndTargetValue (powerSmooth[ch].getTargetValue()); - } + powerSmooth.resize ((size_t) numChannels); + for (auto& power : powerSmooth) + power.reset (sr, 0.005); } void process (AudioBuffer<float>& buffer) @@ -37,9 +36,9 @@ public: if (mixSmooth[0].getTargetValue() == 0.0f && ! mixSmooth[0].isSmoothing()) return; - for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + for (size_t ch = 0; ch < (size_t) buffer.getNumChannels(); ++ch) { - auto* x = buffer.getWritePointer (ch); + auto* x = buffer.getWritePointer ((int) ch); for (int n = 0; n < buffer.getNumSamples(); ++n) { auto mix = mixSmooth[ch].getNextValue(); @@ -48,15 +47,15 @@ public: } } - inline float dropout (float x, int ch) + inline float dropout (float x, size_t ch) { auto sign = (float) chowdsp::signum (x); return pow (abs (x), powerSmooth[ch].getNextValue()) * sign; } private: - LinearSmoothedValue<float> mixSmooth[2]; - LinearSmoothedValue<float> powerSmooth[2]; + std::vector<LinearSmoothedValue<float>> mixSmooth; + std::vector<LinearSmoothedValue<float>> powerSmooth; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Dropout) }; diff --git a/Plugin/Source/Processors/Compression/CompressionProcessor.cpp b/Plugin/Source/Processors/Compression/CompressionProcessor.cpp @@ -27,14 +27,20 @@ void CompressionProcessor::createParameterLayout (std::vector<std::unique_ptr<Ra params.push_back (std::make_unique<AudioParameterFloat> ("comp_release", "Compression Release", relRange, 200.0f, String(), AudioProcessorParameter::genericParameter, twoDecimalFloat)); } -void CompressionProcessor::prepare (double sr, int samplesPerBlock) +void CompressionProcessor::prepare (double sr, int samplesPerBlock, int numChannels) { - oversample.initProcessing ((size_t) samplesPerBlock); - auto osFactor = oversample.getOversamplingFactor(); - bypass.prepare (samplesPerBlock, bypass.toBool (onOff)); - - for (int ch = 0; ch < 2; ++ch) + oversample = std::make_unique<dsp::Oversampling<float>> (numChannels, 1, dsp::Oversampling<float>::filterHalfBandPolyphaseIIR, true, true); + oversample->initProcessing ((size_t) samplesPerBlock); + auto osFactor = oversample->getOversamplingFactor(); + bypass.prepare (samplesPerBlock, numChannels, bypass.toBool (onOff)); + + slewLimiter.clear(); + dbPlusSmooth.clear(); + for (int ch = 0; ch < numChannels; ++ch) { + slewLimiter.emplace_back(); + dbPlusSmooth.emplace_back(); + slewLimiter[ch].prepare ({ sr, (uint32) samplesPerBlock, 1 }); dbPlusSmooth[ch].reset (sr, 0.05); } @@ -71,7 +77,7 @@ void CompressionProcessor::processBlock (AudioBuffer<float>& buffer) return; dsp::AudioBlock<float> block (buffer); - auto osBlock = oversample.processSamplesUp (block); + auto osBlock = oversample->processSamplesUp (block); const auto numSamples = (int) osBlock.getNumSamples(); for (int ch = 0; ch < buffer.getNumChannels(); ++ch) @@ -112,13 +118,16 @@ void CompressionProcessor::processBlock (AudioBuffer<float>& buffer) FloatVectorOperations::multiply (x, compGainVec.data(), numSamples); } - oversample.processSamplesDown (block); + oversample->processSamplesDown (block); bypass.processBlockOut (buffer, bypass.toBool (onOff)); } float CompressionProcessor::getLatencySamples() const noexcept { - return onOff->load() == 1.0f ? oversample.getLatencyInSamples() // on + if (oversample == nullptr) + return 0.0f; + + return onOff->load() == 1.0f ? oversample->getLatencyInSamples() // on : 0.0f; // off } diff --git a/Plugin/Source/Processors/Compression/CompressionProcessor.h b/Plugin/Source/Processors/Compression/CompressionProcessor.h @@ -11,7 +11,7 @@ public: static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepare (double sr, int samplesPerBlock); + void prepare (double sr, int samplesPerBlock, int numChannels); void processBlock (AudioBuffer<float>& buffer); float getLatencySamples() const noexcept; @@ -22,15 +22,15 @@ private: std::atomic<float>* attackParam = nullptr; std::atomic<float>* releaseParam = nullptr; - chowdsp::LevelDetector<float> slewLimiter[2]; + std::vector<chowdsp::LevelDetector<float>> slewLimiter; BypassProcessor bypass; - dsp::Oversampling<float> oversample { 2, 1, dsp::Oversampling<float>::filterHalfBandPolyphaseIIR, true, true }; + std::unique_ptr<dsp::Oversampling<float>> oversample; - SmoothedValue<float, ValueSmoothingTypes::Linear> dbPlusSmooth[2]; + std::vector<SmoothedValue<float, ValueSmoothingTypes::Linear>> dbPlusSmooth; - std::vector<float, XSIMD_DEFAULT_ALLOCATOR (float)> xDBVec; - std::vector<float, XSIMD_DEFAULT_ALLOCATOR (float)> compGainVec; + std::vector<float, xsimd::aligned_allocator<float>> xDBVec; + std::vector<float, xsimd::aligned_allocator<float>> compGainVec; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CompressionProcessor) }; diff --git a/Plugin/Source/Processors/Degrade/DegradeFilter.h b/Plugin/Source/Processors/Degrade/DegradeFilter.h @@ -8,7 +8,7 @@ class DegradeFilter { public: DegradeFilter() { freq.reset (numSteps); } - ~DegradeFilter() {} + DegradeFilter (DegradeFilter&&) noexcept = default; void reset (float sampleRate, int steps = 0) { diff --git a/Plugin/Source/Processors/Degrade/DegradeNoise.h b/Plugin/Source/Processors/Degrade/DegradeNoise.h @@ -7,8 +7,8 @@ class DegradeNoise { public: - DegradeNoise() {} - ~DegradeNoise() {} + DegradeNoise() = default; + DegradeNoise (DegradeNoise&&) noexcept = default; void setGain (float newGain) { curGain = newGain; } diff --git a/Plugin/Source/Processors/Degrade/DegradeProcessor.cpp b/Plugin/Source/Processors/Degrade/DegradeProcessor.cpp @@ -28,34 +28,36 @@ void DegradeProcessor::cookParams() float freqHz = 200.0f * powf (20000.0f / 200.0f, 1.0f - *amtParam); float gainDB = -24.0f * depthValue; - for (int ch = 0; ch < 2; ++ch) - { - noiseProc[ch].setGain (0.5f * depthValue * *amtParam); - filterProc[ch].setFreq (jmin (freqHz + (*varParam * (freqHz / 0.6f) * (random.nextFloat() - 0.5f)), 0.49f * fs)); - } + for (auto& noise : noiseProc) + noise.setGain (0.5f * depthValue * *amtParam); + + for (auto& filter : filterProc) + filter.setFreq (jmin (freqHz + (*varParam * (freqHz / 0.6f) * (random.nextFloat() - 0.5f)), 0.49f * fs)); auto envSkew = 1.0f - std::pow (envParam->load(), 0.8f); levelDetector.setParameters (10.0f, 20.0f * std::pow (5000.0f / 20.0f, envSkew)); gainProc.setGain (Decibels::decibelsToGain (jmin (gainDB + (*varParam * 36.0f * (random.nextFloat() - 0.5f)), 3.0f))); } -void DegradeProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +void DegradeProcessor::prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels) { fs = (float) sampleRate; cookParams(); - for (int ch = 0; ch < 2; ++ch) - { - noiseProc[ch].prepare(); - filterProc[ch].reset ((float) sampleRate, 20); - } + noiseProc.resize ((size_t) numChannels); + for (auto& noise : noiseProc) + noise.prepare(); + + filterProc.resize ((size_t) numChannels); + for (auto& filter : filterProc) + filter.reset ((float) sampleRate, 20); - noiseBuffer.setSize (2, samplesPerBlock); + noiseBuffer.setSize (numChannels, samplesPerBlock); levelBuffer.setSize (1, samplesPerBlock); - levelDetector.prepare ({ sampleRate, (uint32) samplesPerBlock, 2 }); + levelDetector.prepare ({ sampleRate, (uint32) samplesPerBlock, (uint32) numChannels }); gainProc.prepareToPlay (sampleRate, samplesPerBlock); - bypass.prepare (samplesPerBlock, bypass.toBool (onOffParam)); + bypass.prepare (samplesPerBlock, numChannels, bypass.toBool (onOffParam)); } void DegradeProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi) @@ -73,20 +75,21 @@ void DegradeProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& mid dsp::AudioBlock<float> levelBlock (levelBuffer.getArrayOfWritePointers(), 1, numSamples); dsp::ProcessContextNonReplacing<float> levelContext (block, levelBlock); levelDetector.process (levelContext); - auto* levelPtr = levelBuffer.getReadPointer (0); + const auto* levelPtr = levelBuffer.getReadPointer (0); + const auto applyEnvelope = envParam->load() > 0.0f; for (int ch = 0; ch < numChannels; ++ch) { auto* noisePtr = noiseBuffer.getWritePointer (ch); - noiseProc[ch].processBlock (noisePtr, numSamples); + noiseProc[(size_t) ch].processBlock (noisePtr, numSamples); - if (envParam->load() > 0.0f) + if (applyEnvelope) FloatVectorOperations::multiply (noisePtr, levelPtr, numSamples); auto* xPtr = buffer.getWritePointer (ch); FloatVectorOperations::add (xPtr, noisePtr, numSamples); - filterProc[ch].process (xPtr, numSamples); + filterProc[(size_t) ch].process (xPtr, numSamples); } gainProc.processBlock (buffer, midi); diff --git a/Plugin/Source/Processors/Degrade/DegradeProcessor.h b/Plugin/Source/Processors/Degrade/DegradeProcessor.h @@ -14,7 +14,7 @@ public: static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); void cookParams(); - void prepareToPlay (double sampleRate, int samplesPerBlock); + void prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels); void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi); private: @@ -25,11 +25,11 @@ private: std::atomic<float>* varParam = nullptr; std::atomic<float>* envParam = nullptr; - DegradeFilter filterProc[2]; + std::vector<DegradeFilter> filterProc; GainProcessor gainProc; AudioBuffer<float> noiseBuffer; - DegradeNoise noiseProc[2]; + std::vector<DegradeNoise> noiseProc; AudioBuffer<float> levelBuffer; chowdsp::LevelDetector<float> levelDetector; diff --git a/Plugin/Source/Processors/Hysteresis/DCBlocker.h b/Plugin/Source/Processors/Hysteresis/DCBlocker.h @@ -8,6 +8,8 @@ class DCBlocker { public: DCBlocker() = default; + DCBlocker (DCBlocker&&) noexcept = default; + DCBlocker& operator= (DCBlocker&&) noexcept = default; /** Prepare the DC blocker to process samples * diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp @@ -23,13 +23,13 @@ void HysteresisProcessing::setSampleRate (double newSR) hysteresisSTN.prepare (newSR); } -void HysteresisProcessing::cook (float drive, float width, float sat, bool v1) +void HysteresisProcessing::cook (double drive, double width, double sat, bool v1) { - hysteresisSTN.setParams (sat, width); + hysteresisSTN.setParams ((float) sat, (float) width); - hpState.M_s = 0.5 + 1.5 * (1.0 - (double) sat); - hpState.a = hpState.M_s / (0.01 + 6.0 * (double) drive); - hpState.c = std::sqrt (1.0f - (double) width) - 0.01; + hpState.M_s = 0.5 + 1.5 * (1.0 - sat); + hpState.a = hpState.M_s / (0.01 + 6.0 * drive); + hpState.c = std::sqrt (1.0f - width) - 0.01; hpState.k = 0.47875; upperLim = 20.0; @@ -38,7 +38,7 @@ void HysteresisProcessing::cook (float drive, float width, float sat, bool v1) hpState.k = 27.0e3; hpState.c = 1.7e-1; hpState.M_s *= 50000.0; - hpState.a = hpState.M_s / (0.01 + 40.0 * (double) drive); + hpState.a = hpState.M_s / (0.01 + 40.0 * drive); upperLim = 100000.0; } diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.h @@ -23,11 +23,12 @@ class HysteresisProcessing { public: HysteresisProcessing(); + HysteresisProcessing (HysteresisProcessing&&) noexcept = default; void reset(); void setSampleRate (double newSR); - void cook (float drive, float width, float sat, bool v1); + void cook (double drive, double width, double sat, bool v1); /* Process a single sample */ template <SolverType solver, typename Float> diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp @@ -49,15 +49,6 @@ HysteresisProcessor::HysteresisProcessor (AudioProcessorValueTreeState& vts) : o widthParam = vts.getRawParameterValue ("width"); modeParam = vts.getRawParameterValue ("mode"); onOffParam = vts.getRawParameterValue ("hyst_onoff"); - - for (int ch = 0; ch < 2; ++ch) - { - drive[ch].reset (numSteps); - width[ch].reset (numSteps); - sat[ch].reset (numSteps); - } - - makeup.reset (numSteps); } void HysteresisProcessor::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) @@ -107,29 +98,27 @@ double HysteresisProcessor::calcMakeup() void HysteresisProcessor::setDrive (float newDrive) { - for (int ch = 0; ch < 2; ++ch) - { - drive[ch].setTargetValue ((double) newDrive); - } + for (auto& driveVal : drive) + driveVal.setTargetValue ((double) newDrive); } void HysteresisProcessor::setWidth (float newWidth) { - for (int ch = 0; ch < 2; ++ch) - width[ch].setTargetValue ((double) newWidth); + for (auto& widthVal : width) + widthVal.setTargetValue ((double) newWidth); } void HysteresisProcessor::setSaturation (float newSaturation) { - for (int ch = 0; ch < 2; ++ch) - sat[ch].setTargetValue ((double) newSaturation); + for (auto& satVal : sat) + satVal.setTargetValue ((double) newSaturation); } void HysteresisProcessor::setOversampling() { if (osManager.updateOSFactor()) { - for (int ch = 0; ch < 2; ++ch) + for (size_t ch = 0; ch < hProcs.size(); ++ch) { hProcs[ch].setSampleRate (fs * osManager.getOSFactor()); hProcs[ch].cook (drive[ch].getCurrentValue(), width[ch].getCurrentValue(), sat[ch].getCurrentValue(), wasV1); @@ -145,39 +134,52 @@ void HysteresisProcessor::calcBiasFreq() biasFreq = fs * osManager.getOSFactor() / 2.0; } -void HysteresisProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +void HysteresisProcessor::prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels) { fs = sampleRate; wasV1 = useV1; + + osManager.prepareToPlay (sampleRate, samplesPerBlock, numChannels); calcBiasFreq(); - for (int ch = 0; ch < 2; ++ch) - { - drive[ch].skip (numSteps); - width[ch].skip (numSteps); - sat[ch].skip (numSteps); + drive.resize ((size_t) numChannels); + for (auto& val : drive) + val.reset (numSteps); + width.resize ((size_t) numChannels); + for (auto& val : width) + val.reset (numSteps); + + sat.resize ((size_t) numChannels); + for (auto& val : sat) + val.reset (numSteps); + + hProcs.resize ((size_t) numChannels); + for (size_t ch = 0; ch < (size_t) numChannels; ++ch) + { hProcs[ch].setSampleRate (sampleRate * osManager.getOSFactor()); hProcs[ch].cook (drive[ch].getCurrentValue(), width[ch].getCurrentValue(), sat[ch].getCurrentValue(), wasV1); hProcs[ch].reset(); - - biasAngle[ch] = 0.0; } - makeup.skip (numSteps); + biasAngle.resize ((size_t) numChannels, 0.0); + makeup.reset (numSteps); - osManager.prepareToPlay (sampleRate, samplesPerBlock); - for (int ch = 0; ch < 2; ++ch) - dcBlocker[ch].prepare (sampleRate, dcFreq); + dcBlocker.resize ((size_t) numChannels); + for (auto& filt : dcBlocker) + filt.prepare (sampleRate, dcFreq); - doubleBuffer.setSize (2, samplesPerBlock); - bypass.prepare (samplesPerBlock, bypass.toBool (onOffParam)); + doubleBuffer.setSize (numChannels, samplesPerBlock); + bypass.prepare (samplesPerBlock, numChannels, bypass.toBool (onOffParam)); #if HYSTERESIS_USE_SIMD const auto maxOSBlockSize = (uint32) samplesPerBlock * 16; - interleaved = dsp::AudioBlock<Vec2> (interleavedBlockData, 1, maxOSBlockSize); - zero = dsp::AudioBlock<double> (zeroData, Vec2::size(), maxOSBlockSize); - zero.clear(); + const auto numVecChannels = chowdsp::ceiling_divide ((size_t) numChannels, Vec2::size()); + interleavedBlock = dsp::AudioBlock<Vec2> (interleavedBlockData, numVecChannels, maxOSBlockSize); + zeroBlock = dsp::AudioBlock<double> (zeroData, Vec2::size(), maxOSBlockSize); + zeroBlock.clear(); + + channelPointers.resize (numVecChannels * Vec2::size()); #endif } @@ -195,6 +197,8 @@ float HysteresisProcessor::getLatencySamples() const noexcept void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midi*/) { + const auto numChannels = buffer.getNumChannels(); + if (! bypass.processBlockIn (buffer, bypass.toBool (onOffParam))) return; @@ -209,14 +213,14 @@ void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& if (useV1 != wasV1) { - for (int ch = 0; ch < 2; ++ch) - hProcs[ch].reset(); + for (auto& hProc : hProcs) + hProc.reset(); } wasV1 = useV1; // clip input to avoid unstable hysteresis - for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + for (int ch = 0; ch < numChannels; ++ch) { auto* bufferPtr = buffer.getWritePointer (ch); FloatVectorOperations::clip (bufferPtr, @@ -233,16 +237,21 @@ void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& #if HYSTERESIS_USE_SIMD const auto n = osBlock.getNumSamples(); - auto* inout = channelPointers.getData(); - for (size_t ch = 0; ch < Vec2::size(); ++ch) - inout[ch] = (ch < osBlock.getNumChannels() ? const_cast<double*> (osBlock.getChannelPointer (ch)) : zero.getChannelPointer (ch)); + auto* inout = channelPointers.data(); + const auto numChannelsPadded = channelPointers.size(); + for (size_t ch = 0; ch < numChannelsPadded; ++ch) + inout[ch] = (ch < osBlock.getNumChannels() ? const_cast<double*> (osBlock.getChannelPointer (ch)) : zeroBlock.getChannelPointer (ch % Vec2::size())); // interleave channels - interleaveSamples (inout, reinterpret_cast<double*> (interleaved.getChannelPointer (0)), static_cast<int> (n), static_cast<int> (Vec2::size())); + for (size_t ch = 0; ch < numChannelsPadded; ch += Vec2::size()) + { + auto* simdBlockData = reinterpret_cast<double*> (interleavedBlock.getChannelPointer (ch / Vec2::size())); + interleaveSamples (&inout[ch], simdBlockData, static_cast<int> (n), static_cast<int> (Vec2::size())); + } - auto processBlock = interleaved.getSubBlock (0, n); + auto&& processBlock = interleavedBlock.getSubBlock (0, n); #else - auto processBlock = osBlock; + auto&& processBlock = osBlock; #endif if (useV1) @@ -293,10 +302,14 @@ void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& #if HYSTERESIS_USE_SIMD // de-interleave channels - deinterleaveSamples (reinterpret_cast<double*> (interleaved.getChannelPointer (0)), - const_cast<double**> (inout), - static_cast<int> (n), - static_cast<int> (Vec2::size())); + for (size_t ch = 0; ch < numChannelsPadded; ch += Vec2::size()) + { + auto* simdBlockData = reinterpret_cast<double*> (interleavedBlock.getChannelPointer (ch / Vec2::size())); + deinterleaveSamples (simdBlockData, + const_cast<double**> (&inout[ch]), + static_cast<int> (n), + static_cast<int> (Vec2::size())); + } #endif osManager.processSamplesDown (block); @@ -310,7 +323,7 @@ void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& template <typename T, typename SmoothType> void applyMakeup (dsp::AudioBlock<T>& block, SmoothType& makeup) { -#if 1 // HYSTERESIS_USE_SIMD +#if HYSTERESIS_USE_SIMD const auto numSamples = block.getNumSamples(); const auto numChannels = block.getNumChannels(); @@ -342,11 +355,15 @@ void applyMakeup (dsp::AudioBlock<T>& block, SmoothType& makeup) template <SolverType solverType, typename T> void HysteresisProcessor::process (dsp::AudioBlock<T>& block) { - for (size_t channel = 0; channel < block.getNumChannels(); ++channel) + const auto numChannels = block.getNumChannels(); + const auto numSamples = block.getNumSamples(); + + for (size_t channel = 0; channel < numChannels; ++channel) { auto* x = block.getChannelPointer (channel); - for (size_t samp = 0; samp < block.getNumSamples(); samp++) - x[samp] = hProcs[channel].process<solverType> (x[samp]); + auto& hProc = hProcs[channel]; + for (size_t samp = 0; samp < numSamples; samp++) + x[samp] = hProc.process<solverType> (x[samp]); } applyMakeup (block, makeup); @@ -355,13 +372,17 @@ void HysteresisProcessor::process (dsp::AudioBlock<T>& block) template <SolverType solverType, typename T> void HysteresisProcessor::processSmooth (dsp::AudioBlock<T>& block) { - for (size_t channel = 0; channel < block.getNumChannels(); ++channel) + const auto numChannels = block.getNumChannels(); + const auto numSamples = block.getNumSamples(); + + for (size_t channel = 0; channel < numChannels; ++channel) { auto* x = block.getChannelPointer (channel); - for (size_t samp = 0; samp < block.getNumSamples(); samp++) + auto& hProc = hProcs[channel]; + for (size_t samp = 0; samp < numSamples; samp++) { - hProcs[channel].cook (drive[channel].getNextValue(), width[channel].getNextValue(), sat[channel].getNextValue(), false); - x[samp] = hProcs[channel].process<solverType> (x[samp]); + hProc.cook (drive[channel].getNextValue(), width[channel].getNextValue(), sat[channel].getNextValue(), false); + x[samp] = hProc.process<solverType> (x[samp]); } } @@ -371,18 +392,23 @@ void HysteresisProcessor::processSmooth (dsp::AudioBlock<T>& block) template <typename T> void HysteresisProcessor::processV1 (dsp::AudioBlock<T>& block) { + const auto numChannels = block.getNumChannels(); + const auto numSamples = block.getNumSamples(); const auto angleDelta = MathConstants<double>::twoPi * biasFreq / (fs * osManager.getOSFactor()); - for (size_t channel = 0; channel < block.getNumChannels(); ++channel) + for (size_t channel = 0; channel < numChannels; ++channel) { auto* x = block.getChannelPointer (channel); - for (size_t samp = 0; samp < block.getNumSamples(); samp++) + auto& hProc = hProcs[channel]; + auto& bAngle = biasAngle[channel]; + const auto bAngleMult = biasGain * (1.0 - width[channel].getCurrentValue()); + for (size_t samp = 0; samp < numSamples; samp++) { - auto bias = biasGain * (1.0 - width[channel].getCurrentValue()) * std::sin (biasAngle[channel]); - biasAngle[channel] += angleDelta; - biasAngle[channel] -= MathConstants<double>::twoPi * (biasAngle[channel] >= MathConstants<double>::twoPi); + auto bias = bAngleMult * std::sin (bAngle); + bAngle += angleDelta; + bAngle -= MathConstants<double>::twoPi * (bAngle >= MathConstants<double>::twoPi); - x[samp] = hProcs[channel].process<RK4> ((x[samp] + bias) * 10000.0) * v1Norm; + x[samp] = hProc.process<RK4> ((x[samp] + bias) * 10000.0) * v1Norm; } } } @@ -390,20 +416,24 @@ void HysteresisProcessor::processV1 (dsp::AudioBlock<T>& block) template <typename T> void HysteresisProcessor::processSmoothV1 (dsp::AudioBlock<T>& block) { + const auto numChannels = block.getNumChannels(); + const auto numSamples = block.getNumSamples(); const auto angleDelta = MathConstants<double>::twoPi * biasFreq / (fs * osManager.getOSFactor()); - for (size_t channel = 0; channel < block.getNumChannels(); ++channel) + for (size_t channel = 0; channel < numChannels; ++channel) { auto* x = block.getChannelPointer (channel); - for (size_t samp = 0; samp < block.getNumSamples(); samp++) + auto& hProc = hProcs[channel]; + auto& bAngle = biasAngle[channel]; + for (size_t samp = 0; samp < numSamples; samp++) { - hProcs[channel].cook (drive[channel].getNextValue(), width[channel].getNextValue(), sat[channel].getNextValue(), true); + hProc.cook (drive[channel].getNextValue(), width[channel].getNextValue(), sat[channel].getNextValue(), true); - auto bias = biasGain * (1.0 - width[channel].getCurrentValue()) * std::sin (biasAngle[channel]); - biasAngle[channel] += angleDelta; - biasAngle[channel] -= MathConstants<double>::twoPi * (biasAngle[channel] >= MathConstants<double>::twoPi); + auto bias = biasGain * (1.0 - width[channel].getCurrentValue()) * std::sin (bAngle); + bAngle += angleDelta; + bAngle -= MathConstants<double>::twoPi * (bAngle >= MathConstants<double>::twoPi); - x[samp] = hProcs[channel].process<RK4> ((x[samp] + bias) * 10000.0) * v1Norm; + x[samp] = hProc.process<RK4> ((x[samp] + bias) * 10000.0) * v1Norm; } } } @@ -411,5 +441,5 @@ void HysteresisProcessor::processSmoothV1 (dsp::AudioBlock<T>& block) void HysteresisProcessor::applyDCBlockers (AudioBuffer<float>& buffer) { for (int ch = 0; ch < buffer.getNumChannels(); ++ch) - dcBlocker[ch].processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); + dcBlocker[(size_t) ch].processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); } diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h @@ -12,7 +12,7 @@ public: HysteresisProcessor (AudioProcessorValueTreeState& vts); /* Reset fade buffers, filters, and processors. Prepare oversampling */ - void prepareToPlay (double sampleRate, int samplesPerBlock); + void prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels); /* Reset oversampling */ void releaseResources(); @@ -50,22 +50,22 @@ private: std::atomic<float>* modeParam = nullptr; std::atomic<float>* onOffParam = nullptr; - SmoothedValue<double, ValueSmoothingTypes::Linear> drive[2]; - SmoothedValue<double, ValueSmoothingTypes::Linear> width[2]; - SmoothedValue<double, ValueSmoothingTypes::Linear> sat[2]; + std::vector<SmoothedValue<double, ValueSmoothingTypes::Linear>> drive; + std::vector<SmoothedValue<double, ValueSmoothingTypes::Linear>> width; + std::vector<SmoothedValue<double, ValueSmoothingTypes::Linear>> sat; SmoothedValue<double, ValueSmoothingTypes::Multiplicative> makeup; double fs = 44100.0f; - HysteresisProcessing hProcs[2]; - SolverType solver = SolverType::RK4; chowdsp::VariableOversampling<double> osManager; // needs oversampling to avoid aliasing - DCBlocker dcBlocker[2]; + std::vector<HysteresisProcessing> hProcs; + SolverType solver = SolverType::RK4; + std::vector<DCBlocker> dcBlocker; static constexpr double dcFreq = 35.0; double biasGain = 10.0; double biasFreq = 48000.0; - double biasAngle[2] = { 0.0, 0.0 }; + std::vector<double> biasAngle; bool wasV1 = false, useV1 = false; double clipLevel = 20.0; @@ -74,11 +74,11 @@ private: #if HYSTERESIS_USE_SIMD using Vec2 = dsp::SIMDRegister<double>; - dsp::AudioBlock<Vec2> interleaved; - dsp::AudioBlock<double> zero; + dsp::AudioBlock<Vec2> interleavedBlock; + dsp::AudioBlock<double> zeroBlock; HeapBlock<char> interleavedBlockData, zeroData; - HeapBlock<const double*> channelPointers { Vec2::size() }; + std::vector<const double*> channelPointers; #endif JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HysteresisProcessor) diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisSTN.h b/Plugin/Source/Processors/Hysteresis/HysteresisSTN.h @@ -16,6 +16,7 @@ class HysteresisSTN { public: HysteresisSTN(); + HysteresisSTN (HysteresisSTN&&) noexcept = default; static constexpr size_t inputSize = 5; static constexpr double diffMakeup = 1.0 / 6.0e4; diff --git a/Plugin/Source/Processors/Hysteresis/STNModel.h b/Plugin/Source/Processors/Hysteresis/STNModel.h @@ -12,6 +12,7 @@ class STNModel { public: STNModel(); + STNModel (STNModel&&) noexcept = default; inline double forward (const double* input) noexcept { diff --git a/Plugin/Source/Processors/Hysteresis/ToneControl.cpp b/Plugin/Source/Processors/Hysteresis/ToneControl.cpp @@ -6,43 +6,40 @@ constexpr double slewTime = 0.05; constexpr float transFreq = 500.0f; } // namespace -ToneStage::ToneStage() -{ - for (int ch = 0; ch < 2; ++ch) - { - lowGain[ch] = 1.0f; - highGain[ch] = 1.0f; - tFreq[ch] = transFreq; - } -} +ToneStage::ToneStage() = default; -void ToneStage::prepare (double sampleRate) +void ToneStage::prepare (double sampleRate, int numChannels) { fs = (float) sampleRate; - for (int ch = 0; ch < 2; ++ch) + tone.resize ((size_t) numChannels); + lowGain.resize ((size_t) numChannels); + highGain.resize ((size_t) numChannels); + tFreq.resize ((size_t) numChannels); + + for (size_t ch = 0; ch < (size_t) numChannels; ++ch) { - auto resetSmoothValue = [sampleRate] (SmoothGain& value) { + auto resetSmoothValue = [sampleRate] (SmoothGain& value, float startValue) { value.reset (sampleRate, slewTime); - value.setCurrentAndTargetValue (value.getTargetValue()); + value.setCurrentAndTargetValue (startValue); }; - resetSmoothValue (lowGain[ch]); - resetSmoothValue (highGain[ch]); - resetSmoothValue (tFreq[ch]); + resetSmoothValue (lowGain[ch], 1.0f); + resetSmoothValue (highGain[ch], 1.0f); + resetSmoothValue (tFreq[ch], transFreq); tone[ch].reset(); tone[ch].calcCoefs (lowGain[ch].getTargetValue(), highGain[ch].getTargetValue(), tFreq[ch].getTargetValue(), fs); } } -void setSmoothValues (SmoothGain values[2], float newValue) +void setSmoothValues (std::vector<SmoothGain>& values, float newValue) { if (newValue == values[0].getTargetValue()) return; - values[0].setTargetValue (newValue); - values[1].setTargetValue (newValue); + for (auto& smoothedVal : values) + smoothedVal.setTargetValue (newValue); } void ToneStage::setLowGain (float lowGainDB) { setSmoothValues (lowGain, Decibels::decibelsToGain (lowGainDB)); } @@ -51,20 +48,23 @@ void ToneStage::setTransFreq (float newTFreq) { setSmoothValues (tFreq, newTFreq void ToneStage::processBlock (AudioBuffer<float>& buffer) { - for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + const auto numChannels = buffer.getNumChannels(); + const auto numSamples = buffer.getNumSamples(); + + for (size_t ch = 0; ch < (size_t) numChannels; ++ch) { + auto* data = buffer.getWritePointer ((int) ch); if (lowGain[ch].isSmoothing() || highGain[ch].isSmoothing() || tFreq[ch].isSmoothing()) { - auto* x = buffer.getWritePointer (ch); - for (int n = 0; n < buffer.getNumSamples(); ++n) + for (int n = 0; n < numSamples; ++n) { tone[ch].calcCoefs (lowGain[ch].getNextValue(), highGain[ch].getNextValue(), tFreq[ch].getNextValue(), fs); - x[n] = tone[ch].processSample (x[n]); + data[n] = tone[ch].processSample (data[n]); } } else { - tone[ch].processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); + tone[ch].processBlock (data, numSamples); } } } @@ -97,10 +97,10 @@ void ToneControl::createParameterLayout (std::vector<std::unique_ptr<RangedAudio })); } -void ToneControl::prepare (double sampleRate) +void ToneControl::prepare (double sampleRate, int numChannels) { - toneIn.prepare (sampleRate); - toneOut.prepare (sampleRate); + toneIn.prepare (sampleRate, numChannels); + toneOut.prepare (sampleRate, numChannels); } void ToneControl::processBlockIn (AudioBuffer<float>& buffer) diff --git a/Plugin/Source/Processors/Hysteresis/ToneControl.h b/Plugin/Source/Processors/Hysteresis/ToneControl.h @@ -7,14 +7,13 @@ using SmoothGain = SmoothedValue<float, ValueSmoothingTypes::Multiplicative>; struct ToneStage { - // ToneFilter tone[2]; - chowdsp::ShelfFilter<float> tone[2]; - SmoothGain lowGain[2], highGain[2], tFreq[2]; + std::vector<chowdsp::ShelfFilter<float>> tone; + std::vector<SmoothGain> lowGain, highGain, tFreq; float fs = 44100.0f; ToneStage(); - void prepare (double sampleRate); + void prepare (double sampleRate, int numChannels); void processBlock (AudioBuffer<float>& buffer); void setLowGain (float lowGainDB); void setHighGain (float highGainDB); @@ -27,7 +26,7 @@ public: ToneControl (AudioProcessorValueTreeState& vts); static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepare (double sampleRate); + void prepare (double sampleRate, int numChannels); void setDBScale (float newDBScale) { dbScale = newDBScale; }; void processBlockIn (AudioBuffer<float>& buffer); diff --git a/Plugin/Source/Processors/Input_Filters/InputFilters.cpp b/Plugin/Source/Processors/Input_Filters/InputFilters.cpp @@ -46,20 +46,20 @@ void InputFilters::createParameterLayout (std::vector<std::unique_ptr<RangedAudi params.push_back (std::make_unique<AudioParameterBool> ("ifilt_makeup", "Input Cut Makeup", false)); } -void InputFilters::prepareToPlay (double sampleRate, int samplesPerBlock) +void InputFilters::prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels) { fs = (float) sampleRate; - dsp::ProcessSpec spec { sampleRate, (uint32) samplesPerBlock, 2 }; + dsp::ProcessSpec spec { sampleRate, (uint32) samplesPerBlock, (uint32) numChannels }; lowCutFilter.prepare (spec); highCutFilter.prepare (spec); makeupDelay.prepare (spec); - lowCutBuffer.setSize (2, samplesPerBlock); - highCutBuffer.setSize (2, samplesPerBlock); - makeupBuffer.setSize (2, samplesPerBlock); + lowCutBuffer.setSize (numChannels, samplesPerBlock); + highCutBuffer.setSize (numChannels, samplesPerBlock); + makeupBuffer.setSize (numChannels, samplesPerBlock); - bypass.prepare (samplesPerBlock, bypass.toBool (onOffParam)); - makeupBypass.prepare (samplesPerBlock, bypass.toBool (onOffParam)); + bypass.prepare (samplesPerBlock, numChannels, bypass.toBool (onOffParam)); + makeupBypass.prepare (samplesPerBlock, numChannels, bypass.toBool (onOffParam)); } void InputFilters::processBlock (AudioBuffer<float>& buffer) @@ -101,7 +101,7 @@ void InputFilters::processBlockMakeup (AudioBuffer<float>& buffer) } // compile makeup signal - makeupBuffer.setSize (2, buffer.getNumSamples(), false, false, true); + makeupBuffer.setSize (buffer.getNumChannels(), buffer.getNumSamples(), false, false, true); dsp::AudioBlock<float> lowCutBlock (lowCutBuffer); dsp::AudioBlock<float> highCutBlock (highCutBuffer); dsp::AudioBlock<float> makeupBlock (makeupBuffer); diff --git a/Plugin/Source/Processors/Input_Filters/InputFilters.h b/Plugin/Source/Processors/Input_Filters/InputFilters.h @@ -10,7 +10,7 @@ public: InputFilters (AudioProcessorValueTreeState& vts); static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepareToPlay (double sampleRate, int samplesPerBlock); + void prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels); void setMakeupDelay (float newDelaySamples) { makeupDelay.setDelay (newDelaySamples); } void processBlock (AudioBuffer<float>& buffer); @@ -23,8 +23,8 @@ private: std::atomic<float>* makeupParam = nullptr; float fs = 44100.0f; - LinkwitzRileyFilter<float, 2> lowCutFilter; - LinkwitzRileyFilter<float, 2> highCutFilter; + LinkwitzRileyFilter<float> lowCutFilter; + LinkwitzRileyFilter<float> highCutFilter; dsp::DelayLine<float, dsp::DelayLineInterpolationTypes::Lagrange3rd> makeupDelay { 1 << 21 }; AudioBuffer<float> lowCutBuffer, highCutBuffer, makeupBuffer; diff --git a/Plugin/Source/Processors/Input_Filters/LinkwitzRileyFilter.h b/Plugin/Source/Processors/Input_Filters/LinkwitzRileyFilter.h @@ -4,7 +4,7 @@ #include <JuceHeader.h> /** 4th-order L-R Filter */ -template <typename SampleType, size_t N_chan> +template <typename SampleType> class LinkwitzRileyFilter { public: @@ -26,7 +26,8 @@ public: { jassert (spec.sampleRate > 0); jassert (spec.numChannels > 0); - jassert (spec.numChannels == N_chan); + + state.resize (spec.numChannels, {}); sampleRate = spec.sampleRate; update(); @@ -89,7 +90,7 @@ private: SampleType g, h; static constexpr SampleType R2 = static_cast<SampleType> (1.41421356237); - std::array<std::array<SampleType, 4>, N_chan> state; + std::vector<std::array<SampleType, 4>> state; double sampleRate = 44100.0; SampleType cutoffFrequency = 2000.0; diff --git a/Plugin/Source/Processors/Loss_Effects/FIRFilter.h b/Plugin/Source/Processors/Loss_Effects/FIRFilter.h @@ -23,6 +23,8 @@ public: z.resize (2 * order); } + FIRFilter (FIRFilter&&) noexcept = default; + void reset() { zPtr = 0; diff --git a/Plugin/Source/Processors/Loss_Effects/LossFilter.cpp b/Plugin/Source/Processors/Loss_Effects/LossFilter.cpp @@ -8,14 +8,6 @@ LossFilter::LossFilter (AudioProcessorValueTreeState& vts, int order) : order (o gap = vts.getRawParameterValue ("gap"); onOff = vts.getRawParameterValue ("loss_onoff"); azimuth = vts.getRawParameterValue ("azimuth"); - - for (int ch = 0; ch < 2; ++ch) - { - filters[ch].add (new FIRFilter (order)); - filters[ch].add (new FIRFilter (order)); - } - - currentCoefs.resize (order); } void LossFilter::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) @@ -56,10 +48,10 @@ float LossFilter::getLatencySamples() const noexcept : 0.0f; // off } -void LossFilter::prepare (float sampleRate, int samplesPerBlock) +void LossFilter::prepare (float sampleRate, int samplesPerBlock, int numChannels) { fs = sampleRate; - fadeBuffer.setSize (2, samplesPerBlock); + fadeBuffer.setSize (numChannels, samplesPerBlock); fadeLength = jmax (1024, samplesPerBlock); fsFactor = (float) fs / 44100.0f; @@ -67,21 +59,19 @@ void LossFilter::prepare (float sampleRate, int samplesPerBlock) currentCoefs.resize (curOrder); Hcoefs.resize (curOrder); - bumpFilter[0].prepare ({ (double) sampleRate, (uint32) samplesPerBlock, 2 }); - bumpFilter[1].prepare ({ (double) sampleRate, (uint32) samplesPerBlock, 2 }); + bumpFilter[0].prepare ({ (double) sampleRate, (uint32) samplesPerBlock, (uint32) numChannels }); + bumpFilter[1].prepare ({ (double) sampleRate, (uint32) samplesPerBlock, (uint32) numChannels }); calcCoefs (bumpFilter[activeFilter]); - for (int ch = 0; ch < 2; ++ch) + for (auto& filter : filters) { - filters[ch].clear(); - filters[ch].add (new FIRFilter (curOrder)); - filters[ch].add (new FIRFilter (curOrder)); - - filters[ch][0]->reset(); - filters[ch][1]->reset(); - - filters[ch][0]->setCoefs (currentCoefs.getRawDataPointer()); - filters[ch][1]->setCoefs (currentCoefs.getRawDataPointer()); + filter.clear(); + for (size_t ch = 0; ch < (size_t) numChannels; ++ch) + { + filter.emplace_back (curOrder); + filter[ch].reset(); + filter[ch].setCoefs (currentCoefs.getRawDataPointer()); + } } prevSpeed = *speed; @@ -90,17 +80,17 @@ void LossFilter::prepare (float sampleRate, int samplesPerBlock) prevGap = *gap; azimuthProc.prepare (sampleRate, samplesPerBlock); - bypass.prepare (samplesPerBlock, bypass.toBool (onOff)); + bypass.prepare (samplesPerBlock, numChannels, bypass.toBool (onOff)); } -void LossFilter::calcHeadBumpFilter (float speedIps, float gapMeters, double fs, StereoIIR& filter) +void LossFilter::calcHeadBumpFilter (float speedIps, float gapMeters, double fs, MultiChannelIIR& filter) { auto bumpFreq = speedIps * 0.0254f / (gapMeters * 500.0f); auto gain = jmax (1.5f * (1000.0f - std::abs (bumpFreq - 100.0f)) / 1000.0f, 1.0f); *filter.state = *dsp::IIR::Coefficients<float>::makePeakFilter (fs, bumpFreq, 2.0f, gain); } -void LossFilter::calcCoefs (StereoIIR& filter) +void LossFilter::calcCoefs (MultiChannelIIR& filter) { // Set freq domain multipliers binWidth = fs / (float) curOrder; @@ -145,8 +135,9 @@ void LossFilter::processBlock (AudioBuffer<float>& buffer) if ((*speed != prevSpeed || *spacing != prevSpacing || *thickness != prevThickness || *gap != prevGap) && fadeCount == 0) { calcCoefs (bumpFilter[! activeFilter]); - for (int ch = 0; ch < numChannels; ++ch) - filters[ch][! activeFilter]->setCoefs (currentCoefs.getRawDataPointer()); + for (auto& filt : filters[! activeFilter]) + filt.setCoefs (currentCoefs.getRawDataPointer()); + bumpFilter[! activeFilter].reset(); fadeCount = fadeLength; @@ -162,28 +153,26 @@ void LossFilter::processBlock (AudioBuffer<float>& buffer) } else { - for (int ch = 0; ch < numChannels; ++ch) - filters[ch][! activeFilter]->processBypassed (buffer.getReadPointer (ch), numSamples); + for (size_t ch = 0; ch < (size_t) numChannels; ++ch) + filters[! activeFilter][ch].processBypassed (buffer.getReadPointer ((int) ch), numSamples); } // normal processing here... { + dsp::AudioBlock<float> block (buffer); for (int ch = 0; ch < numChannels; ++ch) - filters[ch][activeFilter]->process (buffer.getWritePointer (ch), numSamples); + filters[activeFilter][(size_t) ch].process (buffer.getWritePointer (ch), numSamples); - dsp::AudioBlock<float> block (buffer); - dsp::ProcessContextReplacing<float> ctx (block); - bumpFilter[activeFilter].process (ctx); + bumpFilter[activeFilter].process (dsp::ProcessContextReplacing<float> { block }); } if (fadeCount > 0) { + dsp::AudioBlock<float> fadeBlock (fadeBuffer); for (int ch = 0; ch < numChannels; ++ch) - filters[ch][! activeFilter]->process (fadeBuffer.getWritePointer (ch), numSamples); + filters[! activeFilter][(size_t) ch].process (fadeBuffer.getWritePointer (ch), numSamples); - dsp::AudioBlock<float> block (fadeBuffer); - dsp::ProcessContextReplacing<float> ctx (block); - bumpFilter[! activeFilter].process (ctx); + bumpFilter[! activeFilter].process (dsp::ProcessContextReplacing<float> { fadeBlock }); // fade between buffers auto startGain = (float) fadeCount / (float) fadeLength; diff --git a/Plugin/Source/Processors/Loss_Effects/LossFilter.h b/Plugin/Source/Processors/Loss_Effects/LossFilter.h @@ -13,18 +13,18 @@ public: static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepare (float sampleRate, int samplesPerBlock); + void prepare (float sampleRate, int samplesPerBlock, int numSamples); void processBlock (AudioBuffer<float>& buffer); float getLatencySamples() const noexcept; private: - using StereoIIR = dsp::ProcessorDuplicator<dsp::IIR::Filter<float>, dsp::IIR::Coefficients<float>>; + using MultiChannelIIR = dsp::ProcessorDuplicator<dsp::IIR::Filter<float>, dsp::IIR::Coefficients<float>>; - void calcCoefs (StereoIIR& filter); - static void calcHeadBumpFilter (float speedIps, float gapMeters, double fs, StereoIIR& filter); + void calcCoefs (MultiChannelIIR& filter); + static void calcHeadBumpFilter (float speedIps, float gapMeters, double fs, MultiChannelIIR& filter); - OwnedArray<FIRFilter> filters[2]; - StereoIIR bumpFilter[2]; + std::vector<FIRFilter> filters[2]; + MultiChannelIIR bumpFilter[2]; int activeFilter = 0; int fadeCount = 0; int fadeLength = 1024; diff --git a/Plugin/Source/Processors/MidSide/MidSideProcessor.cpp b/Plugin/Source/Processors/MidSide/MidSideProcessor.cpp @@ -22,10 +22,12 @@ void MidSideProcessor::prepare (double sampleRate) void MidSideProcessor::processInput (AudioBuffer<float>& buffer) { - const auto numSamples = buffer.getNumSamples(); + if (buffer.getNumChannels() != 2) // needs to be stereo! + return; //mid - side encoding logic here - if (curMS && buffer.getNumChannels() != 1) + const auto numSamples = buffer.getNumSamples(); + if (curMS) { 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 @@ -38,7 +40,8 @@ void MidSideProcessor::processInput (AudioBuffer<float>& buffer) void MidSideProcessor::processOutput (AudioBuffer<float>& buffer) { - const auto numSamples = buffer.getNumSamples(); + if (buffer.getNumChannels() != 2) // needs to be stereo! + return; if (prevMS != (*midSideParam == 1.0f) && ! fadeSmooth.isSmoothing()) { @@ -47,7 +50,8 @@ void MidSideProcessor::processOutput (AudioBuffer<float>& buffer) } //mid - side decoding logic here - if (curMS && buffer.getNumChannels() != 1) + const auto numSamples = buffer.getNumSamples(); + if (curMS) { buffer.applyGain (Decibels::decibelsToGain (3.0f)); // undo -3 dB Normalization diff --git a/Plugin/Source/Processors/Timing_Effects/FlutterProcess.cpp b/Plugin/Source/Processors/Timing_Effects/FlutterProcess.cpp @@ -1,37 +1,38 @@ #include "FlutterProcess.h" -void FlutterProcess::prepare (double sampleRate, int samplesPerBlock) +void FlutterProcess::prepare (double sampleRate, int samplesPerBlock, int numChannels) { fs = (float) sampleRate; - for (int ch = 0; ch < 2; ++ch) + depthSlew.resize ((size_t) numChannels); + for (auto& dSlew : depthSlew) { - depthSlew[ch].reset (sampleRate, 0.05); - depthSlew[ch].setCurrentAndTargetValue (depthSlewMin); - - phase1[ch] = 0.0f; - phase2[ch] = 0.0f; - phase3[ch] = 0.0f; + dSlew.reset (sampleRate, 0.05); + dSlew.setCurrentAndTargetValue (depthSlewMin); } + phase1.resize ((size_t) numChannels, 0.0f); + phase2.resize ((size_t) numChannels, 0.0f); + phase3.resize ((size_t) numChannels, 0.0f); + amp1 = -230.0f * 1000.0f / fs; amp2 = -80.0f * 1000.0f / fs; amp3 = -99.0f * 1000.0f / fs; dcOffset = 350.0f * 1000.0f / fs; - flutterBuffer.setSize (2, samplesPerBlock); + flutterBuffer.setSize (numChannels, samplesPerBlock); } -void FlutterProcess::prepareBlock (float curDepth, float flutterFreq, int numSamples) +void FlutterProcess::prepareBlock (float curDepth, float flutterFreq, int numSamples, int numChannels) { - depthSlew[0].setTargetValue (jmax (depthSlewMin, curDepth)); - depthSlew[1].setTargetValue (jmax (depthSlewMin, curDepth)); + for (auto& dSlew : depthSlew) + dSlew.setTargetValue (jmax (depthSlewMin, curDepth)); - angleDelta1 = MathConstants<float>::twoPi * 1.0f * flutterFreq / fs; - angleDelta2 = MathConstants<float>::twoPi * 2.0f * flutterFreq / fs; - angleDelta3 = MathConstants<float>::twoPi * 3.0f * flutterFreq / fs; + angleDelta1 = MathConstants<float>::twoPi * flutterFreq / fs; + angleDelta2 = 2.0f * angleDelta1; + angleDelta3 = 3.0f * angleDelta1; - flutterBuffer.setSize (2, numSamples, false, false, true); + flutterBuffer.setSize (numChannels, numSamples, false, false, true); flutterBuffer.clear(); flutterPtrs = flutterBuffer.getArrayOfWritePointers(); } diff --git a/Plugin/Source/Processors/Timing_Effects/FlutterProcess.h b/Plugin/Source/Processors/Timing_Effects/FlutterProcess.h @@ -8,19 +8,19 @@ class FlutterProcess public: FlutterProcess() = default; - void prepare (double sampleRate, int samplesPerBlock); - void prepareBlock (float curDepth, float flutterFreq, int numSamples); + void prepare (double sampleRate, int samplesPerBlock, int numChannels); + void prepareBlock (float curDepth, float flutterFreq, int numSamples, int numChannels); void plotBuffer (foleys::MagicPlotSource* plot); inline bool shouldTurnOff() const noexcept { return depthSlew[0].getTargetValue() == depthSlewMin; } - inline void updatePhase (int ch) noexcept + inline void updatePhase (size_t ch) noexcept { phase1[ch] += angleDelta1; phase2[ch] += angleDelta2; phase3[ch] += angleDelta3; } - inline std::pair<float, float> getLFO (int n, int ch) noexcept + inline std::pair<float, float> getLFO (int n, size_t ch) noexcept { updatePhase (ch); flutterPtrs[ch][n] = depthSlew[ch].getNextValue() @@ -30,7 +30,7 @@ public: return std::make_pair (flutterPtrs[ch][n], dcOffset); } - inline void boundPhase (int ch) noexcept + inline void boundPhase (size_t ch) noexcept { while (phase1[ch] >= MathConstants<float>::twoPi) phase1[ch] -= MathConstants<float>::twoPi; @@ -41,14 +41,14 @@ public: } private: - float phase1[2] = { 0.0f, 0.0f }; - float phase2[2] = { 0.0f, 0.0f }; - float phase3[2] = { 0.0f, 0.0f }; + std::vector<float> phase1; + std::vector<float> phase2; + std::vector<float> phase3; float amp1 = 0.0f; float amp2 = 0.0f; float amp3 = 0.0f; - SmoothedValue<float, ValueSmoothingTypes::Multiplicative> depthSlew[2]; + std::vector<SmoothedValue<float, ValueSmoothingTypes::Multiplicative>> depthSlew; float angleDelta1 = 0.0f; float angleDelta2 = 0.0f; diff --git a/Plugin/Source/Processors/Timing_Effects/OHProcess.h b/Plugin/Source/Processors/Timing_Effects/OHProcess.h @@ -13,18 +13,20 @@ class OHProcess public: OHProcess() = default; - void prepare (double sampleRate, int samplesPerBlock) + void prepare (double sampleRate, int samplesPerBlock, int numChannels) { - dsp::ProcessSpec spec { sampleRate, (uint32) samplesPerBlock, 1 }; + dsp::ProcessSpec spec { sampleRate, (uint32) samplesPerBlock, (uint32) numChannels }; + dsp::ProcessSpec monoSpec { sampleRate, (uint32) samplesPerBlock, 1 }; noiseGen.setNoiseType (chowdsp::Noise<float>::Normal); noiseGen.setGainLinear (1.0f / 2.33f); - noiseGen.prepare (spec); + noiseGen.prepare (monoSpec); - for (int ch = 0; ch < 2; ++ch) + lpf.resize ((size_t) numChannels); + for (auto& filt : lpf) { - lpf[ch].prepare (spec); - lpf[ch].coefficients = dsp::IIR::Coefficients<float>::makeLowPass (sampleRate, 10.0f); + filt.prepare (spec); + filt.coefficients = dsp::IIR::Coefficients<float>::makeLowPass (sampleRate, 10.0f); } noiseBuffer.setSize (1, samplesPerBlock); @@ -33,8 +35,8 @@ public: sqrtdelta = 1.0f / std::sqrt ((float) sampleRate); T = 1.0f / (float) sampleRate; + y.resize ((size_t) numChannels, 0.0f); y[0] = 1.0f; - y[1] = 0.0f; } void prepareBlock (float amtParam, int numSamples) @@ -51,7 +53,7 @@ public: mean = amtParam; } - inline float process (int n, int ch) noexcept + inline float process (int n, size_t ch) noexcept { y[ch] += sqrtdelta * rPtr[n] * amt; y[ch] += damping * (mean - y[ch]) * T; @@ -61,7 +63,7 @@ public: private: float sqrtdelta = 1.0f / std::sqrt (48000.0f); float T = 1.0f / 48000.0f; - float y[2] = { 0.0f, 0.0f }; + std::vector<float> y; float amt = 0.0f; float mean = 0.0f; @@ -71,7 +73,7 @@ private: AudioBuffer<float> noiseBuffer; const float* rPtr = nullptr; - dsp::IIR::Filter<float> lpf[2]; + std::vector<dsp::IIR::Filter<float>> lpf; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OHProcess) }; diff --git a/Plugin/Source/Processors/Timing_Effects/WowFlutterProcessor.cpp b/Plugin/Source/Processors/Timing_Effects/WowFlutterProcessor.cpp @@ -36,21 +36,20 @@ void WowFlutterProcessor::createParameterLayout (std::vector<std::unique_ptr<Ran params.push_back (std::make_unique<AudioParameterFloat> ("wow_drift", "Wow Drift", 0.0f, 1.0f, 0.0f)); } -void WowFlutterProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +void WowFlutterProcessor::prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels) { fs = (float) sampleRate; - bypass.prepare (samplesPerBlock, bypass.toBool (flutterOnOff)); - wowProcessor.prepare (sampleRate, samplesPerBlock); - flutterProcessor.prepare (sampleRate, samplesPerBlock); + bypass.prepare (samplesPerBlock, numChannels, bypass.toBool (flutterOnOff)); + wowProcessor.prepare (sampleRate, samplesPerBlock, numChannels); + flutterProcessor.prepare (sampleRate, samplesPerBlock, numChannels); - for (int ch = 0; ch < 2; ++ch) - { - delay.prepare ({ sampleRate, (uint32) samplesPerBlock, 2 }); - delay.setDelay (0.0f); + delay.prepare ({ sampleRate, (uint32) samplesPerBlock, (uint32) numChannels }); + delay.setDelay (0.0f); - dcBlocker[ch].prepare (sampleRate, 15.0f); - } + dcBlocker.resize ((size_t) numChannels); + for (auto& filt : dcBlocker) + filt.prepare (sampleRate, 15.0f); wowPlot->prepareToPlay (sampleRate, samplesPerBlock); flutterPlot->prepareToPlay (sampleRate, samplesPerBlock); @@ -60,13 +59,16 @@ void WowFlutterProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& { ScopedNoDenormals noDenormals; + const auto numChannels = buffer.getNumChannels(); + const auto numSamples = buffer.getNumSamples(); + auto curDepthWow = powf (*wowDepth, 3.0f); auto wowFreq = powf (4.5, *wowRate) - 1.0f; - wowProcessor.prepareBlock (curDepthWow, wowFreq, wowVariance->load(), wowDrift->load(), buffer.getNumSamples()); + wowProcessor.prepareBlock (curDepthWow, wowFreq, wowVariance->load(), wowDrift->load(), numSamples, numChannels); auto curDepthFlutter = powf (powf (*flutterDepth, 3.0f) * 81.0f / 625.0f, 0.5f); auto flutterFreq = 0.1f * powf (1000.0f, *flutterRate); - flutterProcessor.prepareBlock (curDepthFlutter, flutterFreq, buffer.getNumSamples()); + flutterProcessor.prepareBlock (curDepthFlutter, flutterFreq, numSamples, numChannels); bool shouldTurnOff = ! bypass.toBool (flutterOnOff) || (wowProcessor.shouldTurnOff() && flutterProcessor.shouldTurnOff()); if (bypass.processBlockIn (buffer, ! shouldTurnOff)) diff --git a/Plugin/Source/Processors/Timing_Effects/WowFlutterProcessor.h b/Plugin/Source/Processors/Timing_Effects/WowFlutterProcessor.h @@ -14,7 +14,7 @@ public: void initialisePlots (foleys::MagicGUIState& magicState); static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepareToPlay (double sampleRate, int samplesPerBlock); + void prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels); void processBlock (AudioBuffer<float>&, MidiBuffer&); private: @@ -42,7 +42,7 @@ private: }; dsp::DelayLine<float, dsp::DelayLineInterpolationTypes::Lagrange3rd> delay { HISTORY_SIZE }; - DCBlocker dcBlocker[2]; + std::vector<DCBlocker> dcBlocker; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WowFlutterProcessor) }; diff --git a/Plugin/Source/Processors/Timing_Effects/WowProcess.cpp b/Plugin/Source/Processors/Timing_Effects/WowProcess.cpp @@ -1,31 +1,38 @@ #include "WowProcess.h" -void WowProcess::prepare (double sampleRate, int samplesPerBlock) +WowProcess::~WowProcess() +{ + depthSlew.clear(); +} + +void WowProcess::prepare (double sampleRate, int samplesPerBlock, int numChannels) { fs = (float) sampleRate; - for (int ch = 0; ch < 2; ++ch) + depthSlew.resize ((size_t) numChannels); + for (auto& dSlew : depthSlew) { - depthSlew[ch].reset (sampleRate, 0.05); - depthSlew[ch].setCurrentAndTargetValue (depthSlewMin); - phase[ch] = 0.0f; + dSlew.reset (sampleRate, 0.05); + dSlew.setCurrentAndTargetValue (depthSlewMin); } + phase.resize ((size_t) numChannels, 0.0f); + amp = 1000.0f * 1000.0f / (float) sampleRate; - wowBuffer.setSize (2, samplesPerBlock); + wowBuffer.setSize (numChannels, samplesPerBlock); - ohProc.prepare (sampleRate, samplesPerBlock); + ohProc.prepare (sampleRate, samplesPerBlock, numChannels); } -void WowProcess::prepareBlock (float curDepth, float wowFreq, float wowVar, float wowDrift, int numSamples) +void WowProcess::prepareBlock (float curDepth, float wowFreq, float wowVar, float wowDrift, int numSamples, int numChannels) { - depthSlew[0].setTargetValue (jmax (depthSlewMin, curDepth)); - depthSlew[1].setTargetValue (jmax (depthSlewMin, curDepth)); + for (auto& dSlew : depthSlew) + dSlew.setTargetValue (jmax (depthSlewMin, curDepth)); auto freqAdjust = wowFreq * (1.0f + std::pow (driftRand.nextFloat(), 1.25f) * wowDrift); angleDelta = MathConstants<float>::twoPi * freqAdjust / fs; - wowBuffer.setSize (2, numSamples, false, false, true); + wowBuffer.setSize (numChannels, numSamples, false, false, true); wowBuffer.clear(); wowPtrs = wowBuffer.getArrayOfWritePointers(); diff --git a/Plugin/Source/Processors/Timing_Effects/WowProcess.h b/Plugin/Source/Processors/Timing_Effects/WowProcess.h @@ -8,15 +8,16 @@ class WowProcess { public: WowProcess() = default; + ~WowProcess(); - void prepare (double sampleRate, int samplesPerBlock); - void prepareBlock (float curDepth, float wowFreq, float wowVar, float wowDrift, int numSamples); + void prepare (double sampleRate, int samplesPerBlock, int numChannels); + void prepareBlock (float curDepth, float wowFreq, float wowVar, float wowDrift, int numSamples, int numChannels); void plotBuffer (foleys::MagicPlotSource* plot); inline bool shouldTurnOff() const noexcept { return depthSlew[0].getTargetValue() == depthSlewMin; } - inline void updatePhase (int ch) noexcept { phase[ch] += angleDelta; } + inline void updatePhase (size_t ch) noexcept { phase[ch] += angleDelta; } - inline std::pair<float, float> getLFO (int n, int ch) noexcept + inline std::pair<float, float> getLFO (int n, size_t ch) noexcept { updatePhase (ch); auto curDepth = depthSlew[ch].getNextValue() * amp; @@ -24,7 +25,7 @@ public: return std::make_pair (wowPtrs[ch][n], curDepth); } - inline void boundPhase (int ch) noexcept + inline void boundPhase (size_t ch) noexcept { while (phase[ch] >= MathConstants<float>::twoPi) phase[ch] -= MathConstants<float>::twoPi; @@ -33,8 +34,8 @@ public: private: float angleDelta = 0.0f; float amp = 0.0f; - float phase[2] = { 0.0f, 0.0f }; - SmoothedValue<float, ValueSmoothingTypes::Multiplicative> depthSlew[2]; + std::vector<float> phase; + std::vector<SmoothedValue<float, ValueSmoothingTypes::Multiplicative>> depthSlew; AudioBuffer<float> wowBuffer; float** wowPtrs = nullptr;