Skip to content

Instantly share code, notes, and snippets.

@oconnelltoby
Last active October 18, 2024 15:15
Show Gist options
  • Select an option

  • Save oconnelltoby/2e824c0070ce92cb37628aaf4acd5ccf to your computer and use it in GitHub Desktop.

Select an option

Save oconnelltoby/2e824c0070ce92cb37628aaf4acd5ccf to your computer and use it in GitHub Desktop.
// TobyDelay for Hothouse DIY DSP Platform
// Copyright (C) 2024 Toby O'Connell <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// ### Uncomment if IntelliSense can't resolve DaisySP-LGPL classes ###
// #include "daisysp-lgpl.h"
#include "daisysp.h"
#include "hothouse.h"
#include <algorithm>
#include <iterator>
#define MAX_DELAY static_cast<size_t>(48000 * 4.0f)
#define BLOCK_SIZE static_cast<size_t>(4)
using clevelandmusicco::Hothouse;
using daisy::AudioHandle;
using daisy::Led;
using daisy::Parameter;
using daisy::SaiHandle;
using daisy::System;
using daisysp::DelayLine;
using daisysp::fonepole;
using daisysp::Oscillator;
Hothouse hothouse;
DelayLine<float, MAX_DELAY> DSY_SDRAM_BSS delay_line;
class TapTempo {
size_t time_since_first_tap;
size_t tap_count;
size_t time_per_tap;
size_t time_increment;
size_t GapCount() {
return tap_count - 1;
}
public:
void Init(size_t time_increment) {
this->time_increment = time_increment;
}
void Tap() {
tap_count++;
if (tap_count > 1) {
time_per_tap = time_since_first_tap / (float)GapCount();
}
}
void Process() {
if (tap_count > 0) {
time_since_first_tap += time_increment;
// Enough time has passed to have missed 2 taps
size_t time_threshold = (GapCount() + 2) * time_per_tap;
if (tap_count > 1 && time_since_first_tap > time_threshold) {
time_since_first_tap = 0;
tap_count = 0;
}
}
}
size_t GetTapCount() {
return tap_count;
}
size_t GetTimePerTap() {
return time_per_tap;
}
};
struct Delay {
DelayLine<float, MAX_DELAY> *delay_line;
void Init(DelayLine<float, MAX_DELAY> *delay_line) {
this->delay_line = delay_line;
}
void SetDelay(float delay) {
delay_line->SetDelay(delay);
}
float Process(float in, bool active) {
float read = delay_line->Read();
if (active) {
delay_line->Write(read);
return read;
} else {
delay_line->Write(in);
return in;
}
}
private:
float current_delay_time;
};
struct RMSCalculator {
private:
float accumulator = 0;
size_t windowSize = 1;
public:
void Init(size_t windowSize) {
this->windowSize = windowSize;
}
float Process(float in) {
accumulator -= accumulator / (float)windowSize;
accumulator += in * in;
return sqrt(accumulator / (float)windowSize);
}
};
Delay delay;
TapTempo tap_tempo;
Led tap_led;
Oscillator lfo;
RMSCalculator rms_calculator;
Parameter knob1;
Parameter knob2;
Parameter knob3;
Parameter knob4;
Parameter knob5;
Parameter knob6;
size_t activation_counter;
Led led_active;
bool active = false;
bool footswitch_one_previously_pressed = false;
std::random_device random_device;
std::mt19937 generator(random_device());
float delay_dividers[] = {1, 1.5, 2, 3, 4, 8};
float delay_divider = delay_dividers[0];
float volume_high = false;
float volume_previously_high = false;
void InitMasterParameters() {
knob1.Init(hothouse.knobs[Hothouse::KNOB_1], 0, 1, Parameter::LINEAR);
knob2.Init(hothouse.knobs[Hothouse::KNOB_2], 0, 1, Parameter::LINEAR);
knob3.Init(hothouse.knobs[Hothouse::KNOB_3], 0, 1, Parameter::LINEAR);
knob4.Init(hothouse.knobs[Hothouse::KNOB_4], 0, 1, Parameter::LINEAR);
knob5.Init(hothouse.knobs[Hothouse::KNOB_5], 0, 1, Parameter::LINEAR);
knob6.Init(hothouse.knobs[Hothouse::KNOB_6], 0, 1, Parameter::LINEAR);
}
void InitTapTempo() {
tap_tempo.Init(BLOCK_SIZE);
}
void InitDelays() {
delay_line.Init();
delay.Init(&delay_line);
}
void InitRMSCalculator() {
rms_calculator.Init(0.01 * hothouse.AudioSampleRate() / BLOCK_SIZE);
}
float GetDelaySize() {
return tap_tempo.GetTimePerTap() / delay_divider;
}
void UpdateDelaySize() {
delay.SetDelay(GetDelaySize());
}
void SetDelayDivider() {
float choice_weights[] = {
knob1.Process(),
knob2.Process(),
knob3.Process(),
knob4.Process(),
knob5.Process(),
knob6.Process()
};
float sum_of_weight = 0;
for(int i = 0; i < 6; i++) {
sum_of_weight += choice_weights[i];
}
float random_number = (static_cast <float> (rand()) / static_cast <float> (RAND_MAX)) * sum_of_weight;
for(int i = 0; i < 6; i++) {
if(random_number < choice_weights[i]) {
delay_divider = delay_dividers[i];
return;
}
random_number -= choice_weights[i];
}
}
void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) {
hothouse.ProcessAllControls();
tap_tempo.Process();
bool pressed = hothouse.switches[Hothouse::FOOTSWITCH_2].Pressed();
bool retrigger_mode_enabled = hothouse.GetToggleswitchPosition(Hothouse::TOGGLESWITCH_2) == Hothouse::TOGGLESWITCH_DOWN;
if ((pressed && !footswitch_one_previously_pressed) || (volume_high && !volume_previously_high && retrigger_mode_enabled)) {
SetDelayDivider();
// Ensure new foot-presses restart the activation counter
activation_counter = 0;
UpdateDelaySize();
}
if (hothouse.switches[Hothouse::FOOTSWITCH_1].RisingEdge()) {
tap_tempo.Tap();
lfo.Reset();
}
if (tap_tempo.GetTapCount() >= 2) {
float cycles_per_second = hothouse.AudioBlockSize() * hothouse.AudioSampleRate() / tap_tempo.GetTimePerTap();
lfo.SetFreq(cycles_per_second);
UpdateDelaySize();
}
float lfo_value = std::max(0.f, lfo.Process());
tap_led.Set(lfo_value > 0);
tap_led.Update();
activation_counter = pressed ? (activation_counter + BLOCK_SIZE) : 0;
active = activation_counter >= GetDelaySize() && activation_counter > 0;
float dry_level = hothouse.GetToggleswitchPosition(Hothouse::TOGGLESWITCH_1) == Hothouse::TOGGLESWITCH_DOWN ? 1 : 0;
float rms;
for (size_t i = 0; i < size; ++i) {
out[0][i] = out[1][i] = delay.Process(in[0][i], active) + in[0][i] * dry_level;
rms = rms_calculator.Process(in[0][i]) * 10.f;
}
volume_previously_high = volume_high;
if (rms > 0.2) {
volume_high = true;
}
if (rms <= 0.1) {
volume_high = false;
}
// Set state ready for next cycle
footswitch_one_previously_pressed = pressed;
}
int main() {
hothouse.Init();
hothouse.SetAudioBlockSize(BLOCK_SIZE); // Number of samples handled per callback
hothouse.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);
InitRMSCalculator();
InitDelays();
InitTapTempo();
InitMasterParameters();
led_active.Init(hothouse.seed.GetPin(Hothouse::LED_2), false);
tap_led.Init(hothouse.seed.GetPin(Hothouse::LED_1), false);
lfo.Init(hothouse.AudioSampleRate());
lfo.SetFreq(0);
hothouse.StartAdc();
hothouse.StartAudio(AudioCallback);
while (true) {
led_active.Set(active ? 1.0f : 0.0f);
led_active.Update();
System::Delay(10);
hothouse.CheckResetToBootloader();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment