AnalogTapeModel

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

commit 112337652e82d6b65bb030f0a23afd8e44bf17ea
parent bd4576d6e9bc32a279c60756c97c3b6d358720f8
Author: jatinchowdhury18 <jatinchowdhury18@gmail.com>
Date:   Sun, 15 Nov 2020 23:44:50 -0800

Implement bypass switches for plugin sections (#109)

* First pass at On/Off switches

* Implement smooth bypass processing for bypass switches

* Fix Mac compilation issues

* Fix pluginval failures

Co-authored-by: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Diffstat:
MCHANGELOG.md | 2++
MManual/ChowTapeManual.pdf | 0
MPlugin/CHOWTapeModel.jucer | 20+++++++++++++++-----
MPlugin/Screenshots/CHEW.png | 0
MPlugin/Screenshots/Degrade.png | 0
MPlugin/Screenshots/Filters.png | 0
MPlugin/Screenshots/Flutter.png | 0
MPlugin/Screenshots/Loss.png | 0
MPlugin/Screenshots/Tape.png | 0
MPlugin/Screenshots/Tone.png | 0
MPlugin/Screenshots/Wow.png | 0
MPlugin/Screenshots/full_gui.png | 0
MPlugin/Source/GUI/Assets/gui.xml | 68+++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
APlugin/Source/GUI/Assets/powerswitch.svg | 6++++++
MPlugin/Source/GUI/MixGroupViz.cpp | 1+
MPlugin/Source/GUI/MyLNF.cpp | 20++++++++++++--------
APlugin/Source/GUI/OnOffManager.cpp | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/GUI/OnOffManager.h | 23+++++++++++++++++++++++
APlugin/Source/GUI/PowerButton.cpp | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/GUI/PowerButton.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
MPlugin/Source/GUI/ScreenshotHelper.cpp | 15++++++++++++++-
MPlugin/Source/GUI/ScreenshotHelper.h | 2+-
MPlugin/Source/PluginProcessor.cpp | 18+++++++++++++++---
MPlugin/Source/PluginProcessor.h | 5++++-
APlugin/Source/Processors/BypassProcessor.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MPlugin/Source/Processors/Chew/ChewProcessor.cpp | 37++++++++++++++++++++++---------------
MPlugin/Source/Processors/Chew/ChewProcessor.h | 5++++-
MPlugin/Source/Processors/Degrade/DegradeProcessor.cpp | 6+++++-
MPlugin/Source/Processors/Degrade/DegradeProcessor.h | 4++++
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp | 12+++++++++++-
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.h | 4++++
MPlugin/Source/Processors/Hysteresis/ToneControl.cpp | 26++++++++++++++++++++++----
MPlugin/Source/Processors/Hysteresis/ToneControl.h | 1+
DPlugin/Source/Processors/InputFilters.cpp | 111-------------------------------------------------------------------------------
DPlugin/Source/Processors/InputFilters.h | 35-----------------------------------
APlugin/Source/Processors/Input_Filters/InputFilters.cpp | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Source/Processors/Input_Filters/InputFilters.h | 38++++++++++++++++++++++++++++++++++++++
RPlugin/Source/Processors/LinkwitzRileyFilter.h -> Plugin/Source/Processors/Input_Filters/LinkwitzRileyFilter.h | 0
MPlugin/Source/Processors/Loss_Effects/LossFilter.cpp | 27++++++++++++++++++---------
MPlugin/Source/Processors/Loss_Effects/LossFilter.h | 7+++++--
MPlugin/Source/Processors/Timing_Effects/Flutter.cpp | 8++++++--
MPlugin/Source/Processors/Timing_Effects/Flutter.h | 1+
MREADME.md | 1+
43 files changed, 674 insertions(+), 217 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -7,9 +7,11 @@ this file. - Added high/low-cut filters for the input section. - Updated presets menu: now supports saving presets, and managing user preset folder. - Tone section: added transition frequency control, and made bass/treble controls more extreme. +- Added bypass switches for plugin sections. - Added visualizations for Wow and Flutter. - Added buttons to snap tape speed to conventional values. - Added coloured circle on bottom bar to visualize mix group. +- Added a new official user manual. ## [2.6.0] - 2020-09-29 - Added Pre/post emphasis filters for the hysteresis stage. diff --git a/Manual/ChowTapeManual.pdf b/Manual/ChowTapeManual.pdf Binary files differ. diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer @@ -12,6 +12,7 @@ <FILE id="DzcyPN" name="gui.xml" compile="0" resource="1" file="Source/GUI/Assets/gui.xml"/> <FILE id="xzpwn3" name="knob.svg" compile="0" resource="1" file="Source/GUI/Assets/knob.svg"/> <FILE id="VVaf9c" name="pointer.svg" compile="0" resource="1" file="Source/GUI/Assets/pointer.svg"/> + <FILE id="kPu6oB" name="powerswitch.svg" compile="0" resource="1" file="Source/GUI/Assets/powerswitch.svg"/> <FILE id="th5YSa" name="RobotoCondensed-Bold.ttf" compile="0" resource="1" file="Source/GUI/Assets/RobotoCondensed-Bold.ttf"/> <FILE id="bQP3yl" name="RobotoCondensed-Regular.ttf" compile="0" resource="1" @@ -32,6 +33,11 @@ file="Source/GUI/ScreenshotHelper.cpp"/> <FILE id="Vw0Q80" name="ScreenshotHelper.h" compile="0" resource="0" file="Source/GUI/ScreenshotHelper.h"/> + <FILE id="jFuKe3" name="OnOffManager.cpp" compile="1" resource="0" + file="Source/GUI/OnOffManager.cpp"/> + <FILE id="yLv4BP" name="OnOffManager.h" compile="0" resource="0" file="Source/GUI/OnOffManager.h"/> + <FILE id="dOs7G8" name="PowerButton.cpp" compile="1" resource="0" file="Source/GUI/PowerButton.cpp"/> + <FILE id="tNdX49" name="PowerButton.h" compile="0" resource="0" file="Source/GUI/PowerButton.h"/> <FILE id="yy3bQ7" name="TapeScope.cpp" compile="1" resource="0" file="Source/GUI/TapeScope.cpp"/> <FILE id="zDg8Lf" name="TapeScope.h" compile="0" resource="0" file="Source/GUI/TapeScope.h"/> <FILE id="WfCYvA" name="TitleComp.cpp" compile="1" resource="0" file="Source/GUI/TitleComp.cpp"/> @@ -118,6 +124,13 @@ <FILE id="vd8RTB" name="ToneControl.h" compile="0" resource="0" file="Source/Processors/Hysteresis/ToneControl.h"/> <FILE id="Gxo9aB" name="ToneFilter.h" compile="0" resource="0" file="Source/Processors/Hysteresis/ToneFilter.h"/> </GROUP> + <GROUP id="{7D3433B2-4187-FE49-3315-E6F91824ED66}" name="Input_Filters"> + <FILE id="UEN8uY" name="InputFilters.cpp" compile="1" resource="0" + file="Source/Processors/Input_Filters/InputFilters.cpp"/> + <FILE id="WU560t" name="InputFilters.h" compile="0" resource="0" file="Source/Processors/Input_Filters/InputFilters.h"/> + <FILE id="TgI8iY" name="LinkwitzRileyFilter.h" compile="0" resource="0" + file="Source/Processors/Input_Filters/LinkwitzRileyFilter.h"/> + </GROUP> <GROUP id="{37F4BCFA-28D3-CD4D-17AF-3C696E7EC8DA}" name="Loss_Effects"> <FILE id="gJA2Gi" name="FIRFilter.h" compile="0" resource="0" file="Source/Processors/Loss_Effects/FIRFilter.h"/> <FILE id="gjr7ub" name="LossFilter.cpp" compile="1" resource="0" file="Source/Processors/Loss_Effects/LossFilter.cpp"/> @@ -128,15 +141,12 @@ <FILE id="Wz3lz6" name="Flutter.h" compile="0" resource="0" file="Source/Processors/Timing_Effects/Flutter.h"/> </GROUP> <FILE id="jlO9LL" name="BilinearUtils.h" compile="0" resource="0" file="Source/Processors/BilinearUtils.h"/> + <FILE id="D6lRuQ" name="BypassProcessor.h" compile="0" resource="0" + file="Source/Processors/BypassProcessor.h"/> <FILE id="YNkJOh" name="DryWetProcessor.h" compile="0" resource="0" file="Source/Processors/DryWetProcessor.h"/> <FILE id="zwLvQ9" name="GainProcessor.h" compile="0" resource="0" file="Source/Processors/GainProcessor.h"/> <FILE id="SSnw2J" name="IIRFilter.h" compile="0" resource="0" file="Source/Processors/IIRFilter.h"/> - <FILE id="ArGmHO" name="InputFilters.cpp" compile="1" resource="0" - file="Source/Processors/InputFilters.cpp"/> - <FILE id="ipAPk7" name="InputFilters.h" compile="0" resource="0" file="Source/Processors/InputFilters.h"/> - <FILE id="U8pzHn" name="LinkwitzRileyFilter.h" compile="0" resource="0" - file="Source/Processors/LinkwitzRileyFilter.h"/> </GROUP> <FILE id="zPJjtw" name="PluginProcessor.cpp" compile="1" resource="0" file="Source/PluginProcessor.cpp"/> diff --git a/Plugin/Screenshots/CHEW.png b/Plugin/Screenshots/CHEW.png Binary files differ. diff --git a/Plugin/Screenshots/Degrade.png b/Plugin/Screenshots/Degrade.png Binary files differ. diff --git a/Plugin/Screenshots/Filters.png b/Plugin/Screenshots/Filters.png Binary files differ. diff --git a/Plugin/Screenshots/Flutter.png b/Plugin/Screenshots/Flutter.png Binary files differ. diff --git a/Plugin/Screenshots/Loss.png b/Plugin/Screenshots/Loss.png Binary files differ. diff --git a/Plugin/Screenshots/Tape.png b/Plugin/Screenshots/Tape.png Binary files differ. diff --git a/Plugin/Screenshots/Tone.png b/Plugin/Screenshots/Tone.png Binary files differ. diff --git a/Plugin/Screenshots/Wow.png b/Plugin/Screenshots/Wow.png Binary files differ. diff --git a/Plugin/Screenshots/full_gui.png b/Plugin/Screenshots/full_gui.png Binary files differ. diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml @@ -55,7 +55,7 @@ padding="0" margin="0" name="Output Gain" tooltip="Sets the output gain from the tape model in Decibels."/> </View> <View flex-direction="column" tab-color="" background-color="FF31323A" - padding="0" tab-caption="Filters"> + padding="0" tab-caption="Filters" margin="0"> <Slider caption="Low Cut" parameter="ifilt_low" class="Slider" name="Low Cut" tooltip="Applies a low cut filter before applying tape processing."/> <Slider caption="High Cut" parameter="ifilt_high" class="Slider" name="High Cut" @@ -64,65 +64,85 @@ margin="0" padding="5" button-color="00000000" flex-grow="0.35" button-on-color="FF8B3232" lookAndFeel="SpeedButtonLNF" name="Makeup" tooltip="Adds the signal cut out by the cut filters back to the processed signal."/> + <PowerButton background-color="00000000" max-height="25" min-height="20" margin="0" + padding="0" button-color="ff595c6b" button-on-color="FFEAA92C" + parameter="ifilt_onoff" name="Filters On/Off" tooltip="Turns the pre-processing filters on or off."/> </View> </View> <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF"> <View flex-direction="column" tab-color="" background-color="FF31323A" - padding="0" tab-caption="Tape"> + padding="0" tab-caption="Tape" margin="0"> + <View margin="0" padding="0" flex-grow="0.05" background-color="00000000"/> <Slider caption="Bias" parameter="width" class="Slider" name="Bias" padding="0" margin="0" tooltip="Controls the amount of bias used by the tape recorder. Turning down the bias can create &quot;deadzone&quot; distortion."/> <Slider caption="Saturation" parameter="sat" class="Slider" name="Saturation" padding="0" margin="0" tooltip="Controls the amount of tape saturation applied to the signal."/> <Slider caption="Drive" parameter="drive" class="Slider" name="Drive" padding="0" margin="0" tooltip="Controls the amount of amplification done during the tape magnetisation process. Note that unlike the &quot;Input Gain&quot;, this amplification is highly nonlinear."/> + <PowerButton flex-grow="1.0" margin="0" padding="0" background-color="00000000" + button-on-color="FFEAA92C" min-height="20" max-height="25" button-color="ff595c6b" + parameter="hyst_onoff" name="Tape On/Off" tooltip="Turns the tape processing on or off."/> </View> <View flex-direction="column" tab-color="" background-color="FF31323A" - padding="0" tab-caption="Tone"> + padding="0" tab-caption="Tone" margin="0"> + <View margin="0" padding="0" flex-grow="0.05" background-color="00000000"/> <Slider caption="Treble" parameter="h_treble" class="Slider" name="Treble" padding="0" margin="0" tooltip="Controls the treble response of the pre/post-emphasis filters."/> <Slider caption="Bass" parameter="h_bass" class="Slider" name="Bass" padding="0" margin="0" tooltip="Controls the bass response of the pre/post-emphasis filters."/> <Slider caption="Frequency" parameter="h_tfreq" class="Slider" name="Transition Frequency" padding="0" margin="0" tooltip="Controls the transition frequency between the bass and treble sections of the EQ."/> + <PowerButton max-height="25" min-height="20" margin="0" padding="0" background-color="00000000" + button-color="ff595c6b" button-on-color="FFEAA92C" parameter="tone_onoff" + name="Tone On/Off" tooltip="Turns the tone control processing on or off."/> </View> </View> <View display="tabbed" padding="0" background-color="FF31323A" flex-grow="1.5" lookAndFeel="MyLNF"> <View flex-direction="column" tab-caption="Loss" tab-color="" background-color="FF31323A" padding="0" margin="0"> - <View flex-grow="0.1" background-color="00000000"/> + <View flex-grow="0.05" background-color="00000000"/> <Slider caption="Gap [microns]" parameter="gap" slider-type="linear-horizontal" class="Slider" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" name="Gap" tooltip="Sets the width of the playhead gap. Certain frequencies that resonate with the gap width will be emphasized." slidertext-height="18" caption-placement="top-left"/> - <View flex-grow="0.2" background-color="00000000"/> - <Slider caption="Thickness [microns]" parameter="thick" class="Slider" slider-type="linear-horizontal" - padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" - name="Thickness" tooltip="Sets the thickness of the tape. Thicker tape has a more muted high-frequency response." + <View flex-grow="0.1" background-color="00000000"/> + <Slider caption="Thickness [microns]" parameter="thick" class="Slider" + slider-type="linear-horizontal" padding="0" slider-background="ff595c6b" + slider-track="ff9cbcbd" name="Thickness" tooltip="Sets the thickness of the tape. Thicker tape has a more muted high-frequency response." caption-placement="top-left"/> - <View flex-grow="0.2" background-color="00000000"/> + <View flex-grow="0.1" background-color="00000000"/> <Slider caption="Spacing [microns]" parameter="spacing" slider-type="linear-horizontal" class="Slider" padding="0" slider-background="ff595c6b" slider-track="ff9cbcbd" name="Spacing" tooltip="Sets the spacing between the tape and the playhead. A larger spacing means more high frequency signal is lost during playback." caption-placement="top-left"/> - <View flex-grow="0.2" background-color="00000000"/> + <View 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" 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." caption-placement="top-left"/> - <View flex-grow="0.55" margin="0" padding="2" background-color="00000000"> + <View flex-grow="0.57" margin="0" padding="2" background-color="00000000"> <TextButton margin="0" padding="2" text="3.75" button-color="00000000" background-color="00000000" - onClick="set_speed_3.75" lookAndFeel="SpeedButtonLNF" button-on-color="00000000"/> + onClick="set_speed_3.75" lookAndFeel="SpeedButtonLNF" button-on-color="00000000" + name="Speed_375"/> <TextButton text="7.5" margin="0" padding="2" button-color="00000000" background-color="00000000" - onClick="set_speed_7.50" lookAndFeel="SpeedButtonLNF" button-on-color="00000000"/> + onClick="set_speed_7.50" lookAndFeel="SpeedButtonLNF" button-on-color="00000000" + name="Speed_75"/> <TextButton margin="0" padding="2" text="15" button-color="00000000" button-on-color="00000000" - background-color="00000000" onClick="set_speed_15.00" lookAndFeel="SpeedButtonLNF"/> + background-color="00000000" onClick="set_speed_15.00" lookAndFeel="SpeedButtonLNF" + name="Speed_15"/> <TextButton margin="0" padding="2" background-color="00000000" onClick="set_speed_30.00" - lookAndFeel="SpeedButtonLNF" text="30" button-color="00000000" button-on-color="00000000"/> + lookAndFeel="SpeedButtonLNF" text="30" button-color="00000000" + button-on-color="00000000" name="Speed_30"/> </View> <View flex-grow="0.01" background-color="00000000"/> + <PowerButton margin="0" padding="0" background-color="00000000" max-height="25" + min-height="20" button-color="ff595c6b" button-on-color="FFEAA92C" + parameter="loss_onoff" name="Loss On/Off" tooltip="Turns the loss filters on or off."/> </View> - <View tab-caption="Degrade" padding="0" flex-direction="column" background-color="FF31323A"> + <View tab-caption="Degrade" padding="0" flex-direction="column" background-color="FF31323A" + margin="0"> + <View margin="0" padding="0" flex-grow="0.05" background-color="00000000"/> <Slider parameter="deg_depth" caption="Depth" class="Slider" name="Depth" tooltip="Sets the depth of the tape degradation." padding="0" margin="0"/> @@ -130,14 +150,22 @@ padding="0" margin="0" tooltip="Sets the amount of the tape that is degraded. At large values all of the tape will be affected, at small values only some sections will be affected."/> <Slider parameter="deg_var" caption="Variance" class="Slider" name="Variance" padding="0" margin="0" tooltip="Sets the variance of the tape degradation. Use lower values for uniform degradation, or higher values for variation across different sections of tape."/> + <PowerButton margin="0" padding="0" background-color="00000000" max-height="25" + min-height="20" button-color="ff595c6b" button-on-color="FFEAA92C" + parameter="deg_onoff" name="Degrade On/Off" tooltip="Turns the degradation processing on or off."/> </View> - <View tab-caption="CHEW" padding="0" flex-direction="column" background-color="FF31323A"> + <View tab-caption="CHEW" padding="0" flex-direction="column" background-color="FF31323A" + margin="0"> + <View margin="0" padding="0" flex-grow="0.05" background-color="00000000"/> <Slider parameter="chew_depth" caption="Depth" padding="0" margin="0" class="Slider" name="Chew Depth" tooltip="Controls how intensely the tape has been chewed up."/> <Slider caption="Frequency" parameter="chew_freq" padding="0" margin="0" class="Slider" name="Chew Frequency" tooltip="Controls the amount of time in between chewed-up sections of tape."/> <Slider caption="Variance" parameter="chew_var" padding="0" margin="0" class="Slider" name="Chew Variance" tooltip="Controls the amount of variance in the chew frequency."/> + <PowerButton margin="0" padding="0" background-color="00000000" max-height="25" + min-height="20" button-color="ff595c6b" button-on-color="FFEAA92C" + parameter="chew_onoff" name="Chew On/Off" tooltip="Turns the chew processing on or off."/> </View> </View> <View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF"> @@ -150,6 +178,9 @@ margin="0" padding="0"/> <Plot source="flutter" plot-decay="0.8" background-color="FF1E1F22" flex-grow="0.8" plot-color="FFEAA92C" plot-fill-color="CC8B3232"/> + <PowerButton margin="0" padding="0" background-color="00000000" max-height="25" + min-height="20" button-color="ff595c6b" button-on-color="FFEAA92C" + parameter="flutter_onoff" name="Wow/Flutter On/Off" tooltip="Turns the wow and flutter processing on or off."/> </View> <View tab-caption="Wow" flex-direction="column" background-color="FF31323A"> <Slider caption="Depth" parameter="wow_depth" max-height="150" class="Slider" @@ -160,6 +191,9 @@ padding="0"/> <Plot source="wow" plot-decay="0.8" flex-grow="0.8" background-color="FF1E1F22" plot-color="FFEAA92C" plot-fill-color="CC8B3232"/> + <PowerButton margin="0" padding="0" background-color="00000000" max-height="25" + min-height="20" button-color="ff595c6b" button-on-color="FFEAA92C" + parameter="flutter_onoff" name="Wow/Flutter On/Off" tooltip="Turns the wow and flutter processing on or off."/> </View> </View> </View> diff --git a/Plugin/Source/GUI/Assets/powerswitch.svg b/Plugin/Source/GUI/Assets/powerswitch.svg @@ -0,0 +1,6 @@ +<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg"> + <g fill-rule="evenodd"> + <path d="M118 40.5l5-5h9.5c-1 0 4.5 5.5 4.5 5.5v81.5l-5.5 4.5H123l-5-4.5v-82z"/> + <path d="M89.887 58.547s-.476 4.792-1.43 6.23c-20.973 13.42-33.367 35.466-34.32 63.264 1.43 41.218 34.32 74.288 74.837 74.288 40.518 0 73.408-33.07 72.932-75.246.476-25.881-13.347-48.886-32.891-62.306-1.43-2.875-2.383-5.751-2.383-5.751l5.243-8.148s1.907-1.917 5.243-1.438c26.218 17.254 43.378 46.01 43.378 78.601 0 50.804-41.47 92.021-91.998 92.021-50.528 0-92.475-41.217-91.998-92.02 0-33.07 17.637-61.827 42.9-79.56 2.86-1.917 5.72 3.355 5.72 3.355l4.767 6.71z"/> + </g> +</svg> diff --git a/Plugin/Source/GUI/MixGroupViz.cpp b/Plugin/Source/GUI/MixGroupViz.cpp @@ -45,6 +45,7 @@ void MixGroupViz::setMixGroupColour (int mixGroupIdx) circleColour = Colour (0x00000000); }; + MessageManagerLock mml; repaint(); } diff --git a/Plugin/Source/GUI/MyLNF.cpp b/Plugin/Source/GUI/MyLNF.cpp @@ -18,7 +18,7 @@ Typeface::Ptr MyLNF::getTypefaceForFont (const Font& font) void MyLNF::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float rotaryStartAngle, - float rotaryEndAngle, juce::Slider&) + float rotaryEndAngle, juce::Slider& s) { int diameter = (width > height)? height : width; if (diameter < 16) return; @@ -35,21 +35,23 @@ void MyLNF::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int he pointer->setTransform (AffineTransform::rotation (MathConstants<float>::twoPi * ((sliderPos - 0.5f) * 300.0f / 360.0f), b.getCentreX(), b.getCentreY())); + const auto alpha = s.isEnabled() ? 1.0f : 0.4f; + auto knobBounds = (bounds * 0.75f).withCentre (centre); - knob->drawWithin (g, knobBounds, RectanglePlacement::stretchToFit, 1.0f); - pointer->drawWithin (g, knobBounds, RectanglePlacement::stretchToFit, 1.0f); + knob->drawWithin (g, knobBounds, RectanglePlacement::stretchToFit, alpha); + pointer->drawWithin (g, knobBounds, RectanglePlacement::stretchToFit, alpha); const auto toAngle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); constexpr float arcFactor = 0.9f; Path valueArc; valueArc.addPieSegment (bounds, rotaryStartAngle, rotaryEndAngle, arcFactor); - g.setColour (Colour (0xff595c6b)); + g.setColour (Colour (0xff595c6b).withAlpha (alpha)); g.fillPath (valueArc); valueArc.clear(); valueArc.addPieSegment (bounds, rotaryStartAngle, toAngle, arcFactor); - g.setColour (Colour (0xff9cbcbd)); + g.setColour (Colour (0xff9cbcbd).withAlpha (alpha)); g.fillPath (valueArc); } @@ -171,10 +173,12 @@ void MyLNF::drawLinearSlider (Graphics& g, int x, int y, int width, int height, Point<float> endPoint (slider.isHorizontal() ? (float) (width + x) : startPoint.x, slider.isHorizontal() ? startPoint.y : (float) y); + const auto alpha = slider.isEnabled() ? 1.0f : 0.4f; + Path backgroundTrack; backgroundTrack.startNewSubPath (startPoint); backgroundTrack.lineTo (endPoint); - g.setColour (slider.findColour (Slider::backgroundColourId)); + g.setColour (slider.findColour (Slider::backgroundColourId).withAlpha (alpha)); g.strokePath (backgroundTrack, { trackWidth, PathStrokeType::curved, PathStrokeType::rounded }); Path valueTrack; @@ -192,12 +196,12 @@ void MyLNF::drawLinearSlider (Graphics& g, int x, int y, int width, int height, valueTrack.startNewSubPath (minPoint); valueTrack.lineTo (maxPoint); - g.setColour (slider.findColour (Slider::trackColourId)); + g.setColour (slider.findColour (Slider::trackColourId).withAlpha (alpha)); g.strokePath (valueTrack, { trackWidth, PathStrokeType::curved, PathStrokeType::rounded }); auto thumbRect = Rectangle<float> (static_cast<float> (thumbWidth), static_cast<float> (thumbWidth)).withCentre (maxPoint); - knob->drawWithin (g, thumbRect, RectanglePlacement::stretchToFit, 1.0f); + knob->drawWithin (g, thumbRect, RectanglePlacement::stretchToFit, alpha); } Slider::SliderLayout MyLNF::getSliderLayout (Slider& slider) diff --git a/Plugin/Source/GUI/OnOffManager.cpp b/Plugin/Source/GUI/OnOffManager.cpp @@ -0,0 +1,68 @@ +#include <unordered_map> +#include "OnOffManager.h" + +namespace +{ + static 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", "Speed_375", "Speed_75", "Speed_15", "Speed_30" }) }, + { String ("chew_onoff"), StringArray ({ "Chew Depth", "Chew Frequency", "Chew Variance" }) }, + { String ("deg_onoff"), StringArray ({ "Depth", "Amount", "Variance" }) }, + { String ("flutter_onoff"), StringArray ({ "Flutter Depth", "Flutter Rate", "Wow Depth", "Wow Rate" }) }, + }; + + void toggleEnableDisable (Component* root, StringArray& compNames, bool shouldBeEnabled) + { + if (root == nullptr || compNames.isEmpty()) + return; + + for (auto child : root->getChildren()) + { + auto compName = child->getName(); + if (compNames.contains (compName)) + { + MessageManagerLock mml; + compNames.removeString (compName); + child->setEnabled (shouldBeEnabled); + continue; + } + + toggleEnableDisable (child, compNames, shouldBeEnabled); + } + } +} + +OnOffManager::OnOffManager (AudioProcessorValueTreeState& vts, const AudioProcessor* proc) : + vts (vts), + proc (proc) +{ + for (auto& trigger : triggerMap) + vts.addParameterListener (trigger.first, this); +} + +OnOffManager::~OnOffManager() +{ + for (auto& trigger : triggerMap) + vts.removeParameterListener (trigger.first, this); +} + +void OnOffManager::setOnOffForNewEditor (AudioProcessorEditor* editor) +{ + for (auto& trigger : triggerMap) + { + auto paramValue = vts.getRawParameterValue (trigger.first)->load(); + 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); +} diff --git a/Plugin/Source/GUI/OnOffManager.h b/Plugin/Source/GUI/OnOffManager.h @@ -0,0 +1,23 @@ +#ifndef ONOFFMANAGER_H_INCLUDED +#define ONOFFMANAGER_H_INCLUDED + +#include <JuceHeader.h> + +/** Utility class to enable/disable components triggered by audio parameter changes */ +class OnOffManager : private AudioProcessorValueTreeState::Listener +{ +public: + OnOffManager (AudioProcessorValueTreeState& vts, const AudioProcessor* proc); + ~OnOffManager(); + + void setOnOffForNewEditor (AudioProcessorEditor* editor); + void parameterChanged (const String &parameterID, float newValue) override; + +private: + AudioProcessorValueTreeState& vts; + const AudioProcessor* proc; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnOffManager) +}; + +#endif // ONOFFMANAGER_H_INCLUDED diff --git a/Plugin/Source/GUI/PowerButton.cpp b/Plugin/Source/GUI/PowerButton.cpp @@ -0,0 +1,83 @@ +#include "PowerButton.h" + +PowerButton::PowerButton() : + button ("", DrawableButton::ImageStretched) +{ + setColour (buttonColourId, Colours::blue); + setColour (buttonOnColourId, Colours::red); + button.setColour (DrawableButton::backgroundColourId, Colours::transparentBlack); + button.setColour (DrawableButton::backgroundOnColourId, Colours::transparentBlack); + + addAndMakeVisible (button); + updateColours(); + button.setClickingTogglesState (true); +} + +void PowerButton::resized() +{ + auto dim = (int) ((float) jmin (getWidth(), getHeight()) * 0.6f); + auto centre = getLocalBounds().getCentre(); + auto topLeft = centre.translated (-dim / 2, -dim / 2); + + button.setBounds (topLeft.x, topLeft.y, dim, dim); +} + +void PowerButton::updateColours() +{ + std::unique_ptr<Drawable> onImage (Drawable::createFromImageData + (BinaryData::powerswitch_svg, BinaryData::powerswitch_svgSize)); + auto offImage = onImage->createCopy(); + + onImage->replaceColour (Colours::black, findColour (buttonOnColourId)); + offImage->replaceColour (Colours::black, findColour (buttonColourId)); + button.setImages (offImage.get(), offImage.get(), onImage.get(), + offImage.get(), onImage.get(), onImage.get(), offImage.get()); +} + +//=============================================================== +PowerButtonItem::PowerButtonItem (foleys::MagicGUIBuilder& builder, const juce::ValueTree& node) : + foleys::GuiItem (builder, node) +{ + setColourTranslation ( + { + { "button-color", PowerButton::buttonColourId }, + { "button-on-color", PowerButton::buttonOnColourId } + } + ); + + addAndMakeVisible (button); +} + +void PowerButtonItem::update() +{ + attachment.reset(); + auto& actualButton = button.getButton(); + + auto parameter = configNode.getProperty (foleys::IDs::parameter, juce::String()).toString(); + if (parameter.isNotEmpty()) + attachment = getMagicState().createAttachment (parameter, actualButton); + + auto triggerID = getProperty (pOnClick).toString(); + if (triggerID.isNotEmpty()) + actualButton.onClick = getMagicState().getTrigger (triggerID); + + button.updateColours(); + + actualButton.setName (magicBuilder.getStyleProperty (foleys::IDs::name, configNode).toString()); + + auto tooltip = magicBuilder.getStyleProperty (foleys::IDs::tooltip, configNode).toString(); + if (tooltip.isNotEmpty()) + actualButton.setTooltip (tooltip); +} + +std::vector<foleys::SettableProperty> PowerButtonItem::getSettableProperties() const +{ + std::vector<foleys::SettableProperty> itemProperties; + + itemProperties.push_back ({ configNode, foleys::IDs::parameter, foleys::SettableProperty::Choice, {}, magicBuilder.createParameterMenu() }); + itemProperties.push_back ({ configNode, pOnClick, foleys::SettableProperty::Choice, {}, magicBuilder.createTriggerMenu() }); + + return itemProperties; +} + +const juce::Identifier PowerButtonItem::pOnClick { "onClick" }; diff --git a/Plugin/Source/GUI/PowerButton.h b/Plugin/Source/GUI/PowerButton.h @@ -0,0 +1,52 @@ +#ifndef POWERBUTTON_H_INCLUDED +#define POWERBUTTON_H_INCLUDED + +#include <JuceHeader.h> + +class PowerButton : public Component +{ +public: + PowerButton(); + + enum ColourIDs + { + buttonColourId, + buttonOnColourId, + }; + + void resized(); + void updateColours(); + DrawableButton& getButton() { return button; } + +private: + DrawableButton button; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PowerButton) +}; + +//=============================================================== +class PowerButtonItem : public foleys::GuiItem +{ +public: + FOLEYS_DECLARE_GUI_FACTORY (PowerButtonItem) + + static const Identifier pOnClick; + + PowerButtonItem (foleys::MagicGUIBuilder& builder, const juce::ValueTree& node); + + void update() override; + std::vector<foleys::SettableProperty> getSettableProperties() const override; + + juce::Component* getWrappedComponent() override + { + return &button; + } + +private: + PowerButton button; + std::unique_ptr<juce::AudioProcessorValueTreeState::ButtonAttachment> attachment; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PowerButtonItem) +}; + +#endif // POWERBUTTON_H_INCLUDED diff --git a/Plugin/Source/GUI/ScreenshotHelper.cpp b/Plugin/Source/GUI/ScreenshotHelper.cpp @@ -61,7 +61,18 @@ void screenshotTab (foleys::Container* container, int tabIdx) void takeScreenshots (std::unique_ptr<ChowtapeModelAudioProcessor> plugin) { processAudio (plugin.get()); - std::unique_ptr<AudioProcessorEditor> editor (plugin->createEditor()); + std::unique_ptr<AudioProcessorEditor> editor (plugin->createEditorIfNeeded()); + + // make sure all plugin sections are enabled + StringArray onOffIDs { "ifilt_onoff", "hyst_onoff", "tone_onoff", "loss_onoff", "chew_onoff", "deg_onoff", "flutter_onoff" }; + for (auto param : plugin->getParameters()) + { + if (auto* paramCast = dynamic_cast<RangedAudioParameter*> (param)) + { + if (onOffIDs.contains (paramCast->paramID)) + paramCast->setValueNotifyingHost (1.0f); + } + } // full screenshot screenshotForBounds (editor.get(), editor->getLocalBounds(), "full_gui.png"); @@ -73,6 +84,8 @@ void takeScreenshots (std::unique_ptr<ChowtapeModelAudioProcessor> plugin) for (auto c : tabbedComps) for (int i = 0; i < c->tabbedButtons->getNumTabs(); ++i) screenshotTab (c, i); + + plugin->editorBeingDeleted (editor.get()); } File getScreenshotFolder() diff --git a/Plugin/Source/GUI/ScreenshotHelper.h b/Plugin/Source/GUI/ScreenshotHelper.h @@ -10,7 +10,7 @@ #define _XKEYCHECK_H #define private public #include "../PluginProcessor.h" -#define private private +#undef private namespace ScreenshotHelper { diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -13,6 +13,7 @@ #include "GUI/TitleComp.h" #include "GUI/TooltipComp.h" #include "GUI/MixGroupViz.h" +#include "GUI/PowerButton.h" #include "GUI/ScreenshotHelper.h" namespace @@ -39,6 +40,7 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() degrade (vts), chewer (vts), flutter (vts), + onOffManager (vts, this), mixGroupsController (vts, this) { for (int ch = 0; ch < 2; ++ch) @@ -160,7 +162,7 @@ void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesP toneControl.prepare (sampleRate); hysteresis.prepareToPlay (sampleRate, samplesPerBlock); degrade.prepareToPlay (sampleRate, samplesPerBlock); - chewer.prepare (sampleRate); + chewer.prepare (sampleRate, samplesPerBlock); for (int ch = 0; ch < 2; ++ch) lossFilter[ch]->prepare ((float) sampleRate, samplesPerBlock); @@ -214,6 +216,15 @@ bool ChowtapeModelAudioProcessor::isBusesLayoutSupported (const BusesLayout& lay } #endif +void ChowtapeModelAudioProcessor::processBlockBypassed (AudioBuffer<float>& buffer, MidiBuffer&) +{ + ScopedNoDenormals noDenormals; + + dryBuffer.makeCopyOf (buffer, true); + latencyCompensation(); + buffer.makeCopyOf (dryBuffer, true); +} + void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) { ScopedNoDenormals noDenormals; @@ -268,10 +279,9 @@ void ChowtapeModelAudioProcessor::latencyCompensation() dryDelay.process (dsp::ProcessContextReplacing<float> { block }); } -//============================================================================== bool ChowtapeModelAudioProcessor::hasEditor() const { - return true; // (change this to false if you choose to not supply an editor) + return true; } AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor() @@ -283,6 +293,7 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor() builder->registerFactory ("InfoComp", &InfoItem::factory); builder->registerFactory ("TitleComp", &TitleItem::factory); builder->registerFactory ("MixGroupViz", &MixGroupVizItem::factory); + builder->registerFactory ("PowerButton", &PowerButtonItem::factory); builder->registerJUCELookAndFeels(); builder->registerLookAndFeel ("MyLNF", std::make_unique<MyLNF>()); @@ -301,6 +312,7 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor() } auto* editor = new foleys::MagicPluginEditor (magicState, BinaryData::gui_xml, BinaryData::gui_xmlSize, std::move (builder)); + onOffManager.setOnOffForNewEditor (editor); updater.showUpdaterScreen (editor); return editor; } diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -19,12 +19,13 @@ #include "Processors/Degrade/DegradeProcessor.h" #include "Processors/Chew/ChewProcessor.h" #include "Processors/DryWetProcessor.h" -#include "Processors/InputFilters.h" +#include "Processors/Input_Filters/InputFilters.h" #include "Presets/PresetManager.h" #include "GUI/MyLNF.h" #include "GUI/AutoUpdating.h" #include "GUI/TapeScope.h" #include "MixGroups/MixGroupsController.h" +#include "GUI/OnOffManager.h" //============================================================================== /** @@ -45,6 +46,7 @@ public: #endif void processBlock (AudioBuffer<float>&, MidiBuffer&) override; + void processBlockBypassed (AudioBuffer<float>&, MidiBuffer&) override; //============================================================================== AudioProcessorEditor* createEditor() override; @@ -90,6 +92,7 @@ private: DryWetProcessor dryWet; dsp::DelayLine<float, dsp::DelayLineInterpolationTypes::Lagrange3rd> dryDelay { 1 << 21 }; GainProcessor outGain; + OnOffManager onOffManager; AudioBuffer<float> dryBuffer; diff --git a/Plugin/Source/Processors/BypassProcessor.h b/Plugin/Source/Processors/BypassProcessor.h @@ -0,0 +1,66 @@ +#ifndef BYPASSPROCESSOR_H_INCLUDED +#define BYPASSPROCESSOR_H_INCLUDED + +#include <JuceHeader.h> + +/** Utility class for *smoothly* bypassing a processor */ +class BypassProcessor +{ +public: + BypassProcessor() = default; + + static bool toBool (const std::atomic<float>* param) + { + return static_cast<bool> (param->load()); + } + + void prepare (int samplesPerBlock, bool onOffParam) + { + prevOnOffParam = onOffParam; + fadeBuffer.setSize (2, samplesPerBlock); + } + + /** + * Call this at the start of your processBlock(). + * If it returns false, you can safely skip all other + * processing. + */ + bool processBlockIn (AudioBuffer<float>& block, bool onOffParam) + { + if (onOffParam == false && prevOnOffParam == false) + return false; + + if (onOffParam != prevOnOffParam) + fadeBuffer.makeCopyOf (block, true); + + return true; + } + + void processBlockOut (AudioBuffer<float>& block, bool onOffParam) + { + if (onOffParam == prevOnOffParam) + return; + + const auto numChannels = block.getNumChannels(); + const auto numSamples = block.getNumSamples(); + + float startGain = onOffParam == false ? 1.0f // fade out + : 0.0f; // fade in + float endGain = 1.0f - startGain; + + block.applyGainRamp (0, numSamples, startGain, endGain); + for (int ch = 0; ch < numChannels; ++ch) + block.addFromWithRamp (ch, 0, fadeBuffer.getReadPointer (ch), numSamples, + 1.0f - startGain, 1.0f - endGain); + + prevOnOffParam = onOffParam; + } + +private: + bool prevOnOffParam = false; + AudioBuffer<float> fadeBuffer; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BypassProcessor) +}; + +#endif // BYPASSPROCESSOR_H_INCLUDED diff --git a/Plugin/Source/Processors/Chew/ChewProcessor.cpp b/Plugin/Source/Processors/Chew/ChewProcessor.cpp @@ -5,6 +5,7 @@ ChewProcessor::ChewProcessor (AudioProcessorValueTreeState& vts) depth = vts.getRawParameterValue ("chew_depth"); freq = vts.getRawParameterValue ("chew_freq"); var = vts.getRawParameterValue ("chew_var"); + onOff = vts.getRawParameterValue ("chew_onoff"); } void ChewProcessor::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) @@ -12,9 +13,10 @@ void ChewProcessor::createParameterLayout (std::vector<std::unique_ptr<RangedAud params.push_back (std::make_unique<AudioParameterFloat> ("chew_depth", "Depth", 0.0f, 1.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("chew_freq", "Freq", 0.0f, 1.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("chew_var", "Variance", 0.0f, 1.0f, 0.0f)); + params.push_back (std::make_unique<AudioParameterBool> ("chew_onoff", "On/Off", false)); } -void ChewProcessor::prepare (double sr) +void ChewProcessor::prepare (double sr, int samplesPerBlock) { sampleRate = (float) sr; @@ -25,36 +27,41 @@ void ChewProcessor::prepare (double sr) isCrinkled = false; samplesUntilChange = getDryTime(); sampleCounter = 0; + + bypass.prepare (samplesPerBlock, bypass.toBool (onOff)); } void ChewProcessor::processBlock (AudioBuffer<float>& buffer) { - if (depth->load() == 0.0f && freq->load() == 0.0f) + if (! bypass.processBlockIn (buffer, bypass.toBool (onOff))) return; const int shortBlockSize = 64; if (buffer.getNumSamples() <= shortBlockSize) { processShortBlock (buffer); - return; } - - int sampleIdx = 0; - for(; sampleIdx + shortBlockSize <= buffer.getNumSamples(); sampleIdx += shortBlockSize) + else { - AudioBuffer<float> shortBuff (buffer.getArrayOfWritePointers(), - buffer.getNumChannels(), sampleIdx, shortBlockSize); + int sampleIdx = 0; + for(; sampleIdx + shortBlockSize <= buffer.getNumSamples(); sampleIdx += shortBlockSize) + { + AudioBuffer<float> shortBuff (buffer.getArrayOfWritePointers(), + buffer.getNumChannels(), sampleIdx, shortBlockSize); - processShortBlock (shortBuff); - } + processShortBlock (shortBuff); + } - if (sampleIdx < buffer.getNumSamples()) - { - AudioBuffer<float> shortBuff (buffer.getArrayOfWritePointers(), - buffer.getNumChannels(), sampleIdx, buffer.getNumSamples() - sampleIdx); + if (sampleIdx < buffer.getNumSamples()) + { + AudioBuffer<float> shortBuff (buffer.getArrayOfWritePointers(), + buffer.getNumChannels(), sampleIdx, buffer.getNumSamples() - sampleIdx); - processShortBlock (shortBuff); + processShortBlock (shortBuff); + } } + + bypass.processBlockOut (buffer, bypass.toBool (onOff)); } void ChewProcessor::processShortBlock (AudioBuffer<float>& buffer) diff --git a/Plugin/Source/Processors/Chew/ChewProcessor.h b/Plugin/Source/Processors/Chew/ChewProcessor.h @@ -3,6 +3,7 @@ #include "Dropout.h" #include "../Degrade/DegradeFilter.h" +#include "../BypassProcessor.h" class ChewProcessor { @@ -11,11 +12,12 @@ public: static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepare (double sr); + void prepare (double sr, int samplesPerBlock); void processBlock (AudioBuffer<float>& buffer); void processShortBlock (AudioBuffer<float>& buffer); private: + std::atomic<float>* onOff = nullptr; std::atomic<float>* depth = nullptr; std::atomic<float>* freq = nullptr; std::atomic<float>* var = nullptr; @@ -31,6 +33,7 @@ private: int sampleCounter = 0; float sampleRate = 44100.0f; + BypassProcessor bypass; inline int getDryTime() { diff --git a/Plugin/Source/Processors/Degrade/DegradeProcessor.cpp b/Plugin/Source/Processors/Degrade/DegradeProcessor.cpp @@ -2,6 +2,7 @@ DegradeProcessor::DegradeProcessor (AudioProcessorValueTreeState& vts) { + onOffParam = vts.getRawParameterValue ("deg_onoff"); depthParam = vts.getRawParameterValue ("deg_depth"); amtParam = vts.getRawParameterValue ("deg_amt"); varParam = vts.getRawParameterValue ("deg_var"); @@ -12,6 +13,7 @@ void DegradeProcessor::createParameterLayout (std::vector<std::unique_ptr<Ranged params.push_back (std::make_unique<AudioParameterFloat> ("deg_depth", "Depth", 0.0f, 1.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("deg_amt", "Amount", 0.0f, 1.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("deg_var", "Variance", 0.0f, 1.0f, 0.0f)); + params.push_back (std::make_unique<AudioParameterBool> ("deg_onoff", "On/Off", false)); } void DegradeProcessor::cookParams() @@ -40,11 +42,12 @@ void DegradeProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) } gainProc.prepareToPlay (sampleRate, samplesPerBlock); + bypass.prepare (samplesPerBlock, bypass.toBool (onOffParam)); } void DegradeProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi) { - if (depthParam->load() == 0.0f && amtParam->load() == 0.0f) + if (! bypass.processBlockIn (buffer, bypass.toBool (onOffParam))) return; cookParams(); @@ -56,4 +59,5 @@ void DegradeProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& mid } gainProc.processBlock (buffer, midi); + bypass.processBlockOut (buffer, bypass.toBool (onOffParam)); } diff --git a/Plugin/Source/Processors/Degrade/DegradeProcessor.h b/Plugin/Source/Processors/Degrade/DegradeProcessor.h @@ -2,6 +2,7 @@ #define DEGRADEPROCESSOR_H_INCLUDED #include "../GainProcessor.h" +#include "../BypassProcessor.h" #include "DegradeNoise.h" #include "DegradeFilter.h" @@ -17,6 +18,7 @@ public: void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi); private: + std::atomic<float>* onOffParam = nullptr; std::atomic<float>* depthParam = nullptr; std::atomic<float>* amtParam = nullptr; std::atomic<float>* varParam = nullptr; @@ -29,6 +31,8 @@ private: float fs = 44100.0f; + BypassProcessor bypass; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DegradeProcessor) }; diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp @@ -12,6 +12,7 @@ HysteresisProcessor::HysteresisProcessor (AudioProcessorValueTreeState& vts) widthParam = vts.getRawParameterValue ("width"); osParam = vts.getRawParameterValue ("os"); modeParam = vts.getRawParameterValue ("mode"); + onOffParam = vts.getRawParameterValue ("hyst_onoff"); for (int i = 0; i < 5; ++i) overSample[i] = std::make_unique<dsp::Oversampling<float>> @@ -34,6 +35,7 @@ void HysteresisProcessor::createParameterLayout (std::vector<std::unique_ptr<Ran params.push_back (std::make_unique<AudioParameterChoice> ("mode", "Mode", StringArray ({"RK2", "RK4", "NR4", "NR8", "V1"}), 0)); params.push_back (std::make_unique<AudioParameterChoice> ("os", "Oversampling", StringArray ({"1x", "2x", "4x", "8x", "16x"}), 1)); + params.push_back (std::make_unique<AudioParameterBool> ("hyst_onoff", "On/Off", true)); } void HysteresisProcessor::setSolver (int newSolver) @@ -151,6 +153,8 @@ void HysteresisProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) for (int ch = 0; ch < 2; ++ch) dcBlocker[ch].prepare (sampleRate, dcFreq); + + bypass.prepare (samplesPerBlock, bypass.toBool (onOffParam)); } void HysteresisProcessor::releaseResources() @@ -162,11 +166,15 @@ void HysteresisProcessor::releaseResources() float HysteresisProcessor::getLatencySamples() const noexcept { // latency of oversampling + fudge factor for hysteresis - return overSample[curOS]->getLatencyInSamples() + 1.4f; + return onOffParam->load() == 1.0f ? overSample[curOS]->getLatencyInSamples() + 1.4f // on + : 0.0f; // off } void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midi*/) { + if (! bypass.processBlockIn (buffer, bypass.toBool (onOffParam))) + return; + setSolver ((int) *modeParam); setDrive (*driveParam); setSaturation (*satParam); @@ -209,6 +217,8 @@ void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& overSample[curOS]->processSamplesDown (block); applyDCBlockers (buffer); + + bypass.processBlockOut (buffer, bypass.toBool (onOffParam)); } void HysteresisProcessor::process (dsp::AudioBlock<float>& block) diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h @@ -3,6 +3,7 @@ #include "HysteresisProcessing.h" #include "DCBlocker.h" +#include "../BypassProcessor.h" /* Hysteresis Processor for tape. */ class HysteresisProcessor @@ -43,6 +44,7 @@ private: std::atomic<float>* widthParam = nullptr; std::atomic<float>* osParam = nullptr; std::atomic<float>* modeParam = nullptr; + std::atomic<float>* onOffParam = nullptr; SmoothedValue<float, ValueSmoothingTypes::Linear> drive[2]; SmoothedValue<float, ValueSmoothingTypes::Linear> width[2]; @@ -64,6 +66,8 @@ private: bool wasV1 = false, useV1 = false; float clipLevel = 20.0f; + BypassProcessor bypass; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HysteresisProcessor) }; diff --git a/Plugin/Source/Processors/Hysteresis/ToneControl.cpp b/Plugin/Source/Processors/Hysteresis/ToneControl.cpp @@ -78,6 +78,7 @@ ToneControl::ToneControl (AudioProcessorValueTreeState& vts) bassParam = vts.getRawParameterValue ("h_bass"); trebleParam = vts.getRawParameterValue ("h_treble"); tFreqParam = vts.getRawParameterValue ("h_tfreq"); + onOffParam = vts.getRawParameterValue ("tone_onoff"); } void ToneControl::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) @@ -85,6 +86,7 @@ void ToneControl::createParameterLayout (std::vector<std::unique_ptr<RangedAudio NormalisableRange freqRange { 100.0f, 4000.0f }; freqRange.setSkewForCentre (transFreq); + params.push_back (std::make_unique<AudioParameterBool> ("tone_onoff", "On/Off", true)); params.push_back (std::make_unique<AudioParameterFloat> ("h_bass", "Bass", -1.0f, 1.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("h_treble", "Treble", -1.0f, 1.0f, 0.0f)); params.push_back (std::make_unique<AudioParameterFloat> ("h_tfreq", "Frequency", freqRange, transFreq, @@ -103,8 +105,16 @@ void ToneControl::prepare (double sampleRate) void ToneControl::processBlockIn (AudioBuffer<float>& buffer) { - toneIn.setLowGain (dbScale * bassParam->load()); - toneIn.setHighGain (dbScale * trebleParam->load()); + if (static_cast<bool> (onOffParam->load())) + { + toneIn.setLowGain (dbScale * bassParam->load()); + toneIn.setHighGain (dbScale * trebleParam->load()); + } + else + { + toneIn.setLowGain (0.0f); + toneIn.setHighGain (0.0f); + } toneIn.setTransFreq (tFreqParam->load()); toneIn.processBlock (buffer); @@ -112,8 +122,16 @@ void ToneControl::processBlockIn (AudioBuffer<float>& buffer) void ToneControl::processBlockOut (AudioBuffer<float>& buffer) { - toneOut.setLowGain (-1.0f * dbScale * bassParam->load()); - toneOut.setHighGain (-1.0f * dbScale * trebleParam->load()); + if (static_cast<bool> (onOffParam->load())) + { + toneOut.setLowGain (-1.0f * dbScale * bassParam->load()); + toneOut.setHighGain (-1.0f * dbScale * trebleParam->load()); + } + else + { + toneOut.setLowGain (0.0f); + toneOut.setHighGain (0.0f); + } toneOut.setTransFreq (tFreqParam->load()); toneOut.processBlock (buffer); diff --git a/Plugin/Source/Processors/Hysteresis/ToneControl.h b/Plugin/Source/Processors/Hysteresis/ToneControl.h @@ -34,6 +34,7 @@ public: private: ToneStage toneIn, toneOut; + std::atomic<float>* onOffParam = nullptr; std::atomic<float>* bassParam = nullptr; std::atomic<float>* trebleParam = nullptr; std::atomic<float>* tFreqParam = nullptr; diff --git a/Plugin/Source/Processors/InputFilters.cpp b/Plugin/Source/Processors/InputFilters.cpp @@ -1,111 +0,0 @@ -#include "InputFilters.h" - -namespace -{ - constexpr float minFreq = 20.0f; - constexpr float maxFreq = 22000.0f; -} - -InputFilters::InputFilters (AudioProcessorValueTreeState& vts) -{ - lowCutParam = vts.getRawParameterValue ("ifilt_low"); - highCutParam = vts.getRawParameterValue ("ifilt_high"); - makeupParam = vts.getRawParameterValue ("ifilt_makeup"); -} - -void InputFilters::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) -{ - NormalisableRange lowFreqRange { minFreq, 2000.0f }; - lowFreqRange.setSkewForCentre (250.0f); - - NormalisableRange highFreqRange { 2000.0f, maxFreq }; - highFreqRange.setSkewForCentre (10000.0f); - - auto freqToString = [] (float freq, int) -> String { - String suffix = " Hz"; - if (freq > 1000.0f) { freq /= 1000.0f; suffix = " kHz"; } - return String (freq, 2, false) + suffix; - }; - - auto stringToFreq = [] (const String& string) -> float { - float freq = string.getFloatValue(); - if (string.getLastCharacter() == 'k') - freq *= 1000.0f; - - return freq; - }; - - params.push_back (std::make_unique<AudioParameterFloat> ("ifilt_low", "Low Cut", lowFreqRange, - minFreq, String(), AudioProcessorParameter::genericParameter, freqToString, stringToFreq)); - params.push_back (std::make_unique<AudioParameterFloat> ("ifilt_high", "High Cut", highFreqRange, - maxFreq, String(), AudioProcessorParameter::genericParameter, freqToString, stringToFreq)); - params.push_back (std::make_unique<AudioParameterBool> ("ifilt_makeup", "Cut Makeup", false)); -} - -void InputFilters::prepareToPlay (double sampleRate, int samplesPerBlock) -{ - fs = (float) sampleRate; - dsp::ProcessSpec spec { sampleRate, (uint32) samplesPerBlock, 2 }; - lowCutFilter.prepare (spec); - highCutFilter.prepare (spec); - makeupDelay.prepare (spec); - - lowCutBuffer .setSize (2, samplesPerBlock); - highCutBuffer.setSize (2, samplesPerBlock); - makeupBuffer .setSize (2, samplesPerBlock); - - internalBypass = false; -} - -void InputFilters::processBlock (AudioBuffer<float>& buffer) -{ - if (*lowCutParam == minFreq && *highCutParam == maxFreq) - { - internalBypass = true; - return; - } - - internalBypass = false; - - lowCutFilter.setCutoff (lowCutParam->load()); - highCutFilter.setCutoff (jmin (highCutParam->load(), fs * 0.48f)); - - for (int ch = 0; ch < buffer.getNumChannels(); ++ch) - { - auto* data = buffer.getWritePointer (ch); - auto* cutLowSignal = lowCutBuffer.getWritePointer (ch); - auto* cutHighSignal = highCutBuffer.getWritePointer (ch); - - for (int n = 0; n < buffer.getNumSamples(); ++n) - { - lowCutFilter.processSample (ch, data[n], cutLowSignal[n], data[n]); - highCutFilter.processSample (ch, data[n], data[n], cutHighSignal[n]); - } - } - - lowCutFilter.snapToZero(); - highCutFilter.snapToZero(); -} - -void InputFilters::processBlockMakeup (AudioBuffer<float>& buffer) -{ - if (! static_cast<bool> (makeupParam->load()) || internalBypass) - return; - - // compile makeup signal - dsp::AudioBlock<float> lowCutBlock (lowCutBuffer); - dsp::AudioBlock<float> highCutBlock (highCutBuffer); - dsp::AudioBlock<float> makeupBlock (makeupBuffer); - - makeupBlock.fill (0.0f); - makeupBlock += lowCutBlock; - makeupBlock += highCutBlock; - - // delay makeup signal to be in phase with everything else - dsp::ProcessContextReplacing<float> context (makeupBlock); - makeupDelay.process (context); - - // add makeup back to main buffer - dsp::AudioBlock<float> outputBlock (buffer); - outputBlock += makeupBlock; -} diff --git a/Plugin/Source/Processors/InputFilters.h b/Plugin/Source/Processors/InputFilters.h @@ -1,35 +0,0 @@ -#ifndef INPUTFILTERS_H_INCLUDED -#define INPUTFILTERS_H_INCLUDED - -#include "LinkwitzRileyFilter.h" - -class InputFilters -{ -public: - InputFilters (AudioProcessorValueTreeState& vts); - - static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); - void prepareToPlay (double sampleRate, int samplesPerBlock); - void setMakeupDelay (float newDelaySamples) { makeupDelay.setDelay (newDelaySamples); } - - void processBlock (AudioBuffer<float>& buffer); - void processBlockMakeup (AudioBuffer<float>& buffer); - -private: - std::atomic<float>* lowCutParam = nullptr; - std::atomic<float>* highCutParam = nullptr; - std::atomic<float>* makeupParam = nullptr; - bool internalBypass = false; - - float fs = 44100.0f; - LinkwitzRileyFilter<float, 2> lowCutFilter; - LinkwitzRileyFilter<float, 2> highCutFilter; - dsp::DelayLine<float, dsp::DelayLineInterpolationTypes::Lagrange3rd> makeupDelay { 1 << 21 }; - - AudioBuffer<float> lowCutBuffer, highCutBuffer, makeupBuffer; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputFilters) -}; - -#endif // !INPUTFILTERS_H_INCLUDED - diff --git a/Plugin/Source/Processors/Input_Filters/InputFilters.cpp b/Plugin/Source/Processors/Input_Filters/InputFilters.cpp @@ -0,0 +1,119 @@ +#include "InputFilters.h" + +namespace +{ + constexpr float minFreq = 20.0f; + constexpr float maxFreq = 22000.0f; +} + +InputFilters::InputFilters (AudioProcessorValueTreeState& vts) +{ + lowCutParam = vts.getRawParameterValue ("ifilt_low"); + highCutParam = vts.getRawParameterValue ("ifilt_high"); + makeupParam = vts.getRawParameterValue ("ifilt_makeup"); + onOffParam = vts.getRawParameterValue ("ifilt_onoff"); +} + +void InputFilters::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params) +{ + NormalisableRange lowFreqRange { minFreq, 2000.0f }; + lowFreqRange.setSkewForCentre (250.0f); + + NormalisableRange highFreqRange { 2000.0f, maxFreq }; + highFreqRange.setSkewForCentre (10000.0f); + + auto freqToString = [] (float freq, int) -> String { + String suffix = " Hz"; + if (freq > 1000.0f) { freq /= 1000.0f; suffix = " kHz"; } + return String (freq, 2, false) + suffix; + }; + + auto stringToFreq = [] (const String& string) -> float { + float freq = string.getFloatValue(); + if (string.getLastCharacter() == 'k') + freq *= 1000.0f; + + return freq; + }; + + params.push_back (std::make_unique<AudioParameterFloat> ("ifilt_low", "Low Cut", lowFreqRange, + minFreq, String(), AudioProcessorParameter::genericParameter, freqToString, stringToFreq)); + params.push_back (std::make_unique<AudioParameterFloat> ("ifilt_high", "High Cut", highFreqRange, + maxFreq, String(), AudioProcessorParameter::genericParameter, freqToString, stringToFreq)); + params.push_back (std::make_unique<AudioParameterBool> ("ifilt_makeup", "Cut Makeup", false)); + params.push_back (std::make_unique<AudioParameterBool> ("ifilt_onoff", "On/Off", false)); +} + +void InputFilters::prepareToPlay (double sampleRate, int samplesPerBlock) +{ + fs = (float) sampleRate; + dsp::ProcessSpec spec { sampleRate, (uint32) samplesPerBlock, 2 }; + lowCutFilter.prepare (spec); + highCutFilter.prepare (spec); + makeupDelay.prepare (spec); + + lowCutBuffer .setSize (2, samplesPerBlock); + highCutBuffer.setSize (2, samplesPerBlock); + makeupBuffer .setSize (2, samplesPerBlock); + + bypass.prepare (samplesPerBlock, bypass.toBool (onOffParam)); + makeupBypass.prepare (samplesPerBlock, bypass.toBool (onOffParam)); +} + +void InputFilters::processBlock (AudioBuffer<float>& buffer) +{ + if (! bypass.processBlockIn (buffer, bypass.toBool (onOffParam))) + return; + + lowCutFilter.setCutoff (lowCutParam->load()); + highCutFilter.setCutoff (jmin (highCutParam->load(), fs * 0.48f)); + + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + { + auto* data = buffer.getWritePointer (ch); + auto* cutLowSignal = lowCutBuffer.getWritePointer (ch); + auto* cutHighSignal = highCutBuffer.getWritePointer (ch); + + for (int n = 0; n < buffer.getNumSamples(); ++n) + { + lowCutFilter.processSample (ch, data[n], cutLowSignal[n], data[n]); + highCutFilter.processSample (ch, data[n], data[n], cutHighSignal[n]); + } + } + + bypass.processBlockOut (buffer, bypass.toBool (onOffParam)); + + lowCutFilter.snapToZero(); + highCutFilter.snapToZero(); +} + +void InputFilters::processBlockMakeup (AudioBuffer<float>& buffer) +{ + if (! makeupBypass.processBlockIn (buffer, bypass.toBool (onOffParam))) + return; + + if (! static_cast<bool> (makeupParam->load())) + { + makeupBypass.processBlockOut (buffer, bypass.toBool (onOffParam)); + return; + } + + // compile makeup signal + dsp::AudioBlock<float> lowCutBlock (lowCutBuffer); + dsp::AudioBlock<float> highCutBlock (highCutBuffer); + dsp::AudioBlock<float> makeupBlock (makeupBuffer); + + makeupBlock.fill (0.0f); + makeupBlock += lowCutBlock; + makeupBlock += highCutBlock; + + // delay makeup signal to be in phase with everything else + dsp::ProcessContextReplacing<float> context (makeupBlock); + makeupDelay.process (context); + + // add makeup back to main buffer + dsp::AudioBlock<float> outputBlock (buffer); + outputBlock += makeupBlock; + + makeupBypass.processBlockOut (buffer, bypass.toBool (onOffParam)); +} diff --git a/Plugin/Source/Processors/Input_Filters/InputFilters.h b/Plugin/Source/Processors/Input_Filters/InputFilters.h @@ -0,0 +1,38 @@ +#ifndef INPUTFILTERS_H_INCLUDED +#define INPUTFILTERS_H_INCLUDED + +#include "LinkwitzRileyFilter.h" +#include "../BypassProcessor.h" + +class InputFilters +{ +public: + InputFilters (AudioProcessorValueTreeState& vts); + + static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); + void prepareToPlay (double sampleRate, int samplesPerBlock); + void setMakeupDelay (float newDelaySamples) { makeupDelay.setDelay (newDelaySamples); } + + void processBlock (AudioBuffer<float>& buffer); + void processBlockMakeup (AudioBuffer<float>& buffer); + +private: + std::atomic<float>* onOffParam = nullptr; + std::atomic<float>* lowCutParam = nullptr; + std::atomic<float>* highCutParam = nullptr; + std::atomic<float>* makeupParam = nullptr; + + float fs = 44100.0f; + LinkwitzRileyFilter<float, 2> lowCutFilter; + LinkwitzRileyFilter<float, 2> highCutFilter; + dsp::DelayLine<float, dsp::DelayLineInterpolationTypes::Lagrange3rd> makeupDelay { 1 << 21 }; + + AudioBuffer<float> lowCutBuffer, highCutBuffer, makeupBuffer; + BypassProcessor bypass; + BypassProcessor makeupBypass; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InputFilters) +}; + +#endif // !INPUTFILTERS_H_INCLUDED + diff --git a/Plugin/Source/Processors/LinkwitzRileyFilter.h b/Plugin/Source/Processors/Input_Filters/LinkwitzRileyFilter.h diff --git a/Plugin/Source/Processors/Loss_Effects/LossFilter.cpp b/Plugin/Source/Processors/Loss_Effects/LossFilter.cpp @@ -7,6 +7,7 @@ LossFilter::LossFilter (AudioProcessorValueTreeState& vts, int order) : spacing = vts.getRawParameterValue ("spacing"); thickness = vts.getRawParameterValue ("thick"); gap = vts.getRawParameterValue ("gap"); + onOff = vts.getRawParameterValue ("loss_onoff"); filters.add (new FIRFilter (order)); filters.add (new FIRFilter (order)); @@ -25,10 +26,10 @@ void LossFilter::createParameterLayout (std::vector<std::unique_ptr<RangedAudioP speedRange.setSkewForCentre (15.0f); NormalisableRange<float> spaceRange (minDist, 20.0f); - spaceRange.setSkewForCentre (5.0f); + spaceRange.setSkewForCentre (10.0f); NormalisableRange<float> thickRange (minDist, 50.0f); - thickRange.setSkewForCentre (5.0f); + thickRange.setSkewForCentre (15.0f); NormalisableRange<float> gapRange (1.0f, 50.0f); gapRange.setSkewForCentre (10.0f); @@ -48,6 +49,14 @@ void LossFilter::createParameterLayout (std::vector<std::unique_ptr<RangedAudioP params.push_back (std::make_unique<AudioParameterFloat> ("gap", "Gap [microns]", gapRange, 1.0f, String(), AudioProcessorParameter::genericParameter, valueToString, stringToValue)); + + params.push_back (std::make_unique<AudioParameterBool> ("loss_onoff", "On/Off", true)); +} + +float LossFilter::getLatencySamples() const noexcept +{ + return onOff->load() == 1.0f ? (float) curOrder / 2.0f // on + : 0.0f; // off } void LossFilter::prepare (float sampleRate, int samplesPerBlock) @@ -77,7 +86,7 @@ void LossFilter::prepare (float sampleRate, int samplesPerBlock) prevThickness = *thickness; prevGap = *gap; - starting = true; + bypass.prepare (samplesPerBlock, bypass.toBool (onOff)); } static void calcHeadBumpFilter (float speedIps, float gapMeters, double fs, dsp::IIR::Filter<float>& filter) @@ -123,6 +132,10 @@ void LossFilter::calcCoefs() void LossFilter::processBlock (float* buffer, const int numSamples) { + AudioBuffer<float> bufferCast (&buffer, 1, numSamples); + if (! bypass.processBlockIn (bufferCast, bypass.toBool (onOff))) + return; + if ((*speed != prevSpeed || *spacing != prevSpacing || *thickness != prevThickness || *gap != prevGap) && fadeCount == 0) { @@ -147,18 +160,12 @@ void LossFilter::processBlock (float* buffer, const int numSamples) filters[! activeFilter]->processBypassed (buffer, numSamples); } - if (! starting) { filters[activeFilter]->process (buffer, numSamples); dsp::AudioBlock<float> block (&buffer, 1, numSamples); dsp::ProcessContextReplacing<float> ctx (block); bumpFilter[activeFilter].process (ctx); } - else - { - starting = false; - filters[activeFilter]->processBypassed (buffer, numSamples); - } if (fadeCount > 0) { @@ -181,4 +188,6 @@ void LossFilter::processBlock (float* buffer, const int numSamples) if (fadeCount == 0) activeFilter = ! activeFilter; } + + bypass.processBlockOut (bufferCast, bypass.toBool (onOff)); } diff --git a/Plugin/Source/Processors/Loss_Effects/LossFilter.h b/Plugin/Source/Processors/Loss_Effects/LossFilter.h @@ -2,6 +2,7 @@ #define LOSSFILTER_H_INCLUDED #include "FIRFilter.h" +#include "../BypassProcessor.h" class LossFilter { @@ -14,7 +15,7 @@ public: void prepare (float sampleRate, int samplesPerBlock); void calcCoefs(); void processBlock (float* buffer, const int numSamples); - float getLatencySamples() const noexcept { return (float) curOrder / 2.0f; } + float getLatencySamples() const noexcept; private: OwnedArray<FIRFilter> filters; @@ -23,8 +24,8 @@ private: int fadeCount = 0; const int fadeLength = 1024; AudioBuffer<float> fadeBuffer; - bool starting = false; + std::atomic<float>* onOff = nullptr; std::atomic<float>* speed = nullptr; std::atomic<float>* spacing = nullptr; std::atomic<float>* thickness = nullptr; @@ -44,6 +45,8 @@ private: Array<float> currentCoefs; Array<float> Hcoefs; + BypassProcessor bypass; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LossFilter) }; diff --git a/Plugin/Source/Processors/Timing_Effects/Flutter.cpp b/Plugin/Source/Processors/Timing_Effects/Flutter.cpp @@ -14,6 +14,8 @@ Flutter::Flutter (AudioProcessorValueTreeState& vts) wowRate = vts.getRawParameterValue ("wow_rate"); wowDepth = vts.getRawParameterValue ("wow_depth"); + flutterOnOff = vts.getRawParameterValue ("flutter_onoff"); + depthSlewWow[0].setCurrentAndTargetValue (*wowDepth); depthSlewWow[1].setCurrentAndTargetValue (*wowDepth); @@ -37,6 +39,8 @@ void Flutter::createParameterLayout (std::vector<std::unique_ptr<RangedAudioPara params.push_back (std::make_unique<AudioParameterFloat> ("wow_rate", "Rate", 0.0f, 1.0f, 0.25f)); params.push_back (std::make_unique<AudioParameterFloat> ("wow_depth", "Depth", 0.0f, 1.0f, 0.0f)); + + params.push_back (std::make_unique<AudioParameterBool> ("flutter_onoff", "On/Off", true)); } void Flutter::prepareToPlay (double sampleRate, int samplesPerBlock) @@ -102,8 +106,8 @@ void Flutter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessag flutterBuffer.setSize (2, buffer.getNumSamples(), false, false, true); flutterBuffer.clear(); - bool shouldTurnOff = depthSlewWow[0].getTargetValue() == depthSlewMin - && depthSlewFlutter[0].getTargetValue() == depthSlewMin; + bool shouldTurnOff = ! static_cast<bool> (flutterOnOff->load()) || + (depthSlewWow[0].getTargetValue() == depthSlewMin && depthSlewFlutter[0].getTargetValue() == depthSlewMin); if (! isOff && ! shouldTurnOff) // process normally { processWetBuffer (buffer); diff --git a/Plugin/Source/Processors/Timing_Effects/Flutter.h b/Plugin/Source/Processors/Timing_Effects/Flutter.h @@ -19,6 +19,7 @@ public: void processBypassed (AudioBuffer<float>& buffer); private: + std::atomic<float>* flutterOnOff = nullptr; std::atomic<float>* flutterRate = nullptr; std::atomic<float>* flutterDepth = nullptr; std::atomic<float>* wowRate = nullptr; diff --git a/README.md b/README.md @@ -58,6 +58,7 @@ see the - GUI Design - [Margus Mets](mailto:hello@mmcreative.eu) - GUI Framework - [Plugin GUI Magic](https://github.com/ffAudio/PluginGUIMagic) +- Power Switch Icon - [FontAudio](https://github.com/fefanto/fontaudio) - Open Build Service Builds - [Konstantin Voinov](https://github.com/lv2-porting-project) - User Manual - Yann