commit 9e581e68736be43bbd2da000b5af97bb9c647a17
parent b785ee294ed321fef4bee98736a0ddebcb91cf8a
Author: jatinchowdhury18 <jatinchowdhury18@gmail.com>
Date: Tue, 23 Mar 2021 20:25:50 -0700
Wow/Flutter rate sync (#162)
* Implement speed sync for flutter
* Implement tempo-sync for flutter
* Implement rate sync for wow
* {Apply clang-format}
Co-authored-by: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Diffstat:
6 files changed, 201 insertions(+), 1 deletion(-)
diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml
@@ -179,6 +179,9 @@
</View>
<View display="tabbed" padding="0" margin="2" background-color="FF31323A" lookAndFeel="MyLNF">
<View tab-caption="Flutter" flex-direction="column" background-color="FF31323A">
+ <FlutterMenu margin="0" padding="0" background-color="00000000"
+ max-height="30" name="Flutter Sync" tooltip="Snaps the flutter rate to a synchronized value."/>
+ <View margin="0" padding="0" flex-grow="0.1" background-color="00000000"/>
<Slider caption="Depth" parameter="depth" max-height="150" class="Slider"
name="Flutter Depth" tooltip="Sets depth of the tape flutter."
margin="0" padding="0"/>
@@ -192,6 +195,9 @@
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" padding="0" margin="3">
+ <WowMenu margin="0" padding="0" background-color="00000000"
+ max-height="30" name="Wow Sync" tooltip="Snaps the wow rate to a synchronized value."/>
+ <View margin="0" padding="0" flex-grow="0.1" background-color="00000000"/>
<Slider caption="Depth" parameter="wow_depth" max-height="150" class="Slider"
name="Wow Depth" tooltip="Sets the depth of the tape wow." margin="0"
padding="0" slider-type="linear-horizontal"/>
diff --git a/Plugin/Source/GUI/CMakeLists.txt b/Plugin/Source/GUI/CMakeLists.txt
@@ -8,4 +8,5 @@ target_sources(CHOWTapeModel PRIVATE
TapeScope.cpp
TitleComp.cpp
TooltipComp.cpp
+ WowFlutterMenu.cpp
)
diff --git a/Plugin/Source/GUI/WowFlutterMenu.cpp b/Plugin/Source/GUI/WowFlutterMenu.cpp
@@ -0,0 +1,130 @@
+#include "WowFlutterMenu.h"
+
+class WowFlutterMenuLNF : public ComboBoxLNF
+{
+public:
+ WowFlutterMenuLNF() = default;
+
+ void drawComboBox (Graphics& g, int width, int height, bool, int, int, int, int, ComboBox& box) override
+ {
+ auto cornerSize = 5.0f;
+ Rectangle<int> boxBounds (0, 0, width, height);
+
+ g.setColour (box.findColour (ComboBox::backgroundColourId));
+ g.fillRoundedRectangle (boxBounds.toFloat(), cornerSize);
+
+ g.setColour (box.findColour (ComboBox::outlineColourId));
+ g.drawRoundedRectangle (boxBounds.toFloat().reduced (1.0f), cornerSize, 1.0f);
+
+ if (box.getName().isNotEmpty())
+ {
+ g.setColour (Colours::white);
+ g.setFont (getComboBoxFont (box).boldened());
+ g.drawFittedText (box.getName(), boxBounds, Justification::centred, 1);
+ }
+ }
+
+ void positionComboBoxText (ComboBox& box, Label& label) override
+ {
+ auto b = box.getBounds();
+ label.setBounds (b);
+ label.setFont (getComboBoxFont (box).boldened());
+ }
+
+private:
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WowFlutterMenuLNF)
+};
+
+//============================================================================
+namespace
+{
+float flutterFreqToParam (float freq)
+{
+ return 0.144765f * std::log (10.0f * freq);
+}
+
+float wowFreqToParam (float freq)
+{
+ return 0.664859f * std::log (freq + 1.0f);
+}
+} // namespace
+
+WowFlutterMenu::WowFlutterMenu (const ChowtapeModelAudioProcessor& proc, const String& type) : proc (proc)
+{
+ setupUI();
+
+ const bool isFlutter = type == "Flutter";
+ setupRateParam (isFlutter);
+
+ auto snycToTapeSpeed = [=, &proc] {
+ const auto& vts = proc.getVTS();
+ auto speedParam = dynamic_cast<AudioParameterFloat*> (vts.getParameter ("speed"));
+ auto speedIps = speedParam->get();
+
+ auto motorFreq = speedIps / (6.0f * MathConstants<float>::pi);
+ auto newRate = isFlutter ? flutterFreqToParam (motorFreq)
+ : wowFreqToParam (std::sqrt (motorFreq));
+ setRateValue (newRate);
+ };
+
+ auto syncToRhythm = [=, &proc] (float multipleOfQuarterNote) {
+ const auto& posInfo = proc.getPositionInfo();
+ auto quarterNoteTime = 60.0f / (float) posInfo.bpm;
+
+ auto newFreq = 1.0f / (quarterNoteTime * multipleOfQuarterNote);
+ auto newRate = isFlutter ? flutterFreqToParam (newFreq) : wowFreqToParam (newFreq);
+ setRateValue (newRate);
+ };
+
+ auto menu = getRootMenu();
+ menu->addItem ("Sync to tape speed", snycToTapeSpeed);
+
+ if (isFlutter)
+ {
+ menu->addItem ("Sync to eighth note", std::bind (syncToRhythm, 0.125f));
+ menu->addItem ("Sync to quarter note", std::bind (syncToRhythm, 0.25f));
+ menu->addItem ("Sync to half note", std::bind (syncToRhythm, 0.5f));
+ menu->addItem ("Sync to whole note", std::bind (syncToRhythm, 1.0f));
+ }
+ else
+ {
+ menu->addItem ("Sync to one bar", std::bind (syncToRhythm, 1.0f));
+ menu->addItem ("Sync to two bars", std::bind (syncToRhythm, 2.0f));
+ menu->addItem ("Sync to four bars", std::bind (syncToRhythm, 4.0f));
+ menu->addItem ("Sync to eight bars", std::bind (syncToRhythm, 8.0f));
+ }
+}
+
+WowFlutterMenu::~WowFlutterMenu()
+{
+ setLookAndFeel (nullptr);
+}
+
+void WowFlutterMenu::setupUI()
+{
+ setColour (ComboBox::backgroundColourId, Colours::transparentBlack);
+ setColour (ComboBox::outlineColourId, Colour (0xff595c6b));
+
+ lnf = std::make_unique<WowFlutterMenuLNF>();
+ setLookAndFeel (lnf.get());
+
+ onChange = [=] { setSelectedItemIndex (-1); };
+}
+
+void WowFlutterMenu::setupRateParam (bool isFlutter)
+{
+ auto& vts = proc.getVTS();
+ if (isFlutter)
+ rateParam = dynamic_cast<AudioParameterFloat*> (vts.getParameter ("rate"));
+ else // "Wow"
+ rateParam = dynamic_cast<AudioParameterFloat*> (vts.getParameter ("wow_rate"));
+
+ jassert (rateParam); // this should never be nullptr!
+}
+
+void WowFlutterMenu::setRateValue (float value)
+{
+ rateParam->beginChangeGesture();
+ rateParam->setValueNotifyingHost (jlimit (0.0f, 1.0f, value));
+ rateParam->endChangeGesture();
+}
diff --git a/Plugin/Source/GUI/WowFlutterMenu.h b/Plugin/Source/GUI/WowFlutterMenu.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "../PluginProcessor.h"
+#include "MyLNF.h"
+
+class WowFlutterMenuLNF;
+class WowFlutterMenu : public ComboBox
+{
+public:
+ WowFlutterMenu (const ChowtapeModelAudioProcessor& proc, const String& type);
+ ~WowFlutterMenu() override;
+
+ void setupUI();
+ void setupRateParam (bool isFlutter);
+ void setRateValue (float value);
+
+private:
+ AudioParameterFloat* rateParam = nullptr;
+
+ const ChowtapeModelAudioProcessor& proc;
+ std::unique_ptr<WowFlutterMenuLNF> lnf;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WowFlutterMenu)
+};
+
+class WowFlutterMenuItem : public foleys::GuiItem
+{
+public:
+ WowFlutterMenuItem (foleys::MagicGUIBuilder& builder, const ValueTree& node, const String& type) : foleys::GuiItem (builder, node)
+ {
+ const auto* proc = dynamic_cast<ChowtapeModelAudioProcessor*> (builder.getMagicState().getProcessor());
+ jassert (proc); // this should never be nullptr!
+
+ menu = std::make_unique<WowFlutterMenu> (*proc, type);
+ addAndMakeVisible (menu.get());
+ }
+
+ void update() override {}
+
+ Component* getWrappedComponent() override { return menu.get(); }
+
+private:
+ std::unique_ptr<WowFlutterMenu> menu;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WowFlutterMenuItem)
+};
diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp
@@ -13,6 +13,7 @@
#include "GUI/PowerButton.h"
#include "GUI/TitleComp.h"
#include "GUI/TooltipComp.h"
+#include "GUI/WowFlutterMenu.h"
namespace
{
@@ -33,6 +34,9 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor()
onOffManager (vts, this),
mixGroupsController (vts, this)
{
+ positionInfo.bpm = 120.0;
+ positionInfo.timeSigNumerator = 4;
+
scope = magicState.createAndAddObject<TapeScope> ("scope", getMainBusNumInputChannels());
flutter.initialisePlots (magicState);
@@ -220,6 +224,9 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi
{
ScopedNoDenormals noDenormals;
+ if (auto playhead = getPlayHead())
+ playhead->getCurrentPosition (positionInfo);
+
inGain.setGain (Decibels::decibelsToGain (vts.getRawParameterValue ("ingain")->load()));
outGain.setGain (Decibels::decibelsToGain (vts.getRawParameterValue ("outgain")->load()));
dryWet.setDryWet (*vts.getRawParameterValue ("drywet") / 100.0f);
@@ -284,6 +291,14 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor()
builder->registerFactory ("MixGroupViz", &MixGroupVizItem::factory);
builder->registerFactory ("PowerButton", &PowerButtonItem::factory);
+ builder->registerFactory ("FlutterMenu", [] (foleys::MagicGUIBuilder& builder, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem> {
+ return std::make_unique<WowFlutterMenuItem> (builder, node, "Flutter");
+ });
+
+ builder->registerFactory ("WowMenu", [] (foleys::MagicGUIBuilder& builder, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem> {
+ return std::make_unique<WowFlutterMenuItem> (builder, node, "Wow");
+ });
+
builder->registerJUCELookAndFeels();
builder->registerLookAndFeel ("MyLNF", std::make_unique<MyLNF>());
builder->registerLookAndFeel ("ComboBoxLNF", std::make_unique<ComboBoxLNF>());
diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h
@@ -73,7 +73,8 @@ public:
void setStateInformation (const void* data, int sizeInBytes) override;
PresetManager& getPresetManager() { return presetManager; }
- const AudioProcessorValueTreeState& getVTS() { return vts; }
+ const AudioProcessorValueTreeState& getVTS() const { return vts; }
+ const AudioPlayHead::CurrentPositionInfo& getPositionInfo() const { return positionInfo; }
private:
using DryDelayType = chowdsp::DelayLine<float, chowdsp::DelayLineInterpolationTypes::Lagrange5th>;
@@ -105,6 +106,7 @@ private:
MyLNF myLNF;
AutoUpdater updater;
MixGroupsController mixGroupsController;
+ AudioPlayHead::CurrentPositionInfo positionInfo;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChowtapeModelAudioProcessor)