commit b55eb970bd9c9cffe9f6297e32cb7ce39fb8f751
parent 4258a72bdb01d5799db24be68c8e6aa18e657ff4
Author: jatinchowdhury18 <jatinchowdhury18@gmail.com>
Date: Wed, 28 Oct 2020 15:39:23 -0700
Add wow/flutter visualizations (#104)
Co-authored-by: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Diffstat:
8 files changed, 123 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -3,9 +3,10 @@ All notable changes to this project will be documented in
this file.
## [Unreleased]
-- Tone section: added transition frequency control, and made bass/treble controls more extreme
-- Added coloured circle on bottom bar to visualize mix group
-- Added buttons to snap tape speed to conventional values
+- Added visualizations for Wow and Flutter.
+- Tone section: added transition frequency control, and made bass/treble controls more extreme.
+- Added coloured circle on bottom bar to visualize mix group.
+- Added buttons to snap tape speed to conventional values.
## [2.6.0] - 2020-09-29
- Added Pre/post emphasis filters for the hysteresis stage.
diff --git a/Plugin/CHOWTapeModel.jucer b/Plugin/CHOWTapeModel.jucer
@@ -24,6 +24,8 @@
<FILE id="u8IfBD" name="AutoUpdating.h" compile="0" resource="0" file="Source/GUI/AutoUpdating.h"/>
<FILE id="rOj90C" name="InfoComp.cpp" compile="1" resource="0" file="Source/GUI/InfoComp.cpp"/>
<FILE id="FxvDV3" name="InfoComp.h" compile="0" resource="0" file="Source/GUI/InfoComp.h"/>
+ <FILE id="AG1VNL" name="LightMeter.cpp" compile="1" resource="0" file="Source/GUI/LightMeter.cpp"/>
+ <FILE id="F1yjo4" name="LightMeter.h" compile="0" resource="0" file="Source/GUI/LightMeter.h"/>
<FILE id="SYzWcj" name="MixGroupViz.cpp" compile="1" resource="0" file="Source/GUI/MixGroupViz.cpp"/>
<FILE id="YzAgWK" name="MixGroupViz.h" compile="0" resource="0" file="Source/GUI/MixGroupViz.h"/>
<FILE id="IgOtsG" name="MyLNF.cpp" compile="1" resource="0" file="Source/GUI/MyLNF.cpp"/>
diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml
@@ -64,12 +64,10 @@
</View>
<View flex-direction="column" tab-color="" background-color="FF31323A"
padding="0" tab-caption="Tone">
- <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."/>
+ <Slider caption="Bass" parameter="h_bass" class="Slider" name="Bass"
+ tooltip="Controls the bass response of the pre/post-emphasis filters."/>
+ <Slider caption="Treble" parameter="h_treble" class="Slider" name="Treble"
+ tooltip="Controls the treble response of the pre/post-emphasis filters."/>
</View>
</View>
<View display="tabbed" padding="0" background-color="FF31323A" flex-grow="1.5"
@@ -129,15 +127,23 @@
<View display="tabbed" padding="0" background-color="FF31323A" lookAndFeel="MyLNF">
<View tab-caption="Flutter" flex-direction="column" background-color="FF31323A">
<Slider caption="Depth" parameter="depth" max-height="150" class="Slider"
- name="Flutter Depth" tooltip="Sets depth of the tape flutter."/>
+ name="Flutter Depth" tooltip="Sets depth of the tape flutter."
+ margin="0" padding="0"/>
<Slider caption="Rate" parameter="rate" class="Slider" max-height="150"
- name="Flutter Rate" tooltip="Sets the rate of the tape flutter."/>
+ name="Flutter Rate" tooltip="Sets the rate of the tape flutter."
+ 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"/>
</View>
<View tab-caption="Wow" flex-direction="column" background-color="FF31323A">
<Slider caption="Depth" parameter="wow_depth" max-height="150" class="Slider"
- name="Wow Depth" tooltip="Sets the depth of the tape wow."/>
+ name="Wow Depth" tooltip="Sets the depth of the tape wow." margin="0"
+ padding="0"/>
<Slider caption="Rate" parameter="wow_rate" class="Slider" max-height="150"
- name="Wow Rate" tooltip="Sets the rate of the tape wow."/>
+ name="Wow Rate" tooltip="Sets the rate of the tape wow." margin="0"
+ padding="0"/>
+ <Plot source="wow" plot-decay="0.8" flex-grow="0.8" background-color="FF1E1F22"
+ plot-color="FFEAA92C" plot-fill-color="CC8B3232"/>
</View>
</View>
</View>
diff --git a/Plugin/Source/GUI/LightMeter.cpp b/Plugin/Source/GUI/LightMeter.cpp
@@ -0,0 +1,25 @@
+#include "LightMeter.h"
+
+void LightMeter::pushSamples (const juce::AudioBuffer<float>& buffer)
+{
+ rms = buffer.getRMSLevel (0, 0, buffer.getNumSamples());
+
+ if (std::isnan (rms.load()))
+ rms = 0.0f;
+
+ resetLastDataFlag();
+}
+
+void LightMeter::createPlotPaths (juce::Path& path, juce::Path& filledPath, juce::Rectangle<float> bounds, foleys::MagicPlotComponent&)
+{
+ const auto centre = bounds.getCentre();
+ const auto maxDiameter = jmin (bounds.getHeight(), bounds.getWidth());
+
+ const auto diameter = maxDiameter * jlimit (0.0f, 1.0f, rms.load());
+ const auto ellipseRect = Rectangle<float> (diameter, diameter).withCentre (centre);
+
+ path.clear();
+ path.addEllipse (ellipseRect);
+
+ filledPath = path;
+}
diff --git a/Plugin/Source/GUI/LightMeter.h b/Plugin/Source/GUI/LightMeter.h
@@ -0,0 +1,40 @@
+#ifndef LIGHTMETER_H_INCLUDED
+#define LIGHTMETER_H_INCLUDED
+
+#include "JuceHeader.h"
+
+class LightMeter : public foleys::MagicPlotSource,
+ public SettableTooltipClient
+{
+public:
+ LightMeter() { rms = 0.0f; }
+
+ /**
+ This method is called by the MagicProcessorState to allow the plot computation to be set up
+ */
+ void prepareToPlay (double, int) override {}
+
+ /**
+ This is the callback whenever new sample data arrives. It is the subclasses
+ responsibility to put that into a FIFO and return as quickly as possible.
+ */
+ void pushSamples (const juce::AudioBuffer<float>& buffer) override;
+
+ /**
+ This is the callback that creates the plot for drawing.
+
+ @param path is the path instance that is constructed by the MagicPlotSource
+ @param filledPath is the path instance that is constructed by the MagicPlotSource to be filled
+ @param bounds the bounds of the plot
+ @param component grants access to the plot component, e.g. to find the colours from it
+ */
+ void createPlotPaths (juce::Path& path, juce::Path& filledPath, juce::Rectangle<float> bounds, foleys::MagicPlotComponent& component) override;
+
+private:
+ std::atomic<float> rms;
+
+ JUCE_DECLARE_WEAK_REFERENCEABLE (LightMeter)
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LightMeter)
+};
+
+#endif // LIGHTMETER_H_INCLUDED
diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp
@@ -38,6 +38,7 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor()
lossFilter[ch].reset (new LossFilter (vts));
scope = magicState.createAndAddObject<foleys::MagicOscilloscope> ("scope");
+ flutter.initialisePlots (magicState);
LookAndFeel::setDefaultLookAndFeel (&myLNF);
diff --git a/Plugin/Source/Processors/Timing_Effects/Flutter.cpp b/Plugin/Source/Processors/Timing_Effects/Flutter.cpp
@@ -1,4 +1,5 @@
#include "Flutter.h"
+#include "../../GUI/LightMeter.h"
namespace
{
@@ -20,6 +21,15 @@ Flutter::Flutter (AudioProcessorValueTreeState& vts)
depthSlewFlutter[1].setCurrentAndTargetValue (*flutterDepth);
}
+void Flutter::initialisePlots (foleys::MagicGUIState& magicState)
+{
+ wowPlot = magicState.createAndAddObject<LightMeter> ("wow");
+ magicState.addBackgroundProcessing (wowPlot);
+
+ flutterPlot = magicState.createAndAddObject<LightMeter> ("flutter");
+ magicState.addBackgroundProcessing (flutterPlot);
+}
+
void Flutter::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params)
{
params.push_back (std::make_unique<AudioParameterFloat> ("rate", "Rate", 0.0f, 1.0f, 0.3f));
@@ -60,6 +70,11 @@ void Flutter::prepareToPlay (double sampleRate, int samplesPerBlock)
isOff = true;
dryBuffer.setSize (2, samplesPerBlock);
+ wowBuffer.setSize (2, samplesPerBlock);
+ flutterBuffer.setSize (2, samplesPerBlock);
+
+ wowPlot->prepareToPlay (sampleRate, samplesPerBlock);
+ flutterPlot->prepareToPlay (sampleRate, samplesPerBlock);
}
void Flutter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessages*/)
@@ -82,6 +97,11 @@ void Flutter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessag
angleDelta2 = MathConstants<float>::twoPi * 2.0f * flutterFreq / fs;
angleDelta3 = MathConstants<float>::twoPi * 3.0f * flutterFreq / fs;
+ wowBuffer.setSize (2, buffer.getNumSamples(), false, false, true);
+ wowBuffer.clear();
+ flutterBuffer.setSize (2, buffer.getNumSamples(), false, false, true);
+ flutterBuffer.clear();
+
bool shouldTurnOff = depthSlewWow[0].getTargetValue() == depthSlewMin
&& depthSlewFlutter[0].getTargetValue() == depthSlewMin;
if (! isOff && ! shouldTurnOff) // process normally
@@ -116,6 +136,12 @@ void Flutter::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midiMessag
// dc block
for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
dcBlocker[ch].processBlock (buffer.getWritePointer (ch), buffer.getNumSamples());
+
+ wowBuffer.applyGain (0.83333f / wowAmp);
+ wowPlot->pushSamples (wowBuffer);
+
+ flutterBuffer.applyGain (1.3333f / amp1);
+ flutterPlot->pushSamples (flutterBuffer);
}
void Flutter::processWetBuffer (AudioBuffer<float>& buffer)
@@ -123,6 +149,8 @@ void Flutter::processWetBuffer (AudioBuffer<float>& buffer)
for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
{
auto* x = buffer.getWritePointer (ch);
+ auto* wowPtr = wowBuffer.getWritePointer (ch);
+ auto* flutterPtr = flutterBuffer.getWritePointer (ch);
for (int n = 0; n < buffer.getNumSamples(); ++n)
{
wowPhase[ch] += angleDeltaWow;
@@ -142,6 +170,9 @@ void Flutter::processWetBuffer (AudioBuffer<float>& buffer)
delay.setDelay (newLength);
delay.pushSample (ch, x[n]);
x[n] = delay.popSample (ch);
+
+ wowPtr[n] = wowLFO;
+ flutterPtr[n] = flutterLFO;
}
while (wowPhase[ch] >= MathConstants<float>::twoPi)
diff --git a/Plugin/Source/Processors/Timing_Effects/Flutter.h b/Plugin/Source/Processors/Timing_Effects/Flutter.h
@@ -9,6 +9,7 @@ class Flutter
public:
Flutter (AudioProcessorValueTreeState& vts);
+ void initialisePlots (foleys::MagicGUIState& magicState);
static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params);
void prepareToPlay (double sampleRate, int samplesPerBlock);
@@ -50,6 +51,9 @@ private:
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> depthSlewWow[2];
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> depthSlewFlutter[2];
+ AudioBuffer<float> wowBuffer, flutterBuffer;
+ foleys::MagicPlotSource* wowPlot = nullptr, *flutterPlot = nullptr;
+
enum
{
HISTORY_SIZE = 1 << 21,