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
71 changes: 54 additions & 17 deletions Software/GuitarPedal/Effect-Modules/delay_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

using namespace bkshepherd;

static const char *s_waveBinNames[5] = {"Sine", "Triangle", "Saw", "Ramp",
"Square"}; //, "Poly Tri", "Poly Saw", "Poly Sqr"}; // Horrible loud sound when switching to
static const char *s_waveBinNames[6] = {"Sine", "Triangle", "Saw", "Ramp",
"Square", "Tape"}; //, "Poly Tri", "Poly Saw", "Poly Sqr"}; // Horrible loud sound when switching to
// poly tri, not every time, TODO whats going on? (I suspect electro smith broke
// the poly tri osc, the same happens in the tremolo too)
static const char *s_modParamNames[4] = {"None", "DelayTime", "DelayLevel", "DelayPan"};
Expand Down Expand Up @@ -109,7 +109,7 @@ static const ParameterMetaData s_metaData[s_paramCount] = {
{
name : "Mod Wave",
valueType : ParameterValueType::Binned,
valueBinCount : 5,
valueBinCount : 6,
valueBinNames : s_waveBinNames,
defaultValue : {.uint_value = 0},
knobMapping : -1,
Expand Down Expand Up @@ -202,6 +202,8 @@ void DelayModule::Init(float sample_rate) {
modOsc.Init(sample_rate);
modOsc.SetAmp(1.0);

modTape.Init(sample_rate);

CalculateDelayMix();
}

Expand Down Expand Up @@ -235,32 +237,67 @@ void DelayModule::ParameterChanged(int parameter_id) {
void DelayModule::ProcessModulation() {
int modParam = (GetParameterAsBinnedValue(9) - 1);
// Calculate Modulation
modOsc.SetWaveform(GetParameterAsBinnedValue(10) - 1);

if (GetParameterAsBool(11)) { // If mod frequency synced to delay time, override mod rate setting
float dividor;
if (modParam == 2 || modParam == 3) {
dividor = 2.0;
int waveForm = GetParameterAsBinnedValue(10) - 1;
float wowDepth = 2.0f;
float flutterDepth = 2.0f;
if (waveForm == 5) { // If using tape modulation
float freq = GetParameterAsFloat(8);
float wowRate = 0.2f + 2.0f * freq;
float flutterRate = 2.0f + 5.0f * freq;
m_currentMod = modTape.GetTapeSpeed(wowRate, flutterRate, wowDepth, flutterDepth);
} else {
modOsc.SetWaveform(waveForm);

if (GetParameterAsBool(11)) { // If mod frequency synced to delay time, override mod rate setting
float dividor;
if (modParam == 2 || modParam == 3) {
dividor = 2.0;
} else {
dividor = 4.0;
}
float freq = (effect_samplerate / delayLeft.delayTarget) / dividor;
modOsc.SetFreq(freq);
} else {
dividor = 4.0;
modOsc.SetFreq(m_modOscFreqMin + (m_modOscFreqMax - m_modOscFreqMin) * GetParameterAsFloat(8));
}
float freq = (effect_samplerate / delayLeft.delayTarget) / dividor;
modOsc.SetFreq(freq);
} else {
modOsc.SetFreq(m_modOscFreqMin + (m_modOscFreqMax - m_modOscFreqMin) * GetParameterAsFloat(8));

// Ease the effect value into it's target to avoid clipping with square or sawtooth waves
fonepole(m_currentMod, modOsc.Process(), .01f);
}

// Ease the effect value into it's target to avoid clipping with square or sawtooth waves
fonepole(m_currentMod, modOsc.Process(), .01f);
float mod = m_currentMod;
float mod_amount = GetParameterAsFloat(7);

// {"None", "DelayTime", "DelayLevel", "Level", "DelayPan"};
if (modParam == 1) {
float delayTarget;
float timeParam = GetParameterAsFloat(0);
delayLeft.delayTarget = m_delaySamplesMin + (m_delaySamplesMax - m_delaySamplesMin) * timeParam + mod * mod_amount * 500;
delayRight.delayTarget = m_delaySamplesMin + (m_delaySamplesMax - m_delaySamplesMin) * timeParam + mod * mod_amount * 500;
const float D_min = 1.0f; // minimum allowable delay time: 1 sample

if (waveForm == 5) {
// Tape flutter mode with dynamic min
const float M = wowDepth + 0.2f * flutterDepth; // Max amplitude of tape modulation.
const float depth = 500.0f;

float baseMin = D_min + M * mod_amount * depth;
float baseMax = m_delaySamplesMax;

float base = baseMin + (baseMax - baseMin) * timeParam;

delayTarget = base + mod * mod_amount * depth;
} else {
delayTarget = m_delaySamplesMin + (m_delaySamplesMax - m_delaySamplesMin) * timeParam + mod * mod_amount * 500;
}
if (delayTarget < D_min) {
delayTarget = D_min;
}
if (delayTarget > MAX_DELAY_NORM - 2) {
delayTarget = MAX_DELAY_NORM - 2;
}

delayLeft.delayTarget = delayTarget;
delayRight.delayTarget = delayTarget;
} else if (modParam == 2) {
float mod_level = mod * mod_amount + (1.0 - mod_amount);
delayLeft.level = mod_level;
Expand Down
2 changes: 2 additions & 0 deletions Software/GuitarPedal/Effect-Modules/delay_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "Delays/delayline_reverse.h"
#include "Delays/delayline_revoct.h"
#include "../Util/tape_modulator.h"
#include "base_effect_module.h"
#include "daisysp.h"
#include <stdint.h>
Expand Down Expand Up @@ -141,6 +142,7 @@ class DelayModule : public BaseEffectModule {
Oscillator modOsc;
float m_modOscFreqMin;
float m_modOscFreqMax;
TapeModulator modTape;

// Delays
delayRevOct delayLeft;
Expand Down
72 changes: 72 additions & 0 deletions Software/GuitarPedal/Util/tape_modulator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include "tape_modulator.h"
#include "daisy_seed.h"

// Actual definitions of the static members
DSY_SDRAM_BSS uint8_t TapeModulator::perm_[512];

void TapeModulator::Init(float sample_rate) {
for(int i = 0; i < 256; i++) {
perm_[i] = perm_[i + 256] = p_[i];
}
t_wow_ = 0.0f;
t_flutter_ = 0.0f;
sample_rate_ = sample_rate;
}

float TapeModulator::Perlin1D(float x)
{
int X = (int)floorf(x) & 255; // lattice coordinate
x -= floorf(x); // position inside cell
float u = fade(x); // fade curve

int a = perm_[X];
int b = perm_[X + 1];

return lerp(u, grad(a, x), grad(b, x - 1)); // range ~[-1,1]
}

// Fractal Brownian Motion for 1D Perlin
float TapeModulator::Fbm1D(float x, int octaves, float lacunarity, float gain)
{
float sum = 0.0f;
float amp = 1.0f;
float freq = 1.0f;
float maxSum = 0.0f;

for(int i = 0; i < octaves; i++)
{
sum += Perlin1D(x * freq) * amp;
maxSum += amp;
freq *= lacunarity;
amp *= gain;
}

return sum / maxSum; // normalized to ~[-1,1]
}

float TapeModulator::GetTapeSpeed(float wow_rate, float flutter_rate, float wow_depth, float flutter_depth) {
// Slow WOW component (FBM, smooth)
float wow = Fbm1D(t_wow_, octaves_wow_, 2.0f, 0.5f);

// Tiny FLUTTER component (faster, low amplitude)
float flutter = Fbm1D(t_flutter_, octaves_flutter_, 2.0f, 0.5f);

// Advance time for next sample
t_wow_ += wow_rate / sample_rate_;
t_flutter_ += flutter_rate / sample_rate_;

// Wrap time accumulators to prevent floating-point precision loss
// Wrap at 256.0 since Perlin uses modulo 256 for lattice coordinates
if(t_wow_ >= 256.0f) {
t_wow_ -= 256.0f;
}
if(t_flutter_ >= 256.0f) {
t_flutter_ -= 256.0f;
}

// Combine, scaled by depth
// Assume potentiometer controls the main depth for wow; flutter is small fraction
float speed = wow_depth * wow + (flutter_depth * 0.2f) * flutter;

return speed;
}
78 changes: 78 additions & 0 deletions Software/GuitarPedal/Util/tape_modulator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#pragma once
#include "daisysp.h"

/**
Tape modulation effect that simulates analog tape wow and flutter using Perlin noise.

Uses 1D Perlin noise with Fractal Brownian Motion (FBM) to generate smooth,
natural-sounding speed variations that mimic the imperfections of analog tape machines.
*/
class TapeModulator {
public:
/** Initializes the TapeModulator module.
\param sample_rate - The sample rate of the audio engine being run.
*/
void Init(float sample_rate);

/** Generates tape speed variation based on wow and flutter parameters.
\param wow_rate - Rate of the slow wow modulation in Hz
\param flutter_rate - Rate of the fast flutter modulation in Hz
\param wow_depth - Depth/amount of wow effect (scaling factor)
\param flutter_depth - Depth/amount of flutter effect (scaling factor)
\return Speed variation value based on combined modulation sources
*/
float GetTapeSpeed(float wow_rate, float flutter_rate, float wow_depth, float flutter_depth);

private:
float sample_rate_; ///< Audio engine sample rate
float t_wow_; ///< Time accumulator for wow modulation
float t_flutter_; ///< Time accumulator for flutter modulation
int octaves_wow_ = 2; ///< Number of octaves for wow FBM
int octaves_flutter_ = 1; ///< Number of octaves for flutter FBM

/** Generates 1D Perlin noise value.
\param x - Input coordinate for noise lookup
\return Noise value in approximate range [-1, 1]
*/
float Perlin1D(float x);

/** Generates Fractal Brownian Motion using layered Perlin noise.
\param x - Input coordinate for noise lookup
\param octaves - Number of noise layers to combine
\param lacunarity - Frequency multiplier between octaves (typically 2.0)
\param gain - Amplitude multiplier between octaves (typically 0.5)
\return Normalized FBM value in range [-1, 1]
*/
float Fbm1D(float x, int octaves, float lacunarity, float gain);

// Original permutation table from Ken Perlin
static inline constexpr uint8_t p_[256] = {
151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,
140,36,103,30,69,142,8,99,37,240,21,10,23,190, 6,148,247,120,
234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,
33,88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,
71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,
133,230,220,105,92,41,55,46,245,40,244,102,143,54, 65,25,
63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,
226,250,124,123, 5,202,38,147,118,126,255,82,85,212,207,206,
59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,
152, 2,44,154,163, 70,221,153,101,155,167, 43,172, 9,129,22,
39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,
97,228,251,34,242,193,238,210,144,12,191,179,162,241, 81,51,
145,235,249,14,239,107,49,192,214, 31,181,199,106,157,184, 84,
204,176,115,121,50,45,127, 4,150,254,138,236,205, 93,222,114,
67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
/// Permutation table duplicated for wraparound (perm[512] from p[256])
static uint8_t perm_[512];

/// Perlin fade curve: 6t^5 - 15t^4 + 10t^3
inline float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }

/// Linear interpolation
inline float lerp(float t, float a, float b) { return a + t * (b - a); }

/// Simple 1D gradient function for Perlin noise
inline float grad(int hash, float x) { return (hash & 1) == 0 ? x : -x; }
};