Last active
October 18, 2024 15:15
-
-
Save oconnelltoby/2e824c0070ce92cb37628aaf4acd5ccf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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