From 2f42b5f39a21b64d40aa19a6869f47a09ae7e50c Mon Sep 17 00:00:00 2001 From: jatin Date: Wed, 11 Oct 2023 23:37:41 -0700 Subject: [PATCH] Waveshaper improvements --- CHANGELOG.md | 2 + modules/chowdsp_utils | 2 +- modules/clap-juce-extensions | 2 +- src/CMakeLists.txt | 1 + src/dsp/Waveshaper/WaveshaperProcessor.cpp | 73 +++++++++++----------- src/dsp/Waveshaper/WaveshaperProcessor.h | 6 +- src/gui/Waveshaper/WaveshaperChyron.cpp | 32 ++++++++++ src/gui/Waveshaper/WaveshaperChyron.h | 29 +++++++++ src/gui/Waveshaper/WaveshaperPlot.cpp | 16 ++++- src/gui/Waveshaper/WaveshaperPlot.h | 3 + 10 files changed, 124 insertions(+), 42 deletions(-) create mode 100644 src/gui/Waveshaper/WaveshaperChyron.cpp create mode 100644 src/gui/Waveshaper/WaveshaperChyron.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a1950b..ff71f2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## [UNRELEASED] - Added gain and frequency labels to spectrum plots. +- Added gain control text slider for Waveshaper. +- Fixed overshoots when using "clipping" waveshapers. - Fixed incorrect EQ filter type for First-Order Lowpass Filter. ## [1.0.0] - 2023-05-24 diff --git a/modules/chowdsp_utils b/modules/chowdsp_utils index 97b1973..4936768 160000 --- a/modules/chowdsp_utils +++ b/modules/chowdsp_utils @@ -1 +1 @@ -Subproject commit 97b19735dbddc2a9a1011ba74d25508a7060df65 +Subproject commit 4936768734629b51d89bda79e3320a3c333c5f45 diff --git a/modules/clap-juce-extensions b/modules/clap-juce-extensions index a40d845..2c23b91 160000 --- a/modules/clap-juce-extensions +++ b/modules/clap-juce-extensions @@ -1 +1 @@ -Subproject commit a40d845f73852dfe15f3ef5a9b9f9f5fb7b69b84 +Subproject commit 2c23b918828ba5fbc5fcb4c95d3a046fbf7e9285 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cb0ef91..99889c8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,6 +40,7 @@ target_sources(ChowMultiTool PRIVATE gui/Waveshaper/WaveshaperDrawer.cpp gui/Waveshaper/WaveshaperMathView.cpp gui/Waveshaper/WaveshaperPointsView.cpp + gui/Waveshaper/WaveshaperChyron.cpp gui/Waveshaper/BottomBar.cpp gui/Waveshaper/FoldFuzzControls.cpp diff --git a/src/dsp/Waveshaper/WaveshaperProcessor.cpp b/src/dsp/Waveshaper/WaveshaperProcessor.cpp index 12d851a..dc7f8ba 100644 --- a/src/dsp/Waveshaper/WaveshaperProcessor.cpp +++ b/src/dsp/Waveshaper/WaveshaperProcessor.cpp @@ -35,6 +35,7 @@ void WaveshaperProcessor::prepare (const juce::dsp::ProcessSpec& spec) freeDrawShaper.prepare (spec); mathShaper.prepare (spec); pointsShaper.prepare (spec); + clipGuard.prepare (spec); static constexpr auto maxOSRatio = static_cast (magic_enum::enum_values().back()); doubleBuffer.setMaxSize ((int) spec.numChannels, (int) spec.maximumBlockSize * maxOSRatio); @@ -48,15 +49,12 @@ void WaveshaperProcessor::oversamplingRateChanged() const juce::SpinLock::ScopedLockType lock { processingMutex }; const auto osRatio = static_cast (params.oversampleParam->get()); - if (osRatio > 1) - { - upsampler.prepare (processSpec, osRatio); + upsampler.prepare (processSpec, osRatio); - auto osProcessSpec = processSpec; - osProcessSpec.sampleRate *= (double) osRatio; - osProcessSpec.maximumBlockSize *= (uint32_t) osRatio; - downsampler.prepare (osProcessSpec, osRatio); - } + auto osProcessSpec = processSpec; + osProcessSpec.sampleRate *= (double) osRatio; + osProcessSpec.maximumBlockSize *= (uint32_t) osRatio; + downsampler.prepare (osProcessSpec, osRatio); ssWaveshaper.prepare (processSpec.sampleRate * osRatio, (int) processSpec.maximumBlockSize * osRatio, @@ -72,40 +70,37 @@ void WaveshaperProcessor::processBlock (const chowdsp::BufferView& buffer gain.setGainDecibels (params.gainParam->getCurrentValue()); gain.process (buffer); - const auto osRatio = static_cast (params.oversampleParam->get()); - std::optional> osBufferView; - if (osRatio > 1) - { - osBufferView.emplace (upsampler.process (buffer), 0, -1); - } - else - { - osBufferView.emplace (buffer, 0, -1); - } - doubleBuffer.setCurrentSize (osBufferView->getNumChannels(), osBufferView->getNumSamples()); - chowdsp::BufferMath::copyBufferData (*osBufferView, doubleBuffer); + const auto osBufferView = upsampler.process (buffer); + + const auto clipLevel = juce::Decibels::decibelsToGain (18.0f); + for (auto [ch, data] : chowdsp::buffer_iters::channels (osBufferView)) + juce::FloatVectorOperations::clip (data.data(), data.data(), -clipLevel, clipLevel, data.size()); + + doubleBuffer.setCurrentSize (osBufferView.getNumChannels(), osBufferView.getNumSamples()); + chowdsp::BufferMath::copyBufferData (osBufferView, doubleBuffer); - if (params.shapeParam->get() == Shapes::Hard_Clip) + const auto shapeParam = params.shapeParam->get(); + if (shapeParam == Shapes::Hard_Clip) adaaHardClipper.processBlock (doubleBuffer); - else if (params.shapeParam->get() == Shapes::Tanh_Clip) + else if (shapeParam == Shapes::Tanh_Clip) adaaTanhClipper.processBlock (doubleBuffer); - else if (params.shapeParam->get() == Shapes::Cubic_Clip) + else if (shapeParam == Shapes::Cubic_Clip) adaaCubicClipper.processBlock (doubleBuffer); - else if (params.shapeParam->get() == Shapes::Nonic_Clip) + else if (shapeParam == Shapes::Nonic_Clip) adaa9thOrderClipper.processBlock (doubleBuffer); - else if (params.shapeParam->get() == Shapes::Full_Wave_Rectify) + else if (shapeParam == Shapes::Full_Wave_Rectify) fullWaveRectifier.processBlock (doubleBuffer); - else if (params.shapeParam->get() == Shapes::West_Coast) + else if (shapeParam == Shapes::West_Coast) { westCoastFolder.processBlock (doubleBuffer); chowdsp::BufferMath::applyGain (doubleBuffer, juce::Decibels::decibelsToGain (-10.0)); } - else if (params.shapeParam->get() == Shapes::Wave_Multiply) + else if (shapeParam == Shapes::Wave_Multiply) { waveMultiplyFolder.processBlock (doubleBuffer); chowdsp::BufferMath::applyGain (doubleBuffer, juce::Decibels::decibelsToGain (16.0)); } - else if (params.shapeParam->get() == Shapes::Fold_Fuzz) + else if (shapeParam == Shapes::Fold_Fuzz) { chowdsp::copyToSIMDBuffer (doubleBuffer, doubleSIMDBuffer); ssWaveshaper.processBlock (doubleSIMDBuffer, @@ -113,25 +108,29 @@ void WaveshaperProcessor::processBlock (const chowdsp::BufferView& buffer MRange.convertFrom0to1 (params.MParam->getCurrentValue())); chowdsp::copyFromSIMDBuffer (doubleSIMDBuffer, doubleBuffer); } - else if (params.shapeParam->get() == Shapes::Free_Draw) + else if (shapeParam == Shapes::Free_Draw) { freeDrawShaper.processBlock (doubleBuffer); } - else if (params.shapeParam->get() == Shapes::Math) + else if (shapeParam == Shapes::Math) { mathShaper.processBlock (doubleBuffer); } - else if (params.shapeParam->get() == Shapes::Spline) + else if (shapeParam == Shapes::Spline) { pointsShaper.processBlock (doubleBuffer); } - chowdsp::BufferMath::copyBufferData (doubleBuffer, *osBufferView); + chowdsp::BufferMath::copyBufferData (doubleBuffer, osBufferView); - if (osRatio > 1) - { - const auto dsBuffer = downsampler.process (*osBufferView); - chowdsp::BufferMath::copyBufferData (dsBuffer, buffer); - } + downsampler.process (osBufferView, buffer); + + clipGuard.setCeiling ((shapeParam == Shapes::Hard_Clip + || shapeParam == Shapes::Tanh_Clip + || shapeParam == Shapes::Cubic_Clip + || shapeParam == Shapes::Nonic_Clip) + ? 1.0f + : 100.0f); + clipGuard.processBlock (buffer); } } // namespace dsp::waveshaper diff --git a/src/dsp/Waveshaper/WaveshaperProcessor.h b/src/dsp/Waveshaper/WaveshaperProcessor.h index 540175d..ecab456 100644 --- a/src/dsp/Waveshaper/WaveshaperProcessor.h +++ b/src/dsp/Waveshaper/WaveshaperProcessor.h @@ -85,7 +85,7 @@ struct Params : chowdsp::ParamHolder chowdsp::EnumChoiceParameter::Ptr oversampleParam { juce::ParameterID { "waveshaper_oversample", ParameterVersionHints::version1_0_0 }, "Waveshaper Oversampling", - OversamplingRatio::TwoX + OversamplingRatio::FourX }; }; @@ -117,7 +117,7 @@ class WaveshaperProcessor using AAFilter = chowdsp::EllipticFilter<8>; chowdsp::Upsampler upsampler; - chowdsp::Downsampler downsampler; + chowdsp::Downsampler downsampler; chowdsp::Buffer doubleBuffer; chowdsp::Buffer> doubleSIMDBuffer; @@ -135,6 +135,8 @@ class WaveshaperProcessor spline::SplineWaveshaper mathShaper; spline::SplineWaveshaper pointsShaper; + chowdsp::OvershootLimiter clipGuard { 64 }; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshaperProcessor) }; } // namespace dsp::waveshaper diff --git a/src/gui/Waveshaper/WaveshaperChyron.cpp b/src/gui/Waveshaper/WaveshaperChyron.cpp new file mode 100644 index 0000000..0ae454e --- /dev/null +++ b/src/gui/Waveshaper/WaveshaperChyron.cpp @@ -0,0 +1,32 @@ +#include "WaveshaperChyron.h" +#include "gui/Shared/Colours.h" + +namespace gui::waveshaper +{ +WaveshaperChyron::WaveshaperChyron (chowdsp::PluginState& pluginState, + dsp::waveshaper::Params& params, + const chowdsp::HostContextProvider& hcp) + : state (pluginState), + gainSlider (state, params.gainParam.get(), &hcp) +{ + gainSlider.setName ("Gain"); + addAndMakeVisible (gainSlider); +} + +void WaveshaperChyron::resized() +{ + auto bounds = getLocalBounds(); + gainSlider.setBounds (bounds.reduced (proportionOfHeight (0.2f))); +} + +void WaveshaperChyron::paint (juce::Graphics& g) +{ + const auto bounds = getLocalBounds(); + + g.setColour (juce::Colours::black.withAlpha (0.75f)); + g.fillRoundedRectangle (bounds.toFloat(), 2.5f); + + g.setColour (colours::linesColour); + g.drawRoundedRectangle (bounds.toFloat(), 2.5f, 1.0f); +} +} // namespace gui::waveshaper diff --git a/src/gui/Waveshaper/WaveshaperChyron.h b/src/gui/Waveshaper/WaveshaperChyron.h new file mode 100644 index 0000000..7976d01 --- /dev/null +++ b/src/gui/Waveshaper/WaveshaperChyron.h @@ -0,0 +1,29 @@ +#pragma once + +#include "dsp/Waveshaper/WaveshaperProcessor.h" +#include "gui/Shared/Fonts.h" +#include "gui/Shared/TextSlider.h" +#include "state/PluginState.h" + +namespace gui::waveshaper +{ +class WaveshaperChyron : public juce::Component +{ +public: + WaveshaperChyron (chowdsp::PluginState& pluginState, + dsp::waveshaper::Params& params, + const chowdsp::HostContextProvider& hcp); + + void resized() override; + void paint (juce::Graphics& g) override; + +private: + chowdsp::PluginState& state; + + TextSlider gainSlider; + + SharedFonts fonts; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshaperChyron) +}; +} // namespace gui::waveshaper diff --git a/src/gui/Waveshaper/WaveshaperPlot.cpp b/src/gui/Waveshaper/WaveshaperPlot.cpp index f9d1e6f..e0800df 100644 --- a/src/gui/Waveshaper/WaveshaperPlot.cpp +++ b/src/gui/Waveshaper/WaveshaperPlot.cpp @@ -12,6 +12,7 @@ WaveshaperPlot::WaveshaperPlot (State& pluginState, dsp::waveshaper::Params& wsP drawArea (*pluginState.nonParams.waveshaperExtraState, *pluginState.undoManager), mathArea (*pluginState.nonParams.waveshaperExtraState, *pluginState.undoManager), pointsArea (*pluginState.nonParams.waveshaperExtraState, *pluginState.undoManager), + chyron (pluginState, wsParams, hcp), shapeParam (*wsParams.shapeParam), gainAttach (*wsParams.gainParam, pluginState, *this), hostContextProvider (hcp) @@ -177,6 +178,8 @@ WaveshaperPlot::WaveshaperPlot (State& pluginState, dsp::waveshaper::Params& wsP addChildComponent (drawArea); addChildComponent (mathArea); addChildComponent (pointsArea); + + addAndMakeVisible (chyron); } void WaveshaperPlot::toggleDrawMode (bool isDrawMode) @@ -184,6 +187,7 @@ void WaveshaperPlot::toggleDrawMode (bool isDrawMode) drawMode = isDrawMode; drawArea.setVisible (drawMode); + chyron.setVisible (! drawMode); if (drawMode) { @@ -202,6 +206,7 @@ void WaveshaperPlot::toggleMathMode (bool isMathMode) mathMode = isMathMode; mathArea.setVisible (mathMode); + chyron.setVisible (! mathMode); if (mathMode) { @@ -220,6 +225,7 @@ void WaveshaperPlot::togglePointsMode (bool isPointsMode) pointsMode = isPointsMode; pointsArea.setVisible (pointsMode); + chyron.setVisible (! pointsMode); if (mathMode) { @@ -291,6 +297,14 @@ void WaveshaperPlot::resized() drawArea.setBounds (getLocalBounds()); mathArea.setBounds (getLocalBounds()); pointsArea.setBounds (getLocalBounds()); + + const auto pad = proportionOfWidth (0.005f); + const auto chyronWidth = proportionOfWidth (0.15f); + const auto chyronHeight = proportionOfWidth (0.05f); + chyron.setBounds (getWidth() - pad - chyronWidth, + getHeight() - pad - chyronHeight, + chyronWidth, + chyronHeight); } void WaveshaperPlot::mouseDown (const juce::MouseEvent& e) @@ -298,7 +312,7 @@ void WaveshaperPlot::mouseDown (const juce::MouseEvent& e) if (e.mods.isPopupMenu()) { chowdsp::SharedLNFAllocator lnfAllocator; - hostContextProvider.showParameterContextPopupMenu (gainAttach.getParameter(), + hostContextProvider.showParameterContextPopupMenu (*gainAttach.getParameter(), {}, lnfAllocator->getLookAndFeel()); return; diff --git a/src/gui/Waveshaper/WaveshaperPlot.h b/src/gui/Waveshaper/WaveshaperPlot.h index 449343e..a0cfe6c 100644 --- a/src/gui/Waveshaper/WaveshaperPlot.h +++ b/src/gui/Waveshaper/WaveshaperPlot.h @@ -5,6 +5,7 @@ #include "WaveshaperPointsView.h" #include "dsp/Waveshaper/WaveshaperProcessor.h" #include "state/PluginState.h" +#include "WaveshaperChyron.h" namespace gui::waveshaper { @@ -27,6 +28,8 @@ class WaveshaperPlot : public juce::Slider WaveshaperMathView mathArea; WaveshaperPointsView pointsArea; + WaveshaperChyron chyron; + chowdsp::ScopedCallbackList callbacks; const chowdsp::EnumChoiceParameter& shapeParam; chowdsp::SliderAttachment gainAttach;