diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ea3cd6..caa5190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ All notable changes to this project will be documented in this file. * Performs first downsampling and then bit reduction * LofiEngine class which handles Left and Right channel BitCrushers and parameters * Lofi Mix, SampleRate and BitDepth parameters - +* Insert effect selector + * Allows user to switch between chorus and lofi effect in the feedback loop + ## \[0.2.0] - 2026-05-18 ### Added diff --git a/CMakeLists.txt b/CMakeLists.txt index 61bb47e..a8e0b43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,7 @@ set_target_properties(${project_targets} PROPERTIES CXX_EXTENSIONS OFF) target_sources(viive PRIVATE Source/BitCrusher.cpp Source/LofiEngine.cpp + Source/InsertEffectSelector.cpp Source/FilterEngine.cpp Source/Tempo.cpp Source/Selector.cpp diff --git a/Source/BitCrusher.h b/Source/BitCrusher.h index 3c05a4a..ff097f9 100644 --- a/Source/BitCrusher.h +++ b/Source/BitCrusher.h @@ -1,6 +1,5 @@ #pragma once #include -#include "Parameters.h" class BitCrusher { diff --git a/Source/ChorusEngine.cpp b/Source/ChorusEngine.cpp index cb22c8a..18acc02 100644 --- a/Source/ChorusEngine.cpp +++ b/Source/ChorusEngine.cpp @@ -59,12 +59,15 @@ void ChorusEngine::setModDepth(float modDepth) } } -void ChorusEngine::processSample(const float& inL, const float& inR, float& outL, float& outR, const Parameters& params) +void ChorusEngine::update(const Parameters& params) { setIntensity(params.chorusIntensity()); setModRate(params.chorusModRate()); setModDepth(params.chorusModDepth()); +} +void ChorusEngine::processSample(const float& inL, const float& inR, float& outL, float& outR) +{ // Dual chorus intensity mapping: c1 peaks early (at ~0.6 intensity), // c2 ramps exponentially, staying subtle until higher intensities. // This creates a smooth character progression: barely audible -> chorus1 dominant diff --git a/Source/ChorusEngine.h b/Source/ChorusEngine.h index 28e3452..2e37169 100644 --- a/Source/ChorusEngine.h +++ b/Source/ChorusEngine.h @@ -1,13 +1,13 @@ #pragma once #include +#include "InsertEffect.h" #include "Chorus.h" #include "Parameters.h" using Filter = juce::dsp::StateVariableTPTFilter; - - -class ChorusEngine { +class ChorusEngine : public InsertEffect +{ public: ChorusEngine(); ~ChorusEngine() = default; @@ -16,10 +16,11 @@ class ChorusEngine { static constexpr float chorus2DefaultTime = 11.0f; - void prepareToPlay(double sampleRate, int samplesPerBlock) noexcept; - void reset() noexcept; + void prepareToPlay(double sampleRate, int samplesPerBlock) noexcept override; + void reset() noexcept override; - void processSample(const float& inL, const float& inR, float& outL, float& outR, const Parameters& params); + void processSample(const float& inL, const float& inR, float& outL, float& outR) override; + void update(const Parameters& params) override; private: Chorus m_chorus1L{ chorus1DefaultTime, Parameters::defaultChorusModRate, Parameters::defaultChorusModDepth, 0.0f }; @@ -43,4 +44,6 @@ class ChorusEngine { void setIntensity(float value) { m_intensity = value; } void setModDepth(float newModDepth); void setModRate(float newRate); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ChorusEngine) }; diff --git a/Source/DelayEngine.cpp b/Source/DelayEngine.cpp index d8b4a32..5a66f8f 100644 --- a/Source/DelayEngine.cpp +++ b/Source/DelayEngine.cpp @@ -1,6 +1,5 @@ #include "DelayEngine.h" #include "DSP.h" -#include "Types.h" DelayEngine::DelayEngine() { @@ -19,23 +18,22 @@ void DelayEngine::prepareToPlay(double sampleRate, int samplesPerBlock) noexcept spec.numChannels = 2; m_sampleRate = static_cast(sampleRate); m_feedbackCompressor.prepare(spec); + m_insertEffectSelector.prepareToPlay(sampleRate, samplesPerBlock); m_filterEngine.prepareToPlay(sampleRate, samplesPerBlock); m_feedbackHighpass.prepare(spec); - m_chorusEngine.prepareToPlay(sampleRate, samplesPerBlock); m_stereoDelay.prepareToPlay(sampleRate, samplesPerBlock); m_coeff = onePoleLowpassCoeff(100.0f, static_cast(sampleRate)); - } void DelayEngine::reset() noexcept { m_stereoDelay.reset(); - m_chorusEngine.reset(); m_filterEngine.reset(); m_feedbackHighpass.reset(); m_feedbackHighpass.setCutoffFrequency(60.0f); // just to remove rumble from fb loop m_feedbackHighpass.setResonance(Parameters::defaultFilterQ); m_feedbackCompressor.reset(); + m_insertEffectSelector.reset(); m_feedbackL = 0.0f; m_feedbackR = 0.0f; m_baseDelayTimeMs = -1.0f; @@ -52,6 +50,15 @@ void DelayEngine::setDelayMode(const int modeIndex) } } +void DelayEngine::setInsertEffect(const int effectIndex) +{ + InsertEffectType newEffect = static_cast(effectIndex); + if (newEffect != m_currentInsertEffect) { + m_currentInsertEffect = newEffect; + m_insertEffectSelector.select(m_currentInsertEffect); + } +} + void DelayEngine::setDelayTimes(const float targetL, const float targetR) { float targetLeftMs = targetL - m_offsetMs; float targetRightMs = targetR + m_offsetMs; @@ -74,6 +81,7 @@ void DelayEngine::update(const Parameters& params) setOffsetMs(params.offset()); setDelayTimes(params.delayTimeL(), params.delayTimeR()); m_filterEngine.update(params); + m_insertEffectSelector.update(params); } void DelayEngine::updateSyncedModulators(const double ppqPosition) @@ -113,7 +121,7 @@ void DelayEngine::processSample(const float& inL, const float& inR, float& outL, m_stereoDelay.processSample(delayInL, wetL, Channel::Left); m_stereoDelay.processSample(delayInR, wetR, Channel::Right); - m_chorusEngine.processSample(wetL, wetR, wetL, wetR, params); + m_insertEffectSelector.processSample(wetL, wetR, wetL, wetR); m_filterEngine.processSample(wetL, wetR, wetL, wetR); diff --git a/Source/DelayEngine.h b/Source/DelayEngine.h index f360c71..80a20a9 100644 --- a/Source/DelayEngine.h +++ b/Source/DelayEngine.h @@ -1,17 +1,16 @@ #pragma once #include #include "Parameters.h" -#include "ChorusEngine.h" #include "StereoDelay.h" #include "FilterEngine.h" +#include "InsertEffectSelector.h" +#include "Types.h" #if JUCE_DEBUG #include "LFOScopeBuffer.h" #endif using Filter = juce::dsp::StateVariableTPTFilter; -enum class DelayMode { Stereo, Cross, PingPongLR, PingPongRL}; - class DelayEngine { public: @@ -30,6 +29,7 @@ class DelayEngine void setWidthLevel(const float value) { m_widthLevel = value; } void setOffsetMs(const float value) { m_offsetMs = value * .5f; } void setDelayMode(const int modeIndex); + void setInsertEffect(const int effectIndex); void updateSyncedModulators(const double ppqPosition); std::atomic& getLowCutLfoValue() { return m_filterEngine.getLowCutLfoValue(); } @@ -45,8 +45,9 @@ class DelayEngine FilterEngine m_filterEngine; Filter m_feedbackHighpass; juce::dsp::Compressor m_feedbackCompressor; - ChorusEngine m_chorusEngine; - + InsertEffectSelector m_insertEffectSelector; + InsertEffectType m_currentInsertEffect = InsertEffectType::Chorus; + float m_sampleRate = 0.0f; float m_coeff = 0.0f; diff --git a/Source/InsertEffect.h b/Source/InsertEffect.h new file mode 100644 index 0000000..f6c2330 --- /dev/null +++ b/Source/InsertEffect.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Parameters.h" + +class InsertEffect +{ +public: + InsertEffect() = default; + virtual ~InsertEffect() = default; + + virtual void prepareToPlay(double sampleRate, int samplesPerBlock) noexcept = 0; + virtual void reset() noexcept = 0; + virtual void processSample(const float& inL, const float& inR, float& outL, float& outR) = 0; + virtual void update(const Parameters& params) = 0; +}; \ No newline at end of file diff --git a/Source/InsertEffectSelector.cpp b/Source/InsertEffectSelector.cpp new file mode 100644 index 0000000..aaaf710 --- /dev/null +++ b/Source/InsertEffectSelector.cpp @@ -0,0 +1,46 @@ +#include "InsertEffectSelector.h" + +InsertEffectSelector::InsertEffectSelector() +{ + m_active = &m_chorus; +} + +void InsertEffectSelector::prepareToPlay(double sampleRate, int samplesPerBlock) noexcept +{ + m_chorus.prepareToPlay(sampleRate, samplesPerBlock); + m_lofi.prepareToPlay(sampleRate, samplesPerBlock); +} + +void InsertEffectSelector::reset() noexcept +{ + m_chorus.reset(); + m_lofi.reset(); +} + +void InsertEffectSelector::processSample(const float& inL, const float& inR, float& outL, float& outR) +{ + if (m_active) + m_active->processSample(inL, inR, outL, outR); +} + +void InsertEffectSelector::update(const Parameters& params) +{ + if (m_active) + m_active->update(params); +} + +void InsertEffectSelector::select(InsertEffectType type) +{ + switch(type) { + case InsertEffectType::Chorus: + m_active = &m_chorus; + break; + case InsertEffectType::Lofi: + m_active = &m_lofi; + break; + default: + jassertfalse; // this shouldn't happen unless the enum and string array get out of sync + return; + } + m_active->reset(); +} \ No newline at end of file diff --git a/Source/InsertEffectSelector.h b/Source/InsertEffectSelector.h new file mode 100644 index 0000000..fa4e987 --- /dev/null +++ b/Source/InsertEffectSelector.h @@ -0,0 +1,23 @@ +#pragma once +#include "InsertEffect.h" +#include "ChorusEngine.h" +#include "LofiEngine.h" +#include "Types.h" + +class InsertEffectSelector +{ +public: + InsertEffectSelector(); + ~InsertEffectSelector() = default; + + void prepareToPlay(double sampleRate, int samplesPerBlock) noexcept; + void reset() noexcept; + void processSample(const float& inL, const float& inR, float& outL, float& outR); + void update(const Parameters& params); + void select(InsertEffectType type); + +private: + ChorusEngine m_chorus; + LofiEngine m_lofi; + InsertEffect* m_active = nullptr; +}; \ No newline at end of file diff --git a/Source/LofiEngine.cpp b/Source/LofiEngine.cpp index d6eb4d0..561eba3 100644 --- a/Source/LofiEngine.cpp +++ b/Source/LofiEngine.cpp @@ -4,7 +4,7 @@ LofiEngine::LofiEngine() { } -void LofiEngine::prepareToPlay(double sampleRate) noexcept +void LofiEngine::prepareToPlay(double sampleRate, int samplesPerBlock) noexcept { m_sampleRate = static_cast(sampleRate); m_bitCrusherL.prepareToPlay(m_sampleRate); diff --git a/Source/LofiEngine.h b/Source/LofiEngine.h index 3469ec8..70960f6 100644 --- a/Source/LofiEngine.h +++ b/Source/LofiEngine.h @@ -2,17 +2,19 @@ #include #include "BitCrusher.h" +#include "Parameters.h" +#include "InsertEffect.h" -class LofiEngine +class LofiEngine : public InsertEffect { public: LofiEngine(); ~LofiEngine() = default; - void prepareToPlay(double sampleRate) noexcept; - void reset() noexcept; - void processSample(const float& inL, const float& inR, float& outL, float& outR); - void update(const Parameters& params); + void prepareToPlay(double sampleRate, int samplesPerBlock) noexcept override; + void reset() noexcept override; + void processSample(const float& inL, const float& inR, float& outL, float& outR) override; + void update(const Parameters& params) override; private: BitCrusher m_bitCrusherL{ 12.0f, 22050.0f }; diff --git a/Source/Parameters.cpp b/Source/Parameters.cpp index 668869d..09eff2a 100644 --- a/Source/Parameters.cpp +++ b/Source/Parameters.cpp @@ -78,13 +78,7 @@ static float degreesFromString(const juce::String& text) return text.getFloatValue(); } -const juce::StringArray Parameters::delayModes = { - "Stereo", - "Cross Fb", - "Ping Pong LR", - "Ping Pong RL", -}; - +// keep this syncronized with delayNoteLengthMultipliers in Sync.h const juce::StringArray Parameters::delayNoteLengths = { "1/32", "1/16 trip", @@ -104,6 +98,7 @@ const juce::StringArray Parameters::delayNoteLengths = { "1/1", }; +// keep this syncronized with modNoteLengthMultipliers in Sync.h const juce::StringArray Parameters::modNoteLengths = { "1/8", "1/8 dot", @@ -122,6 +117,15 @@ const juce::StringArray Parameters::modNoteLengths = { "8/1", }; +// keep this syncronized with the DelayMode enum in Types.h +const juce::StringArray Parameters::delayModes = { + "Stereo", + "Cross Fb", + "Ping Pong LR", + "Ping Pong RL", +}; + +// keep this syncronized with the LFOShape enum in Types.h const juce::StringArray Parameters::lfoShapes = { "SIN", "TRI", @@ -131,6 +135,11 @@ const juce::StringArray Parameters::lfoShapes = { "S&H", }; +// keep this syncronized with the InsertEffectType enum in Types.h +const juce::StringArray Parameters::insertEffects = { + "Chorus", + "Lofi", +}; Parameters::Parameters(juce::AudioProcessorValueTreeState& apvts) { @@ -146,7 +155,13 @@ Parameters::Parameters(juce::AudioProcessorValueTreeState& apvts) castParameter(apvts, feedbackParamID, m_feedbackParam); castParameter(apvts, stereoParamID, m_stereoParam); castParameter(apvts, offsetParamID, m_offsetParam); + castParameter(apvts, insertEffectTypeParamID, m_insertEffectTypeParam); castParameter(apvts, chorusIntensityParamID, m_chorusIntensityParam); + castParameter(apvts, chorusModRateParamID, m_chorusModRateParam); + castParameter(apvts, chorusModDepthParamID, m_chorusModDepthParam); + castParameter(apvts, lofiSampleRateParamID, m_lofiSampleRateParam); + castParameter(apvts, lofiBitDepthParamID, m_lofiBitDepthParam); + castParameter(apvts, lofiMixLevelParamID, m_lofiMixLevelParam); castParameter(apvts, lowCutFreqParamID, m_lowCutFreqParam); castParameter(apvts, lowCutQParamID, m_lowCutQParam); castParameter(apvts, lowCutModRateParamID, m_lowCutModRateParam); @@ -164,11 +179,7 @@ Parameters::Parameters(juce::AudioProcessorValueTreeState& apvts) castParameter(apvts, highCutModNoteParamID, m_highCutModNoteParam); castParameter(apvts, highCutModShapeParamID, m_highCutModShapeParam); castParameter(apvts, highCutModInvertParamID, m_highCutModInvertParam); - castParameter(apvts, chorusModRateParamID, m_chorusModRateParam); - castParameter(apvts, chorusModDepthParamID, m_chorusModDepthParam); - castParameter(apvts, lofiSampleRateParamID, m_lofiSampleRateParam); - castParameter(apvts, lofiBitDepthParamID, m_lofiBitDepthParam); - castParameter(apvts, lofiMixLevelParamID, m_lofiMixLevelParam); + } juce::AudioProcessorValueTreeState::ParameterLayout Parameters::createParameterLayout() @@ -261,6 +272,12 @@ juce::AudioProcessorValueTreeState::ParameterLayout Parameters::createParameterL juce::AudioParameterFloatAttributes() .withStringFromValueFunction(stringFromDecibels) )); + layout.add(std::make_unique( + insertEffectTypeParamID, + "Effect Type", + insertEffects, + 0 + )); layout.add(std::make_unique( lowCutFreqParamID.getParamID(), "Low Cut Frequency", @@ -523,6 +540,7 @@ void Parameters::update(const Tempo& tempo) noexcept m_delayNoteL = m_delayNoteLParam->getIndex(); m_delayNoteR = m_delayNoteRParam->getIndex(); m_delayMode = m_delayModeParam->getIndex(); + m_insertEffectType = m_insertEffectTypeParam->getIndex(); m_lowCutModTempoSync = m_lowCutModTempoSyncParam->get(); m_lowCutModNote = m_lowCutModNoteParam->getIndex(); m_highCutModTempoSync = m_highCutModTempoSyncParam->get(); diff --git a/Source/Parameters.h b/Source/Parameters.h index 995465f..6db8e33 100644 --- a/Source/Parameters.h +++ b/Source/Parameters.h @@ -31,6 +31,7 @@ const juce::ParameterID highCutModTempoSyncParamID{ "highCutModTempoSync", 1 }; const juce::ParameterID highCutModNoteParamID{ "highCutModNote", 1 }; const juce::ParameterID highCutModShapeParamID{ "highCutModShape", 1 }; const juce::ParameterID highCutModInvertParamID{ "highCutModInvert", 1 }; +const juce::ParameterID insertEffectTypeParamID{ "insertEffectType", 1 }; const juce::ParameterID chorusIntensityParamID{ "chorusIntensity", 1 }; const juce::ParameterID chorusModRateParamID{ "chorusModRate", 1 }; const juce::ParameterID chorusModDepthParamID{ "chorusModDepth", 1 }; @@ -62,6 +63,7 @@ class Parameters { static const juce::StringArray delayNoteLengths; static const juce::StringArray modNoteLengths; static const juce::StringArray lfoShapes; + static const juce::StringArray insertEffects; void prepareToPlay(double sampleRate) noexcept; void update(const Tempo& tempo) noexcept; @@ -97,13 +99,15 @@ class Parameters { int highCutModNote() const { return m_highCutModNote; } int highCutModShape() const { return m_highCutModShape; } bool highCutModInvert() const { return m_highCutModInvert; } + int insertEffectType() const { return m_insertEffectType; } float chorusIntensity() const { return m_chorusIntensity; } float chorusModRate() const { return m_chorusModRate; } float chorusModDepth() const { return m_chorusModDepth; } float lofiSampleRate() const { return m_lofiSampleRate; } float lofiBitDepth() const { return m_lofiBitDepth; } float lofiMixLevel() const { return m_lofiMixLevel; } - private: + +private: juce::AudioParameterFloat* m_gainParam; juce::AudioParameterFloat* m_delayTimeLParam; juce::AudioParameterFloat* m_delayTimeRParam; @@ -116,6 +120,7 @@ class Parameters { juce::AudioParameterFloat* m_feedbackParam; juce::AudioParameterFloat* m_stereoParam; juce::AudioParameterFloat* m_offsetParam; + juce::AudioParameterChoice* m_insertEffectTypeParam; juce::AudioParameterFloat* m_lowCutFreqParam; juce::AudioParameterFloat* m_lowCutQParam; juce::AudioParameterFloat* m_lowCutModRateParam; @@ -191,6 +196,7 @@ class Parameters { int m_highCutModNote = 0; int m_highCutModShape = 0; bool m_highCutModInvert = false; + int m_insertEffectType = 0; float m_chorusIntensity = 0.0f; float m_chorusModRate = defaultChorusModRate; float m_chorusModDepth = defaultChorusModDepth; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index e03d99d..735f88b 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -55,13 +55,22 @@ ViiveAudioProcessorEditor::ViiveAudioProcessorEditor(ViiveAudioProcessor& p) m_outputGroup.addAndMakeVisible(m_gainKnob); addAndMakeVisible(m_outputGroup); - m_chorusGroup.setText("Chorus"); + m_chorusGroup.setText(""); m_chorusGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); m_chorusGroup.addAndMakeVisible(m_chorusIntensityKnob); m_chorusGroup.addAndMakeVisible(m_chorusModRateKnob); m_chorusGroup.addAndMakeVisible(m_chorusModDepthKnob); addAndMakeVisible(m_chorusGroup); + m_lofiGroup.setText(""); + m_lofiGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); + m_lofiGroup.addAndMakeVisible(m_lofiSampleRateKnob); + m_lofiGroup.addAndMakeVisible(m_lofiBitDepthKnob); + m_lofiGroup.addAndMakeVisible(m_lofiMixLevelKnob); + addChildComponent(m_lofiGroup); +// addAndMakeVisible(m_lofiGroup); + addAndMakeVisible(m_insertEffectTypeSelector); + addAndMakeVisible(m_meter); #if JUCE_DEBUG @@ -80,6 +89,7 @@ ViiveAudioProcessorEditor::ViiveAudioProcessorEditor(ViiveAudioProcessor& p) m_audioProcessor.apvts.addParameterListener(tempoSyncRParamID.getParamID(), this); m_audioProcessor.apvts.addParameterListener(lowCutModTempoSyncParamID.getParamID(), this); m_audioProcessor.apvts.addParameterListener(highCutModTempoSyncParamID.getParamID(), this); + m_audioProcessor.apvts.addParameterListener(insertEffectTypeParamID.getParamID(), this); #if JUCE_DEBUG setSize(770, 670); #else @@ -93,6 +103,7 @@ ViiveAudioProcessorEditor::~ViiveAudioProcessorEditor() m_audioProcessor.apvts.removeParameterListener(tempoSyncRParamID.getParamID(), this); m_audioProcessor.apvts.removeParameterListener(lowCutModTempoSyncParamID.getParamID(), this); m_audioProcessor.apvts.removeParameterListener(highCutModTempoSyncParamID.getParamID(), this); + m_audioProcessor.apvts.removeParameterListener(insertEffectTypeParamID.getParamID(), this); } //============================================================================== @@ -115,6 +126,8 @@ void ViiveAudioProcessorEditor::resized() int height = bounds.getHeight() - 20; #endif m_chorusGroup.setBounds(bounds.getWidth() - 360, y + 35, 300, 165); + m_lofiGroup.setBounds(bounds.getWidth() - 360, y + 35, 300, 165); + m_delayGroup.setBounds(10, y + 35, m_chorusGroup.getX() - 20, 165); m_outputGroup.setBounds(bounds.getWidth() - 360, m_delayGroup.getBottom() + 10, 300, 155); m_filterGroup.setBounds(10, m_delayGroup.getBottom() + 10, m_outputGroup.getX() - 20, 265); @@ -130,6 +143,8 @@ void ViiveAudioProcessorEditor::resized() m_modeSelector.setTopLeftPosition(10, y); + m_insertEffectTypeSelector.setCentrePosition(m_chorusGroup.getX()+m_chorusGroup.getWidth()/2, m_chorusGroup.getY() + 15); + m_offsetKnob.setTopLeftPosition(m_feedbackKnob.getRight() + 20, 20); m_lowCutFreqKnob.setTopLeftPosition(20, 20); m_lowCutQKnob.setTopLeftPosition(m_lowCutFreqKnob.getRight() + 20, 20); @@ -161,10 +176,15 @@ void ViiveAudioProcessorEditor::resized() m_stereoKnob.setTopLeftPosition(20, 20); m_mixKnob.setTopLeftPosition(m_stereoKnob.getRight() + 20, 20); m_gainKnob.setTopLeftPosition(m_mixKnob.getRight() + 20, 20); + m_chorusIntensityKnob.setTopLeftPosition(20, 20); m_chorusModRateKnob.setTopLeftPosition(m_chorusIntensityKnob.getRight() + 20, 20); m_chorusModDepthKnob.setTopLeftPosition(m_chorusModRateKnob.getRight() + 20, 20); + m_lofiMixLevelKnob.setTopLeftPosition(20, 20); + m_lofiSampleRateKnob.setTopLeftPosition(m_lofiMixLevelKnob.getRight() + 20, 20); + m_lofiBitDepthKnob.setTopLeftPosition(m_lofiSampleRateKnob.getRight() + 20, 20); + m_meter.setBounds(m_outputGroup.getRight() + 15, y + 45, 35, height - 45); #if JUCE_DEBUG @@ -182,18 +202,28 @@ void ViiveAudioProcessorEditor::updateSyncedKnobs(bool syncLActive, bool syncRAc m_lowCutModNoteKnob.setVisible(lowCutSyncActive); m_highCutModRateKnob.setVisible(!highCutSyncActive); m_highCutModNoteKnob.setVisible(highCutSyncActive); - DBG("Updated synced knobs: " << (syncLActive ? "Sync L active, " : "Sync L inactive, ") << (syncRActive ? "Sync R active, " : "Sync R inactive, ") << (lowCutSyncActive ? "Low Cut Sync active, " : "Low Cut Sync inactive, ") << (highCutSyncActive ? "High Cut Sync active" : "High Cut Sync inactive")); +} + +void ViiveAudioProcessorEditor::updateInsertEffectControls(int insertEffectType) +{ + DBG("Insert effect type changed: " << insertEffectType); + bool isChorus = (insertEffectType == 0); + bool isLofi = (insertEffectType == 1); + m_chorusGroup.setVisible(isChorus); + m_lofiGroup.setVisible(isLofi); } void ViiveAudioProcessorEditor::parameterChanged(const juce::String& paramID, float newValue) { - if (paramID == tempoSyncLParamID.getParamID() || paramID == tempoSyncRParamID.getParamID() || paramID == lowCutModTempoSyncParamID.getParamID() || paramID == highCutModTempoSyncParamID.getParamID()){ + if (paramID == tempoSyncLParamID.getParamID() || paramID == tempoSyncRParamID.getParamID() || paramID == lowCutModTempoSyncParamID.getParamID() || paramID == highCutModTempoSyncParamID.getParamID() || paramID == insertEffectTypeParamID.getParamID()){ juce::MessageManager::callAsync([this] { bool syncL = m_audioProcessor.apvts.getParameter(tempoSyncLParamID.getParamID())->getValue() > 0.5f; bool syncR = m_audioProcessor.apvts.getParameter(tempoSyncRParamID.getParamID())->getValue() > 0.5f; bool lowCutSync = m_audioProcessor.apvts.getParameter(lowCutModTempoSyncParamID.getParamID())->getValue() > 0.5f; bool highCutSync = m_audioProcessor.apvts.getParameter(highCutModTempoSyncParamID.getParamID())->getValue() > 0.5f; updateSyncedKnobs(syncL, syncR, lowCutSync, highCutSync); + auto* param = dynamic_cast(m_audioProcessor.apvts.getParameter(insertEffectTypeParamID.getParamID())); + updateInsertEffectControls(param->getIndex()); }); } } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 8f83751..7e37cb9 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -59,12 +59,17 @@ class ViiveAudioProcessorEditor : public juce::AudioProcessorEditor, RotaryKnob m_chorusModRateKnob{ "Mod Rate", m_audioProcessor.apvts, chorusModRateParamID.getParamID() }; RotaryKnob m_chorusModDepthKnob{ "Mod Depth", m_audioProcessor.apvts, chorusModDepthParamID.getParamID() }; + RotaryKnob m_lofiMixLevelKnob{ "Mix", m_audioProcessor.apvts, lofiMixLevelParamID.getParamID() }; + RotaryKnob m_lofiSampleRateKnob{ "Sample Rate", m_audioProcessor.apvts, lofiSampleRateParamID.getParamID() }; + RotaryKnob m_lofiBitDepthKnob{ "Bit Depth", m_audioProcessor.apvts, lofiBitDepthParamID.getParamID() }; + SyncButton m_tempoSyncLButton{ "Sync", m_audioProcessor.apvts, tempoSyncLParamID.getParamID() }; SyncButton m_tempoSyncRButton{ "Sync", m_audioProcessor.apvts, tempoSyncRParamID.getParamID() }; SyncButton m_lowCutModTempoSyncButton{ "Sync", m_audioProcessor.apvts, lowCutModTempoSyncParamID.getParamID() }; SyncButton m_highCutModTempoSyncButton{ "Sync", m_audioProcessor.apvts, highCutModTempoSyncParamID.getParamID() }; Selector m_modeSelector{ "Delay Mode:", m_audioProcessor.apvts, delayModeParamID.getParamID(), Parameters::delayModes, 215, 0.0f, 85, 25 }; + Selector m_insertEffectTypeSelector{ "", m_audioProcessor.apvts, insertEffectTypeParamID.getParamID(), Parameters::insertEffects, 100, 14.0f, 0, 30 }; Selector m_lowCutModShapeSelector{ "", m_audioProcessor.apvts, lowCutModShapeParamID.getParamID(), Parameters::lfoShapes, 60, 12.0f }; Selector m_highCutModShapeSelector{ "", m_audioProcessor.apvts, highCutModShapeParamID.getParamID(), Parameters::lfoShapes, 60, 12.0f }; @@ -76,7 +81,7 @@ class ViiveAudioProcessorEditor : public juce::AudioProcessorEditor, juce::HashMap m_linkedParams; - juce::GroupComponent m_delayGroup, m_filterGroup, m_outputGroup, m_chorusGroup, m_filterModGroup; + juce::GroupComponent m_delayGroup, m_filterGroup, m_outputGroup, m_chorusGroup, m_lofiGroup, m_filterModGroup; #if JUCE_DEBUG std::unique_ptr m_lfoScope; @@ -84,6 +89,7 @@ class ViiveAudioProcessorEditor : public juce::AudioProcessorEditor, virtual void parameterChanged(const juce::String& paramID, float newValue) override; void updateSyncedKnobs(bool syncLActive, bool syncRActive, bool lowCutSyncActive, bool highCutSyncActive); + void updateInsertEffectControls(int insertEffectType); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ViiveAudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 3789f73..bd880a7 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -119,6 +119,7 @@ void ViiveAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce::M if (m_tempo.getIsPlaying()) m_delayEngine.updateSyncedModulators(m_tempo.getPpqPosition()); m_delayEngine.setDelayMode(m_params.delayMode()); + m_delayEngine.setInsertEffect(m_params.insertEffectType()); auto mainInput = getBusBuffer(buffer, true, 0); const float* inputDataL = mainInput.getReadPointer(0); @@ -126,7 +127,7 @@ void ViiveAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce::M auto mainOutput = getBusBuffer(buffer, false, 0); float* outputDataL = mainOutput.getWritePointer(0); float* outputDataR = mainOutput.getWritePointer(1); - + float maxL = 0.0f, maxR = 0.0f; for (int sample = 0; sample < buffer.getNumSamples(); ++sample) diff --git a/Source/Types.h b/Source/Types.h index e0374c0..38eeb57 100644 --- a/Source/Types.h +++ b/Source/Types.h @@ -2,4 +2,12 @@ enum Channel { Left = 0, Right = 1 }; +// keep these syncronized with the string arrays in Parameters.cpp +// Parameters::delayModes +enum class DelayMode { Stereo, Cross, PingPongLR, PingPongRL}; + +// Parameters::lfoShapes enum class LFOShape { Sine, Triangle, Square, SawUp, SawDown, SampleAndHold }; + +// Parameters::insertEffects +enum class InsertEffectType { Chorus, Lofi };