Skip to content

Commit

Permalink
Clip guard improvements and internal bypass control
Browse files Browse the repository at this point in the history
  • Loading branch information
jatinchowdhury18 committed Oct 15, 2023
1 parent 2f42b5f commit b6048c8
Show file tree
Hide file tree
Showing 18 changed files with 160 additions and 22 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12" CACHE STRING "Minimum OS X deployment ta
if(WIN32)
set(CMAKE_SYSTEM_VERSION 7.1 CACHE STRING INTERNAL FORCE) # Windows SDK for Windows 7 and up
endif()
project(ChowMultiTool VERSION 1.0.1)
project(ChowMultiTool VERSION 1.0.2)

set(CMAKE_CXX_STANDARD 20)
set(COMPANY_NAME "chowdsp")
Expand Down Expand Up @@ -70,7 +70,7 @@ if(CHOWMULTITOOL_BUILD_CLAP)
clap_juce_extensions_plugin(
TARGET ChowMultiTool
CLAP_ID "org.chowdsp.ChowMultiTool"
CLAP_FEATURES audio-effect
CLAP_FEATURES audio-effect win32-dpi-aware
CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES 64
CLAP_USE_JUCE_PARAMETER_RANGES DISCRETE
)
Expand Down
2 changes: 1 addition & 1 deletion modules/clap-juce-extensions
38 changes: 31 additions & 7 deletions src/dsp/MultiToolProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ MultiToolProcessor::MultiToolProcessor (juce::AudioProcessor& parentPlugin, Stat
tools (detail::generate_tools (pluginState))
{
for (auto param : std::initializer_list<const juce::RangedAudioParameter*> { params.toolParam.get(),
params.eqParams->linearPhaseMode.get() })
params.eqParams->linearPhaseMode.get(),
params.waveshaperParams->clipGuardParam.get() })
{
latencyChangeCallbacks += {
pluginState.addParameterListener (*param,
Expand All @@ -63,11 +64,13 @@ MultiToolProcessor::MultiToolProcessor (juce::AudioProcessor& parentPlugin, Stat
}
}

CHOWDSP_CHECK_HAS_METHOD (HasGetLatencySamples, getLatencySamples)
void MultiToolProcessor::recalculateLatency()
{
const auto toolChoice = params.toolParam->getIndex() - 1;
if (toolChoice < 0) // no tool!
{
bypass.setLatencySamples (0);
plugin.setLatencySamples (0);
return;
}
Expand All @@ -77,10 +80,17 @@ void MultiToolProcessor::recalculateLatency()
[this] (auto& tool)
{
using ToolType [[maybe_unused]] = std::decay_t<decltype (tool)>;
if constexpr (std::is_same_v<ToolType, eq::EQProcessor>)
plugin.setLatencySamples (tool.getLatencySamples());
if constexpr (HasGetLatencySamples<ToolType>)
{
const auto latencySamples = tool.getLatencySamples();
bypass.setLatencySamples (latencySamples);
plugin.setLatencySamples (latencySamples);
}
else
{
bypass.setLatencySamples (0);
plugin.setLatencySamples (0);
}
});
}

Expand All @@ -89,18 +99,31 @@ void MultiToolProcessor::prepare (const juce::dsp::ProcessSpec& spec)
chowdsp::TupleHelpers::forEachInTuple ([&spec] (auto& tool, size_t)
{ tool.prepare (spec); },
tools);
bypass.prepare (spec, ! params.bypassParam->get());
recalculateLatency();
}

void MultiToolProcessor::processBlock (juce::AudioBuffer<float>& buffer, const juce::MidiBuffer& midiBuffer)
{
const auto toolChoice = params.toolParam->getIndex() - 1;
if (toolChoice < 0) // no tool!
auto mainBusBuffer = plugin.getBusBuffer (buffer, true, 0);
const auto clearExtraBusses = [&buffer, &mainBusBuffer]
{
auto busBuffer = plugin.getBusBuffer (buffer, true, 0);
for (int ch = busBuffer.getNumChannels(); ch < buffer.getNumChannels(); ++ch)
for (int ch = mainBusBuffer.getNumChannels(); ch < buffer.getNumChannels(); ++ch)
buffer.clear (ch, 0, buffer.getNumSamples());
};


const auto isOn = ! params.bypassParam->get();
if (! bypass.processBlockIn (mainBusBuffer, isOn))
{
clearExtraBusses();
return;
}

const auto toolChoice = params.toolParam->getIndex() - 1;
if (toolChoice < 0) // no tool!
{
clearExtraBusses();
return;
}

Expand Down Expand Up @@ -138,5 +161,6 @@ void MultiToolProcessor::processBlock (juce::AudioBuffer<float>& buffer, const j
buffer.clear (ch, 0, buffer.getNumSamples());
}
});
bypass.processBlockOut (mainBusBuffer, isOn);
}
} // namespace dsp
3 changes: 3 additions & 0 deletions src/dsp/MultiToolProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class MultiToolProcessor

ToolTypes::Types tools;

// at the moment all tools are expected to have integer latency
chowdsp::BypassProcessor<float, chowdsp::DelayLineInterpolationTypes::None> bypass;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiToolProcessor)
};
} // namespace dsp
34 changes: 27 additions & 7 deletions src/dsp/Waveshaper/WaveshaperProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ WaveshaperProcessor::WaveshaperProcessor (chowdsp::PluginState& state, Params& w
{
oversamplingRateChanged();
});
clipGuardChangeCallback = state.addParameterListener (*params.clipGuardParam,
chowdsp::ParameterListenerThread::AudioThread,
[this]
{
if (params.clipGuardParam->get())
clipGuard.reset();
});
}

void WaveshaperProcessor::prepare (const juce::dsp::ProcessSpec& spec)
Expand Down Expand Up @@ -125,12 +132,25 @@ void WaveshaperProcessor::processBlock (const chowdsp::BufferView<float>& 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);
if (params.clipGuardParam->get())
{
clipGuard.setCeiling (modeUsesClipGuard (shapeParam)
? 1.0f
: 100.0f);
clipGuard.processBlock (buffer);
}
}

bool WaveshaperProcessor::modeUsesClipGuard (Shapes shape)
{
return shape == Shapes::Hard_Clip
|| shape == Shapes::Tanh_Clip
|| shape == Shapes::Cubic_Clip
|| shape == Shapes::Nonic_Clip;
}

int WaveshaperProcessor::getLatencySamples() const
{
return params.clipGuardParam->get() ? clipGuardLookaheadSamples : 0;
}
} // namespace dsp::waveshaper
21 changes: 19 additions & 2 deletions src/dsp/Waveshaper/WaveshaperProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ struct Params : chowdsp::ParamHolder
{
Params()
{
add (gainParam, shapeParam, kParam, MParam, oversampleParam);
add (gainParam, shapeParam, kParam, MParam, oversampleParam, clipGuardParam);
versionStreamingCallback = [this] (const chowdsp::Version& version)
{
using namespace chowdsp::version_literals;
if (version < "1.0.2"_v)
clipGuardParam->setValueNotifyingHost (0.0f);
};
}

chowdsp::GainDBParameter::Ptr gainParam {
Expand Down Expand Up @@ -87,6 +93,12 @@ struct Params : chowdsp::ParamHolder
"Waveshaper Oversampling",
OversamplingRatio::FourX
};

chowdsp::BoolParameter::Ptr clipGuardParam {
juce::ParameterID { "waveshaper_clip_guard", ParameterVersionHints::version1_1_0 },
"Waveshaper Clip Guard",
true
};
};

struct ExtraState
Expand All @@ -104,11 +116,15 @@ class WaveshaperProcessor
void prepare (const juce::dsp::ProcessSpec& spec);
void processBlock (const chowdsp::BufferView<float>& buffer);

int getLatencySamples() const;
static bool modeUsesClipGuard (Shapes shape);

private:
void oversamplingRateChanged();

const Params& params;
chowdsp::ScopedCallback osChangeCallback;
chowdsp::ScopedCallback clipGuardChangeCallback;

juce::dsp::ProcessSpec processSpec {};
juce::SpinLock processingMutex;
Expand All @@ -135,7 +151,8 @@ class WaveshaperProcessor
spline::SplineWaveshaper<spline::SplinePoints, spline::SplineADAA> mathShaper;
spline::SplineWaveshaper<spline::VectorSplinePoints, spline::VectorSplineADAA> pointsShaper;

chowdsp::OvershootLimiter<float> clipGuard { 64 };
static constexpr int clipGuardLookaheadSamples = 64;
chowdsp::OvershootLimiter<float> clipGuard { clipGuardLookaheadSamples };

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshaperProcessor)
};
Expand Down
3 changes: 3 additions & 0 deletions src/gui/EQ/EQChyron.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ void EQChyron::setSelectedBand (int newSelectedBand)
{
setVisible (newSelectedBand >= 0);

if (newSelectedBand < 0)
return;

auto& activeParams = eqParams.eqParams[(size_t) selectedBand];
const auto newFilterType = helpers::getFilterType (activeParams.typeParam->getIndex());

Expand Down
20 changes: 20 additions & 0 deletions src/gui/PluginEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ PluginEditor::PluginEditor (ChowMultiTool& p)
: juce::AudioProcessorEditor (p),
plugin (p),
hostContextProvider (plugin, *this),
paintBypassCover (plugin.getState().params.bypassParam->get()),
toolbar (plugin, oglHelper)
{
oglHelper.setComponent (this);
Expand All @@ -48,6 +49,14 @@ PluginEditor::PluginEditor (ChowMultiTool& p)
{ refreshEditor(); });

juce::LookAndFeel::setDefaultLookAndFeel (lnfAllocator->getLookAndFeel<chowdsp::ChowLNF>());

bypassChangeCallback = plugin.getState().addParameterListener (plugin.getState().params.bypassParam,
chowdsp::ParameterListenerThread::MessageThread,
[this]
{
paintBypassCover = plugin.getState().params.bypassParam->get();
repaint();
});
}

PluginEditor::~PluginEditor()
Expand Down Expand Up @@ -131,6 +140,17 @@ void PluginEditor::paint (juce::Graphics& g)
g.fillAll();
}

void PluginEditor::paintOverChildren (juce::Graphics& g)
{
const auto toolChoice = plugin.getState().params.toolParam->getIndex();
if (! paintBypassCover || toolChoice == 0)
return;

g.setColour (colours::linesColour.withAlpha (0.25f));
if (editorComponent != nullptr)
g.fillRect (editorComponent->getBoundsInParent());
}

void PluginEditor::resized()
{
auto bounds = getLocalBounds();
Expand Down
4 changes: 4 additions & 0 deletions src/gui/PluginEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class PluginEditor : public juce::AudioProcessorEditor,
~PluginEditor() override;

void paint (juce::Graphics& g) override;
void paintOverChildren (juce::Graphics& g) override;
void resized() override;

auto& getErrorMessageView() { return errorMessageView; }
Expand All @@ -30,6 +31,9 @@ class PluginEditor : public juce::AudioProcessorEditor,
ChowMultiTool& plugin;
chowdsp::HostContextProvider hostContextProvider;

bool paintBypassCover;
chowdsp::ScopedCallback bypassChangeCallback;

Toolbar toolbar;
std::unique_ptr<juce::Component> editorComponent;
ErrorMessageView errorMessageView;
Expand Down
5 changes: 5 additions & 0 deletions src/gui/Shared/LookAndFeels.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@ class MenuLNF : public chowdsp::ChowLNF
const juce::Drawable* icon,
const juce::Colour* const textColourToUse) override;
void drawPopupMenuBackground (juce::Graphics& g, int width, int height) override;

juce::Font getTextButtonFont (juce::TextButton&, int buttonHeight) override
{
return juce::Font { juce::jmin (18.0f, (float) buttonHeight * 0.8f) }.boldened();
}
};
} // namespace gui::lnf
17 changes: 16 additions & 1 deletion src/gui/Toolbar/Toolbar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ Toolbar::Toolbar (ChowMultiTool& plugin, chowdsp::OpenGLHelper& oglHelper)
presetsComp.setLookAndFeel (lnfAllocator->getLookAndFeel<gui::lnf::MenuLNF>());
addAndMakeVisible (presetsComp);

bypassButton.setColour (juce::ComboBox::ColourIds::outlineColourId, juce::Colours::transparentWhite);
bypassButton.setColour (juce::TextButton::ColourIds::buttonColourId, colours::linesColour);
bypassButton.setColour (juce::TextButton::ColourIds::buttonOnColourId, logo::colours::accentRed);
bypassButton.setColour (juce::TextButton::ColourIds::textColourOffId, colours::backgroundLight);
bypassButton.setColour (juce::TextButton::ColourIds::textColourOnId, colours::linesColour);
bypassButton.setClickingTogglesState (true);
bypassButton.setLookAndFeel (lnfAllocator->getLookAndFeel<lnf::MenuLNF>());
addAndMakeVisible (bypassButton);

addAndMakeVisible (settingsButton);
}

Expand Down Expand Up @@ -99,7 +108,7 @@ void Toolbar::resized()
const auto buttonDim = proportionOfHeight (0.7f);
const auto buttonY = (getHeight() - buttonDim) / 2;

bounds.reduce (proportionOfWidth (0.01875f), 0);
bounds.reduce (proportionOfWidth (0.01f), 0);
undoButton.setBounds (juce::Rectangle { buttonDim, buttonDim }.withY (buttonY).withX (bounds.getX()));

bounds.removeFromLeft (buttonDim + proportionOfWidth (0.0125f));
Expand All @@ -115,6 +124,12 @@ void Toolbar::resized()
proportionOfWidth (0.3125f),
proportionOfHeight (55.0f / 75.0f));

bypassButton.setBounds (juce::Rectangle { 2 * buttonDim, buttonDim }
.withY (buttonY)
.withRight (bounds.getRight() - buttonDim)
.withLeft (presetsComp.getRight())
.reduced (proportionOfWidth (0.01f), 0));

settingsButton.setBounds (juce::Rectangle { buttonDim, buttonDim }.withY (buttonY).withRightX (bounds.getRight()));
}
} // namespace gui
3 changes: 3 additions & 0 deletions src/gui/Toolbar/Toolbar.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class Toolbar : public juce::Component,
chowdsp::presets::frontend::FileInterface presetsFileInterface;
presets::PresetsComponent presetsComp;

juce::TextButton bypassButton { "Bypass" };
chowdsp::ButtonAttachment bypassAttach { state.params.bypassParam, state, bypassButton };

SettingsButton settingsButton;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Toolbar)
Expand Down
9 changes: 9 additions & 0 deletions src/gui/Waveshaper/WaveshaperEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ WaveshaperEditor::WaveshaperEditor (State& pluginState, dsp::waveshaper::Params&
: params (wsParams),
plot (pluginState, wsParams, hcp),
foldFuzzControls (pluginState, wsParams, hcp),
clipGuardButton ("Vector/arrows-up-to-line-solid.svg", colours::plotColour, colours::linesColour),
clipGuardAttach (wsParams.clipGuardParam, pluginState, clipGuardButton),
freeDrawButton ("Vector/pencil-solid.svg", colours::plotColour, colours::linesColour),
mathButton ("Vector/calculator-solid.svg", colours::plotColour, colours::linesColour),
pointsButton ("Vector/eye-dropper-solid.svg", colours::plotColour, colours::linesColour)
Expand All @@ -20,6 +22,10 @@ WaveshaperEditor::WaveshaperEditor (State& pluginState, dsp::waveshaper::Params&
addChildComponent (foldFuzzControls);
foldFuzzControls.setVisible (params.shapeParam->get() == dsp::waveshaper::Shapes::Fold_Fuzz);

addChildComponent (clipGuardButton);
clipGuardButton.setVisible (dsp::waveshaper::WaveshaperProcessor::modeUsesClipGuard (wsParams.shapeParam->get()));
clipGuardButton.setTooltip ("Enables \"clip guard\" mode (requires added latency).");

addChildComponent (freeDrawButton);
freeDrawButton.setVisible (wsParams.shapeParam->get() == dsp::waveshaper::Shapes::Free_Draw);
freeDrawButton.onStateChange = [this]
Expand Down Expand Up @@ -49,6 +55,8 @@ WaveshaperEditor::WaveshaperEditor (State& pluginState, dsp::waveshaper::Params&
chowdsp::ParameterListenerThread::MessageThread,
[this, &wsParams]
{
clipGuardButton.setVisible (dsp::waveshaper::WaveshaperProcessor::modeUsesClipGuard (wsParams.shapeParam->get()));

const auto isFreeDrawMode = wsParams.shapeParam->get() == dsp::waveshaper::Shapes::Free_Draw;
freeDrawButton.setVisible (isFreeDrawMode);
if (! isFreeDrawMode)
Expand Down Expand Up @@ -80,6 +88,7 @@ void WaveshaperEditor::resized()
const auto pad = proportionOfWidth (0.005f);
const auto dim = proportionOfWidth (0.05f);
const auto editButtonBounds = juce::Rectangle { bounds.getWidth() - pad - dim, pad, dim, dim };
clipGuardButton.setBounds (editButtonBounds);
freeDrawButton.setBounds (editButtonBounds);
mathButton.setBounds (editButtonBounds);
pointsButton.setBounds (editButtonBounds);
Expand Down
Loading

0 comments on commit b6048c8

Please sign in to comment.