Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion Source/BitCrusher.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#pragma once
#include <JuceHeader.h>
#include "Parameters.h"

class BitCrusher
{
Expand Down
5 changes: 4 additions & 1 deletion Source/ChorusEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions Source/ChorusEngine.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#pragma once
#include <JuceHeader.h>
#include "InsertEffect.h"
#include "Chorus.h"
#include "Parameters.h"

using Filter = juce::dsp::StateVariableTPTFilter<float>;



class ChorusEngine {
class ChorusEngine : public InsertEffect
{
public:
ChorusEngine();
~ChorusEngine() = default;
Expand All @@ -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 };
Expand All @@ -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)
};
18 changes: 13 additions & 5 deletions Source/DelayEngine.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "DelayEngine.h"
#include "DSP.h"
#include "Types.h"

DelayEngine::DelayEngine()
{
Expand All @@ -19,23 +18,22 @@ void DelayEngine::prepareToPlay(double sampleRate, int samplesPerBlock) noexcept
spec.numChannels = 2;
m_sampleRate = static_cast<float>(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<float>(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;
Expand All @@ -52,6 +50,15 @@ void DelayEngine::setDelayMode(const int modeIndex)
}
}

void DelayEngine::setInsertEffect(const int effectIndex)
{
InsertEffectType newEffect = static_cast<InsertEffectType>(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;
Expand All @@ -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)
Expand Down Expand Up @@ -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);

Expand Down
11 changes: 6 additions & 5 deletions Source/DelayEngine.h
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
#pragma once
#include <JuceHeader.h>
#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<float>;

enum class DelayMode { Stereo, Cross, PingPongLR, PingPongRL};

class DelayEngine
{
public:
Expand All @@ -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<float>& getLowCutLfoValue() { return m_filterEngine.getLowCutLfoValue(); }
Expand All @@ -45,8 +45,9 @@ class DelayEngine
FilterEngine m_filterEngine;
Filter m_feedbackHighpass;
juce::dsp::Compressor<float> m_feedbackCompressor;
ChorusEngine m_chorusEngine;

InsertEffectSelector m_insertEffectSelector;
InsertEffectType m_currentInsertEffect = InsertEffectType::Chorus;

float m_sampleRate = 0.0f;

float m_coeff = 0.0f;
Expand Down
15 changes: 15 additions & 0 deletions Source/InsertEffect.h
Original file line number Diff line number Diff line change
@@ -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;
};
46 changes: 46 additions & 0 deletions Source/InsertEffectSelector.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
23 changes: 23 additions & 0 deletions Source/InsertEffectSelector.h
Original file line number Diff line number Diff line change
@@ -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;
};
2 changes: 1 addition & 1 deletion Source/LofiEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ LofiEngine::LofiEngine()
{
}

void LofiEngine::prepareToPlay(double sampleRate) noexcept
void LofiEngine::prepareToPlay(double sampleRate, int samplesPerBlock) noexcept
{
m_sampleRate = static_cast<float>(sampleRate);
m_bitCrusherL.prepareToPlay(m_sampleRate);
Expand Down
12 changes: 7 additions & 5 deletions Source/LofiEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

#include <JuceHeader.h>
#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 };
Expand Down
Loading
Loading