commit 04b41080c601250cdfdd63a51d74b2dd7bb7fede
parent d44ac4b75b54c3b6b5d2584ab5046f1ff6aab62a
Author: jatinchowdhury18 <[email protected]>
Date: Thu, 3 Jun 2021 23:35:16 -0700
More oversampling choices (#199)
* Refactor existing oversampling code
* Add offline options for oversampling
* Clean up oversampling menu
* Update iOS UI config
* {Apply clang-format}
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Diffstat:
12 files changed, 312 insertions(+), 39 deletions(-)
diff --git a/Plugin/Source/GUI/Assets/gui.xml b/Plugin/Source/GUI/Assets/gui.xml
@@ -236,10 +236,12 @@
<View max-height="35" margin="0" padding="0" background-color="FF31323A"
flex-grow="0.1">
<View background-color="00000000" flex-grow="0.1"/>
- <ComboBox caption="Oversampling" parameter="os" class="Slider" caption-size="0"
- padding="0" combo-text="FFEAA92C" combo-background="00000000"
- max-height="100" margin="" lookAndFeel="ComboBoxLNF" name="Oversampling"
- tooltip="Sets the amount of oversampling used for the hysteresis processing. More oversampling will reduce aliasing artifacts, but requires more CPU resources."/>
+ <OversamplingMenu caption="Oversampling" os-param="os" os-mode="os_mode"
+ os-off-param="os_render_factor" os-off-mode="os_render_mode"
+ os-off-same="os_render_like_realtime" class="Slider"
+ caption-size="0" padding="0" combo-text="FFEAA92C" combo-background="00000000"
+ max-height="100" margin="" lookAndFeel="ComboBoxLNF" name="Oversampling"
+ tooltip="Sets the amount of oversampling used for the hysteresis processing. More oversampling will reduce aliasing artifacts, but requires more CPU resources."/>
<ComboBox lookAndFeel="ComboBoxLNF" padding="0" border="0" background-color="00000000"
name="Hysteresis Mode" caption="Hysteresis Mode" caption-size="0"
combo-text="FFEAA92C" caption-color="FFFFFFFF" max-height="100"
diff --git a/Plugin/Source/GUI/Assets/gui_ios.xml b/Plugin/Source/GUI/Assets/gui_ios.xml
@@ -272,10 +272,12 @@
<View max-height="40" min-height="20" margin="0" padding="0" background-color="FF31323A"
flex-grow="0.1">
<View background-color="00000000" flex-grow="0.1"/>
- <ComboBox caption="Oversampling" parameter="os" class="Slider" caption-size="0"
- padding="0" combo-text="FFEAA92C" combo-background="00000000"
- max-height="100" margin="" lookAndFeel="ComboBoxLNF" name="Oversampling"
- tooltip="Sets the amount of oversampling used for the hysteresis processing. More oversampling will reduce aliasing artifacts, but requires more CPU resources."/>
+ <OversamplingMenu caption="Oversampling" os-param="os" os-mode="os_mode"
+ os-off-param="os_render_factor" os-off-mode="os_render_mode"
+ os-off-same="os_render_like_realtime" class="Slider"
+ caption-size="0" padding="0" combo-text="FFEAA92C" combo-background="00000000"
+ max-height="100" margin="" lookAndFeel="ComboBoxLNF" name="Oversampling"
+ tooltip="Sets the amount of oversampling used for the hysteresis processing. More oversampling will reduce aliasing artifacts, but requires more CPU resources."/>
<ComboBox lookAndFeel="ComboBoxLNF" padding="0" border="0" background-color="00000000"
name="Hysteresis Mode" caption="Hysteresis Mode" caption-size="0"
combo-text="FFEAA92C" caption-color="FFFFFFFF" max-height="100"
diff --git a/Plugin/Source/GUI/CMakeLists.txt b/Plugin/Source/GUI/CMakeLists.txt
@@ -1,6 +1,7 @@
target_sources(CHOWTapeModel PRIVATE
AutoUpdating.cpp
MyLNF.cpp
+ OversamplingMenu.cpp
TitleComp.cpp
TooltipComp.cpp
WowFlutterMenu.cpp
diff --git a/Plugin/Source/GUI/OversamplingMenu.cpp b/Plugin/Source/GUI/OversamplingMenu.cpp
@@ -0,0 +1,142 @@
+#include "OversamplingMenu.h"
+#include "../PluginProcessor.h"
+
+OversamplingMenu::OversamplingMenu (foleys::MagicGUIBuilder& builder, const ValueTree& node) : foleys::GuiItem (builder, node),
+ osManager (dynamic_cast<ChowtapeModelAudioProcessor*> (getMagicState()
+ .getProcessor())
+ ->getHysteresisProcessor()
+ .getOSManager())
+{
+ setColourTranslation (
+ { { "combo-background", ComboBox::backgroundColourId },
+ { "combo-text", ComboBox::textColourId },
+ { "combo-outline", ComboBox::outlineColourId },
+ { "combo-button", ComboBox::buttonColourId },
+ { "combo-arrow", ComboBox::arrowColourId },
+ { "combo-focused-outline", ComboBox::focusedOutlineColourId },
+ { "combo-menu-background", PopupMenu::backgroundColourId },
+ { "combo-menu-background-highlight", PopupMenu::highlightedBackgroundColourId },
+ { "combo-menu-text", PopupMenu::textColourId },
+ { "combo-menu-text-highlight", PopupMenu::highlightedTextColourId } });
+
+ addAndMakeVisible (comboBox);
+}
+
+void OversamplingMenu::update()
+{
+ auto& vts = dynamic_cast<ChowtapeModelAudioProcessor*> (getMagicState().getProcessor())->getVTS();
+
+ int count = 0;
+ for (auto paramTag : { &osParam, &osMode, &osOfflineParam, &osOfflineMode, &osOfflineSame })
+ {
+ attachments[count].reset();
+ auto paramID = configNode.getProperty (*paramTag, String()).toString();
+ if (paramID.isNotEmpty())
+ {
+ parameters[count] = vts.getParameter (paramID);
+ attachments[count] = std::make_unique<ParameterAttachment> (
+ *parameters[count],
+ [=] (float) { generateComboBoxMenu(); },
+ vts.undoManager);
+ }
+
+ count += 1;
+ }
+
+ generateComboBoxMenu();
+}
+
+void OversamplingMenu::generateComboBoxMenu()
+{
+ comboBox.clear();
+ auto* menu = comboBox.getRootMenu();
+
+ auto createParamItem = [=] (PopupMenu::Item& item, auto* parameter, auto& attachment, int& menuIdx, int menuOffset, const String& choice, bool forceOff = false, bool disableSame = false) {
+ item.itemID = menuIdx++;
+ int paramVal = item.itemID - menuOffset;
+ bool isSelected = ((int) parameter->convertFrom0to1 (parameter->getValue()) == paramVal) && ! forceOff;
+ item.text = choice;
+ item.colour = isSelected ? Colour (0xFFEAA92C) : Colours::white;
+ item.action = [&, paramVal, disableSame] {
+ if (disableSame)
+ attachments[4]->setValueAsCompleteGesture (0.0f);
+ attachment->setValueAsCompleteGesture (float (paramVal));
+ };
+ return isSelected;
+ };
+
+ // set up main menu
+ StringArray headers { "OS Factor", "Mode", "OS Factor", "Mode" };
+ int menuIdx = 1;
+ int menuOffset = menuIdx;
+
+ // set up offline menu
+ PopupMenu offlineMenu;
+ int offlineMenuIdx = 1;
+ int offlineMenuOffset = menuIdx;
+
+ bool sameAsRT = false;
+ { // same as real-time option
+ PopupMenu::Item item;
+ item.itemID = menuIdx++;
+ auto* parameter = parameters[4];
+ sameAsRT = (int) parameter->convertFrom0to1 (parameter->getValue()) == 1;
+ item.text = "Same as real-time";
+ item.colour = sameAsRT ? Colour (0xFFEAA92C) : Colours::white;
+ item.action = [&] { attachments[4]->setValueAsCompleteGesture (1.0f); };
+ offlineMenu.addItem (item);
+ }
+
+ // add parameter to menus
+ for (int paramIdx = 0; paramIdx < 4; ++paramIdx)
+ {
+ bool isOfflineParam = paramIdx >= 2;
+ auto* thisMenu = isOfflineParam ? &offlineMenu : menu;
+ auto& thisMenuIdx = isOfflineParam ? offlineMenuIdx : menuIdx;
+ auto& thisMenuOffset = isOfflineParam ? offlineMenuOffset : menuOffset;
+ thisMenuOffset = thisMenuIdx;
+
+ thisMenu->addSectionHeader (headers[paramIdx]);
+ for (auto& choice : parameters[paramIdx]->getAllValueStrings())
+ {
+ PopupMenu::Item item;
+ bool isSelected = createParamItem (item,
+ parameters[paramIdx],
+ attachments[paramIdx],
+ thisMenuIdx,
+ thisMenuOffset,
+ choice,
+ sameAsRT && isOfflineParam,
+ isOfflineParam);
+ thisMenu->addItem (item);
+
+ if (isSelected && paramIdx == 0)
+ comboBox.setText (item.text);
+ }
+ }
+
+ menu->addSeparator();
+ menu->addSubMenu ("Offline:", offlineMenu);
+
+ auto osParam = parameters[0]->convertFrom0to1 (parameters[0]->getValue());
+ auto osMode = parameters[1]->convertFrom0to1 (parameters[1]->getValue());
+ auto osIndex = osManager.getOSIndex (osParam, osMode);
+ auto curLatencyMs = osManager.getLatencyMilliseconds (osIndex);
+ menu->addSectionHeader ("Current Latency: " + String (curLatencyMs, 3) + " ms");
+}
+
+std::vector<foleys::SettableProperty> OversamplingMenu::getSettableProperties() const
+{
+ std::vector<foleys::SettableProperty> properties;
+ properties.push_back ({ configNode, osParam, foleys::SettableProperty::Choice, {}, magicBuilder.createParameterMenu() });
+ properties.push_back ({ configNode, osMode, foleys::SettableProperty::Choice, {}, magicBuilder.createParameterMenu() });
+ properties.push_back ({ configNode, osOfflineParam, foleys::SettableProperty::Choice, {}, magicBuilder.createParameterMenu() });
+ properties.push_back ({ configNode, osOfflineMode, foleys::SettableProperty::Choice, {}, magicBuilder.createParameterMenu() });
+ return properties;
+}
+
+const Identifier OversamplingMenu::osParam = { "os-param" };
+const Identifier OversamplingMenu::osMode = { "os-mode" };
+const Identifier OversamplingMenu::osOfflineParam = { "os-off-param" };
+const Identifier OversamplingMenu::osOfflineMode = { "os-off-mode" };
+const Identifier OversamplingMenu::osOfflineSame = { "os-off-same" };
diff --git a/Plugin/Source/GUI/OversamplingMenu.h b/Plugin/Source/GUI/OversamplingMenu.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "../Processors/Hysteresis/OversamplingManager.h"
+
+class OversamplingMenu : public foleys::GuiItem
+{
+public:
+ FOLEYS_DECLARE_GUI_FACTORY (OversamplingMenu)
+
+ static const Identifier osParam;
+ static const Identifier osMode;
+ static const Identifier osOfflineParam;
+ static const Identifier osOfflineMode;
+ static const Identifier osOfflineSame;
+
+ OversamplingMenu (foleys::MagicGUIBuilder& builder, const ValueTree& node);
+
+ void update() override;
+ std::vector<foleys::SettableProperty> getSettableProperties() const override;
+
+ Component* getWrappedComponent() override { return &comboBox; }
+
+private:
+ void generateComboBoxMenu();
+
+ ComboBox comboBox;
+
+ std::unique_ptr<ParameterAttachment> attachments[5];
+ RangedAudioParameter* parameters[5];
+
+ const OversamplingManager& osManager;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OversamplingMenu)
+};
diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp
@@ -10,6 +10,7 @@
#include "PluginProcessor.h"
#include "GUI/OnOff/PowerButton.h"
+#include "GUI/OversamplingMenu.h"
#include "GUI/TitleComp.h"
#include "GUI/TooltipComp.h"
#include "GUI/Visualizers/MixGroupViz.h"
@@ -31,7 +32,7 @@ ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor()
vts (*this, nullptr, Identifier ("Parameters"), createParameterLayout()),
inputFilters (vts),
toneControl (vts),
- hysteresis (vts),
+ hysteresis (vts, *this),
degrade (vts),
chewer (vts),
lossFilter (vts),
@@ -295,6 +296,7 @@ AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor()
builder->registerFactory ("TitleComp", &TitleItem::factory);
builder->registerFactory ("MixGroupViz", &MixGroupVizItem::factory);
builder->registerFactory ("PowerButton", &PowerButtonItem::factory);
+ builder->registerFactory ("OversamplingMenu", &OversamplingMenu::factory);
builder->registerFactory ("FlutterMenu", [] (foleys::MagicGUIBuilder& b, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem> {
return std::make_unique<WowFlutterMenuItem> (b, node, "Flutter");
diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h
@@ -78,6 +78,7 @@ public:
PresetManager& getPresetManager() { return presetManager; }
const AudioProcessorValueTreeState& getVTS() const { return vts; }
const AudioPlayHead::CurrentPositionInfo& getPositionInfo() const { return positionInfo; }
+ HysteresisProcessor& getHysteresisProcessor() { return hysteresis; }
private:
using DryDelayType = chowdsp::DelayLine<float, chowdsp::DelayLineInterpolationTypes::Lagrange5th>;
diff --git a/Plugin/Source/Processors/CMakeLists.txt b/Plugin/Source/Processors/CMakeLists.txt
@@ -5,6 +5,7 @@ target_sources(CHOWTapeModel PRIVATE
Hysteresis/HysteresisProcessing.cpp
Hysteresis/HysteresisProcessor.cpp
Hysteresis/HysteresisSTN.cpp
+ Hysteresis/OversamplingManager.cpp
Hysteresis/STNModel.cpp
Hysteresis/ToneControl.cpp
diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp
@@ -5,18 +5,14 @@ enum
numSteps = 500,
};
-HysteresisProcessor::HysteresisProcessor (AudioProcessorValueTreeState& vts)
+HysteresisProcessor::HysteresisProcessor (AudioProcessorValueTreeState& vts, const AudioProcessor& p) : osManager (vts, p)
{
driveParam = vts.getRawParameterValue ("drive");
satParam = vts.getRawParameterValue ("sat");
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>> (2, i, dsp::Oversampling<float>::filterHalfBandPolyphaseIIR);
-
for (int ch = 0; ch < 2; ++ch)
{
drive[ch].reset (numSteps);
@@ -34,7 +30,7 @@ void HysteresisProcessor::createParameterLayout (std::vector<std::unique_ptr<Ran
params.push_back (std::make_unique<AudioParameterFloat> ("width", "Tape Bias", 0.0f, 1.0f, 0.5f));
params.push_back (std::make_unique<AudioParameterChoice> ("mode", "Tape Mode", StringArray ({ "RK2", "RK4", "NR4", "NR8", "STN", "V1" }), 0));
- params.push_back (std::make_unique<AudioParameterChoice> ("os", "Oversampling", StringArray ({ "1x", "2x", "4x", "8x", "16x" }), 1));
+ OversamplingManager::createParameterLayout (params);
}
void HysteresisProcessor::setSolver (int newSolver)
@@ -103,15 +99,11 @@ void HysteresisProcessor::setSaturation (float newSaturation)
void HysteresisProcessor::setOversampling()
{
- curOS = (int) *osParam;
- if (curOS != prevOS)
+ if (osManager.updateOSFactor())
{
- overSamplingFactor = 1 << curOS;
- prevOS = curOS;
-
for (int ch = 0; ch < 2; ++ch)
{
- hProcs[ch].setSampleRate (fs * overSamplingFactor);
+ hProcs[ch].setSampleRate (fs * osManager.getOSFactor());
hProcs[ch].cook (drive[ch].getCurrentValue(), width[ch].getCurrentValue(), sat[ch].getCurrentValue(), wasV1);
hProcs[ch].reset();
}
@@ -122,13 +114,12 @@ void HysteresisProcessor::setOversampling()
void HysteresisProcessor::calcBiasFreq()
{
- biasFreq = fs * overSamplingFactor / 2.0f;
+ biasFreq = fs * osManager.getOSFactor() / 2.0f;
}
void HysteresisProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
fs = (float) sampleRate;
- overSamplingFactor = 1 << curOS;
wasV1 = useV1;
calcBiasFreq();
@@ -139,17 +130,14 @@ void HysteresisProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
sat[ch].skip (numSteps);
makeup[ch].skip (numSteps);
- hProcs[ch].setSampleRate (sampleRate * overSamplingFactor);
+ hProcs[ch].setSampleRate (sampleRate * osManager.getOSFactor());
hProcs[ch].cook (drive[ch].getCurrentValue(), width[ch].getCurrentValue(), sat[ch].getCurrentValue(), wasV1);
hProcs[ch].reset();
biasAngle[ch] = 0.0f;
}
- for (int i = 0; i < 5; ++i)
- overSample[i]->initProcessing ((size_t) samplesPerBlock);
- prevOS = curOS;
-
+ osManager.prepareToPlay (sampleRate, samplesPerBlock);
for (int ch = 0; ch < 2; ++ch)
dcBlocker[ch].prepare (sampleRate, dcFreq);
@@ -158,14 +146,13 @@ void HysteresisProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
void HysteresisProcessor::releaseResources()
{
- for (int i = 0; i < 5; ++i)
- overSample[i]->reset();
+ osManager.releaseResources();
}
float HysteresisProcessor::getLatencySamples() const noexcept
{
// latency of oversampling + fudge factor for hysteresis
- return onOffParam->load() == 1.0f ? overSample[curOS]->getLatencyInSamples() + 1.4f // on
+ return onOffParam->load() == 1.0f ? osManager.getLatencySamples() + 1.4f // on
: 0.0f; // off
}
@@ -199,7 +186,7 @@ void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer&
buffer.getNumSamples());
dsp::AudioBlock<float> block (buffer);
- dsp::AudioBlock<float> osBlock = overSample[curOS]->processSamplesUp (block);
+ dsp::AudioBlock<float> osBlock = osManager.getOversampler()->processSamplesUp (block);
if (needsSmoothing)
{
@@ -216,7 +203,7 @@ void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer&
process (osBlock);
}
- overSample[curOS]->processSamplesDown (block);
+ osManager.getOversampler()->processSamplesDown (block);
applyDCBlockers (buffer);
@@ -251,7 +238,7 @@ void HysteresisProcessor::processSmooth (dsp::AudioBlock<float>& block)
void HysteresisProcessor::processV1 (dsp::AudioBlock<float>& block)
{
- const auto angleDelta = MathConstants<float>::twoPi * biasFreq / (fs * overSamplingFactor);
+ const auto angleDelta = MathConstants<float>::twoPi * biasFreq / (fs * osManager.getOSFactor());
for (size_t channel = 0; channel < block.getNumChannels(); ++channel)
{
@@ -271,7 +258,7 @@ void HysteresisProcessor::processV1 (dsp::AudioBlock<float>& block)
void HysteresisProcessor::processSmoothV1 (dsp::AudioBlock<float>& block)
{
- const auto angleDelta = MathConstants<float>::twoPi * biasFreq / (fs * overSamplingFactor);
+ const auto angleDelta = MathConstants<float>::twoPi * biasFreq / (fs * osManager.getOSFactor());
for (size_t channel = 0; channel < block.getNumChannels(); ++channel)
{
diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h
@@ -4,12 +4,13 @@
#include "../BypassProcessor.h"
#include "DCBlocker.h"
#include "HysteresisProcessing.h"
+#include "OversamplingManager.h"
/* Hysteresis Processor for tape. */
class HysteresisProcessor
{
public:
- HysteresisProcessor (AudioProcessorValueTreeState& vts);
+ HysteresisProcessor (AudioProcessorValueTreeState& vts, const AudioProcessor& p);
/* Reset fade buffers, filters, and processors. Prepare oversampling */
void prepareToPlay (double sampleRate, int samplesPerBlock);
@@ -23,6 +24,7 @@ public:
static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params);
float getLatencySamples() const noexcept;
+ const OversamplingManager& getOSManager() const { return osManager; }
private:
void setSolver (int newSolver);
@@ -52,12 +54,10 @@ private:
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> makeup[2];
float fs = 44100.0f;
- int curOS = 0, prevOS = 0;
HysteresisProcessing hProcs[2];
- std::unique_ptr<dsp::Oversampling<float>> overSample[5]; // needs oversampling to avoid aliasing
+ OversamplingManager osManager; // needs oversampling to avoid aliasing
DCBlocker dcBlocker[2];
- int overSamplingFactor = 2;
const float dcFreq = 35.0f;
float biasGain = 10.0f;
diff --git a/Plugin/Source/Processors/Hysteresis/OversamplingManager.cpp b/Plugin/Source/Processors/Hysteresis/OversamplingManager.cpp
@@ -0,0 +1,60 @@
+#include "OversamplingManager.h"
+
+OversamplingManager::OversamplingManager (const AudioProcessorValueTreeState& vts, const AudioProcessor& p) : proc (p)
+{
+ osParam = vts.getRawParameterValue ("os");
+ osModeParam = vts.getRawParameterValue ("os_mode");
+ osOfflineParam = vts.getRawParameterValue ("os_render_factor");
+ osOfflineModeParam = vts.getRawParameterValue ("os_render_mode");
+ osOfflineSameParam = vts.getRawParameterValue ("os_render_like_realtime");
+
+ for (int i = 0; i < numOSChoices; ++i)
+ {
+ overSample[i] = std::make_unique<dsp::Oversampling<float>> (2, i, dsp::Oversampling<float>::filterHalfBandPolyphaseIIR);
+ overSample[i + numOSChoices] = std::make_unique<dsp::Oversampling<float>> (2, i, dsp::Oversampling<float>::filterHalfBandFIREquiripple);
+ }
+}
+
+void OversamplingManager::createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params)
+{
+ params.push_back (std::make_unique<AudioParameterChoice> ("os", "Oversampling", StringArray ({ "1x", "2x", "4x", "8x", "16x" }), 1));
+ params.push_back (std::make_unique<AudioParameterChoice> ("os_mode", "Oversampling Mode", StringArray ({ "Min. Phase", "Linear Phase" }), 0));
+
+ params.push_back (std::make_unique<AudioParameterChoice> ("os_render_factor", "Oversampling (render)", StringArray ({ "1x", "2x", "4x", "8x", "16x" }), 1));
+ params.push_back (std::make_unique<AudioParameterChoice> ("os_render_mode", "Oversampling Mode (render)", StringArray ({ "Min. Phase", "Linear Phase" }), 0));
+ params.push_back (std::make_unique<AudioParameterBool> ("os_render_like_realtime", "Oversampling (render like real-time)", true));
+}
+
+bool OversamplingManager::updateOSFactor()
+{
+ curOS = getOSIndex (*osParam, *osModeParam);
+ if (proc.isNonRealtime() && *osOfflineSameParam == 0.0f)
+ {
+ curOS = getOSIndex (*osOfflineParam, *osOfflineModeParam);
+ }
+ if (curOS != prevOS)
+ {
+ overSamplingFactor = 1 << curOS;
+ prevOS = curOS;
+ return true;
+ }
+
+ return false;
+}
+
+void OversamplingManager::prepareToPlay (double sr, int samplesPerBlock)
+{
+ sampleRate = (float) sr;
+
+ overSamplingFactor = 1 << curOS;
+
+ for (int i = 0; i < numOSChoices; ++i)
+ overSample[i]->initProcessing ((size_t) samplesPerBlock);
+ prevOS = curOS;
+}
+
+void OversamplingManager::releaseResources()
+{
+ for (int i = 0; i < numOSChoices; ++i)
+ overSample[i]->reset();
+}
diff --git a/Plugin/Source/Processors/Hysteresis/OversamplingManager.h b/Plugin/Source/Processors/Hysteresis/OversamplingManager.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <JuceHeader.h>
+
+class OversamplingManager
+{
+public:
+ OversamplingManager (const AudioProcessorValueTreeState& vts, const AudioProcessor& p);
+
+ static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params);
+
+ void prepareToPlay (double sampleRate, int samplesPerBlock);
+ void releaseResources();
+
+ int getOSFactor() const noexcept { return overSamplingFactor; }
+ bool updateOSFactor();
+
+ static int getOSIndex (float osFactor, float osMode) { return (int) osFactor + (numOSChoices * (int) osMode); }
+ float getLatencySamples() const noexcept { return overSample[curOS]->getLatencyInSamples(); }
+ float getLatencyMilliseconds (int osIndex) const noexcept { return (overSample[osIndex]->getLatencyInSamples() / sampleRate) * 1000.0f; }
+
+ dsp::Oversampling<float>* getOversampler() { return overSample[curOS].get(); }
+
+private:
+ std::atomic<float>* osParam = nullptr;
+ std::atomic<float>* osModeParam = nullptr;
+ std::atomic<float>* osOfflineParam = nullptr;
+ std::atomic<float>* osOfflineModeParam = nullptr;
+ std::atomic<float>* osOfflineSameParam = nullptr;
+
+ int curOS = 0, prevOS = 0;
+ int overSamplingFactor = 2;
+ float sampleRate = 48000.0f;
+
+ static constexpr int numOSChoices = 5;
+ std::unique_ptr<dsp::Oversampling<float>> overSample[2 * numOSChoices];
+
+ const AudioProcessor& proc;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OversamplingManager)
+};