diff --git a/code/daisy-looper/README.md b/code/daisy-looper/README.md deleted file mode 100644 index bacab1a29c72756576855193ee23afec135b449e..0000000000000000000000000000000000000000 --- a/code/daisy-looper/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# daisy-looper - - - -# What does the looper do? - -*daisy-looper* is a software that can be installed on the [daisyy hardware](../../README.md) developed in the course *Synthesizer DIY* that took place during winter semester 2023/2024 at HFBK Hamburg. The looper allows you to record audio present at the input and [overdub](https://en.wikipedia.org/wiki/Overdubbing) into up to 5 different virtual "tapes" called *buffers*. The recorded sounds stored in each buffer will be displayed as a waveform and can be played back at different speeds (including reverse speed). - diff --git a/code/daisy-looper/button_grid.h b/code/daisy-looper/button_grid.h deleted file mode 100644 index 96395e3a441d14019b08c57eaf5f938a884df272..0000000000000000000000000000000000000000 --- a/code/daisy-looper/button_grid.h +++ /dev/null @@ -1,187 +0,0 @@ -#ifndef Buttongrid_h -#define Buttongrid_h - -#include "Adafruit_SH110X.h" -#include "Adafruit_GFX.h" -#include "potentiometers.h" -#include "buttons.h" -#include "ui.h" - - -extern Potentiometer pot_1, pot_2, pot_3, pot_4, pot_5, pot_6, pot_7; -extern Button button_1, button_2, button_3, button_4, button_5, button_6; -extern Adafruit_SH1106G display; - -// Represents the different types of GridButtons in the UI -enum ButtonType { - BUTTON_TYPE_SIMPLE, // Simple Button that can be pressed - BUTTON_TYPE_TOGGLE, // Toggles between two values (name needs to have a break "\n") - BUTTON_TYPE_MULTITOGGLE, // Toggles between two or more values (name needs to have a break "\n") - BUTTON_TYPE_ENUM, // Toggles between a group of buttons - BUTTON_TYPE_LAST -}; - -// A Gridbutton is a single button within the Ui-Button Grid -// The name can have multiple lines, delimited by "\n" which -// will be used to represent different states for buttons of -// the type BUTTON_TYPE_TOGGLE or BUTTON_TYPE_MULTITOGGLE -// is_home tells us if this is the home button (and thus should -// be rendered inverted) -class GridButton { - public: - const char* name; - const char* description; - GridButton(const char* name, Button *button, bool is_home=false, ButtonType type=BUTTON_TYPE_SIMPLE, int default_value=0, const char* description="") - : - name(name), - button(button), - is_home(is_home), - type(type), - active(default_value), - description(description) - { - // Count the number of lines in the name - for(int i = 0; name[i] != '\0'; i++) { - if(name[i] == '\n') - ++lines; - } - } - Button *button; - ButtonType type; - bool is_home; - bool should_render_description = false; - int active; - int lines = 0; - - // Go to the next option - void next() { - active++; - if (active > lines) { - active = 0; - } - } - - void renderDescription() { - if (should_render_description) { - display.setTextWrap(true); - display.clearDisplay(); - display.setCursor(0,0); - display.setTextColor(SH110X_WHITE); - display.print(description); - display.setTextWrap(false); - } - } -}; - -// The ButtonGrid is a grid of 2×3 = 6 buttons -class ButtonGrid { - public: - ButtonGrid(int home_mode, const GridButton (&grid_buttons)[6]) - : grid_buttons_{ - grid_buttons[0], - grid_buttons[1], - grid_buttons[2], - grid_buttons[3], - grid_buttons[4], - grid_buttons[5] - }, - ui_mode(home_mode) - {} - GridButton grid_buttons_[6]; - int ui_mode; - - void setup() { - for (int n=0; n<6; n++) { - if (!grid_buttons_[n].is_home) { - // Not a home button, display help on long hold and hide on release - // grid_buttons_[n].button->onLongHold([this, n](){ - // grid_buttons_[n].should_render_description = true; - // }); - // grid_buttons_[n].button->onReleased([this, n](){ - // grid_buttons_[n].should_render_description = false; - // }); - } - } - } - - int homeButtonIndex() { - for (int i=0; i<6; i++) { - if (grid_buttons_[i].is_home) { - return i; - } - } - return -1; - } - - void hideAllDescriptions() { - for (int i=0; i<6; i++) { - grid_buttons_[i].should_render_description = false; - } - } - - void render(int button_enum) { - int width = display.width(); - int height = display.height(); - int box_width = int(width/3.0f); - int box_height= int(height/2.0f); - int i = 0; - // Draw boxes (2 rows, 3 columns) - for (int box_y=0; box_y<2; box_y++) { - for (int box_x=0; box_x<3; box_x++) { - // Get the current buttons name - const char* name = grid_buttons_[i].name; - - // Prepare colors - uint16_t bg_color = SH110X_BLACK; - uint16_t text_color = SH110X_WHITE; - - // Home-Buttons have a inverted color scheme - if (grid_buttons_[i].is_home) { - bg_color = SH110X_WHITE; - text_color = SH110X_BLACK; - } - - // Position variables - uint16_t x = box_x * box_width; - uint16_t y = box_y * box_height; - uint16_t xc = x + box_width/2; - uint16_t yc = y + box_height/2; - - // Fill Background - display.fillRect(x, y, box_width, box_height, bg_color); - - // Render the different button types - if (grid_buttons_[i].type == BUTTON_TYPE_TOGGLE) { - centeredTextMarkMulti(name, xc, yc, text_color, grid_buttons_[i].active, 12); - } else if (grid_buttons_[i].type == BUTTON_TYPE_MULTITOGGLE) { - button_multi(name, xc, yc, text_color, grid_buttons_[i].active, grid_buttons_[i].lines); - } else if (grid_buttons_[i].type == BUTTON_TYPE_ENUM) { - bool active = i == button_enum; - centeredTextMark(name, xc, yc, text_color, active); - } else { - centeredText(name, xc, yc, text_color); - } - // Increase ounter for the index of the button - i++; - } - } - - // Draw divider lines - display.drawFastVLine(box_width, 0, height, SH110X_WHITE); - display.drawFastVLine(box_width*2, 0, height, SH110X_WHITE); - display.drawFastHLine(0, box_height, width, SH110X_WHITE); - - // Render Descriptions on top (hence another loop) - i = 0; - // Draw boxes (2 rows, 3 columns) - for (int box_y=0; box_y<2; box_y++) { - for (int box_x=0; box_x<3; box_x++) { - grid_buttons_[i].renderDescription(); - i++; - } - } - } -}; - - -#endif \ No newline at end of file diff --git a/code/daisy-looper/buttons.h b/code/daisy-looper/buttons.h deleted file mode 100644 index 4ab6d9935deb016e332dd482f1080f93688a2b17..0000000000000000000000000000000000000000 --- a/code/daisy-looper/buttons.h +++ /dev/null @@ -1,165 +0,0 @@ -#include "wiring_constants.h" -#ifndef Buttons_h -#define Buttons_h - -#include "Arduino.h" -#include "Adafruit_SH110X.h" -#include "Adafruit_GFX.h" -extern Adafruit_SH1106G display; - -#define DURATION_SHORT_PRESS 800 -#define DURATION_VERY_LONG_PRESS 2000 - - - - - -class Button { - int pin; - bool has_been_pressed; - unsigned long press_start; - unsigned long release_start; - std::function<void()> onPressFunction; - std::function<void()> onHoldFunction; - std::function<void()> onLongHoldFunction; - std::function<void()> onVeryLongHoldFunction; - std::function<void()> onLongPressFunction; - std::function<void()> onVeryLongPressFunction; - std::function<void()> onReleasedFunction; - - public: - Button(int pin); - void init(); - void read(); - unsigned long pressed_since(); - unsigned long released_since(); - - void onPress(std::function<void()> f); - void onHold(std::function<void()> f); - void onLongHold(std::function<void()> f); - void onVeryLongHold(std::function<void()> f); - void onLongPress(std::function<void()> f); - void onVeryLongPress(std::function<void()> f); - void onReleased(std::function<void()> f); - - void reset(); -}; - -Button::Button(int pin) { - this->pin = pin; -} - -void Button::init() { - pinMode(this->pin, INPUT_PULLUP); - this->has_been_pressed = false; - this->press_start = 0; - this->release_start = 0; -} - -void Button::read() { - int is_pressed = !digitalRead(this->pin); - - if (is_pressed && this->press_start == 0) { - this->press_start = millis(); - } - if (!is_pressed && this->has_been_pressed && this->release_start == 0) { - this->release_start = millis(); - } - - unsigned long pressed_since = this->pressed_since(); - unsigned long released_since = this->released_since(); - - if (is_pressed) { - // Fire the callback function all the time while this is being pressed - if (this->onHoldFunction) { this->onHoldFunction(); } - - if (this->pressed_since() > 1000) { - if (this->onLongHoldFunction) { this->onLongHoldFunction(); } - } - if (this->pressed_since() > 5000) { - if (this->onVeryLongHoldFunction) { this->onVeryLongHoldFunction(); } - } - // Serial.print("Pressed since "); - // Serial.println(pressed_since); - if ( released_since > 100) { - this->has_been_pressed = false; - } - } else { - // Not pressed. - if (!this->has_been_pressed) { - if (pressed_since > 0 && pressed_since < DURATION_SHORT_PRESS) { - if (this->onPressFunction) { this->onPressFunction(); } - // Serial.print("Short Press (released after "); - // Serial.print(pressed_since); - // Serial.print(", released since "); - // Serial.print(released_since); - } else if (pressed_since > 0 && pressed_since < DURATION_VERY_LONG_PRESS) { - if (this->onLongPressFunction) { this->onLongPressFunction(); } - // Serial.print("Long Press (released after "); - // Serial.print(pressed_since); - // Serial.println(")"); - } else if (pressed_since > 0 && pressed_since >= DURATION_VERY_LONG_PRESS) { - if (this->onVeryLongPressFunction) { this->onVeryLongPressFunction(); } - // Serial.print("Very Long Press (released after "); - // Serial.print(pressed_since); - // Serial.println(")"); - } - this->press_start = 0; - this->has_been_pressed = true; - this->release_start = millis(); - if (this->onReleasedFunction) { this->onReleasedFunction(); } - } - } -} - -unsigned long Button::pressed_since() { - if ( this->press_start == 0) { - return 0; - } - return millis() - this->press_start; -} - -unsigned long Button::released_since() { - if ( this->release_start == 0) { - return 0; - } - return millis() - this->release_start; -} - -void Button::onPress(std::function<void()> f) { - this->onPressFunction = f; -} - -void Button::onHold(std::function<void()> f) { - this->onHoldFunction = f; -} - -void Button::onLongHold(std::function<void()> f) { - this->onLongHoldFunction = f; -} - -void Button::onVeryLongHold(std::function<void()> f) { - this->onVeryLongHoldFunction = f; -} - -void Button::onLongPress(std::function<void()> f) { - this->onLongPressFunction = f; -} - -void Button::onVeryLongPress(std::function<void()> f) { - this->onVeryLongPressFunction = f; -} - -void Button::onReleased(std::function<void()> f) { - this->onReleasedFunction = f; -} - -void Button::reset() { - this->onPressFunction = NULL; - this->onHoldFunction = NULL; - this->onLongPressFunction = NULL; - this->onVeryLongPressFunction = NULL; -} - - -#endif \ No newline at end of file diff --git a/code/daisy-looper/daisy-looper.ino b/code/daisy-looper/daisy-looper.ino deleted file mode 100644 index b19ec1885feda0039f732ee8acb37ce07b295706..0000000000000000000000000000000000000000 --- a/code/daisy-looper/daisy-looper.ino +++ /dev/null @@ -1,467 +0,0 @@ -#include "DaisyDuino.h" -#include <Wire.h> -#include <Adafruit_GFX.h> -#include <Adafruit_SH110X.h> -#include <MIDI.h> - -#include "leds.h" -#include "potentiometers.h" -#include "buttons.h" -#include "looper.h" -#include "env_follower.h" -#include "helpers.h" -#include "luts.h" -#include "ui.h" -#include "lfo.h" - -MIDI_CREATE_DEFAULT_INSTANCE(); -#define BUFFER_LENGTH_SECONDS 5 - -#define DEBUGMODE - -static const size_t buffer_length = 48000 * BUFFER_LENGTH_SECONDS; -static float DSY_SDRAM_BSS buffer[buffer_length]; -static float DSY_SDRAM_BSS buffer_b[buffer_length]; -static float DSY_SDRAM_BSS buffer_c[buffer_length]; -static float DSY_SDRAM_BSS buffer_d[buffer_length]; -static float DSY_SDRAM_BSS buffer_e[buffer_length]; - - -// Create instances of audio stuff -atoav::Looper looper_a, looper_b, looper_c, looper_d, looper_e; -static atoav::EnvelopeFollower input_envelope_follower; -DSY_SDRAM_BSS ReverbSc reverb; -static Compressor compressor; -Oscillator lfo; -static SampleHold sample_and_hold; -static WhiteNoise noise; -static Metro tick; -static Easer easer; - -// Initialize Buttons -Button button_1 = Button(D7); -Button button_2 = Button(D8); -Button button_3 = Button(D9); -Button button_4 = Button(D10); -Button button_5 = Button(D13); -Button button_6 = Button(D27); - -// Initialize Potentiometers -Potentiometer pot_1 = Potentiometer(A0); -Potentiometer pot_2 = Potentiometer(A1); -Potentiometer pot_3 = Potentiometer(A3); -Potentiometer pot_4 = Potentiometer(A2); -Potentiometer pot_5 = Potentiometer(A4); -Potentiometer pot_6 = Potentiometer(A5); -Potentiometer pot_7 = Potentiometer(A6); - -// RGB LED R G B -RGBLed rgb_led = RGBLed(A10, A11, A9); - - -// OLED Display -#define SCREEN_WIDTH 128 // OLED display width, in pixels -#define SCREEN_HEIGHT 64 // OLED display height, in pixels -#define OLED_RESET -1 // QT-PY / XIAO -Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); - -// User Interface -Ui ui; - -// Daisy -DaisyHardware hw; - -// Variables for the Audio-Callback -size_t num_channels; -float blocksize; -float drywetmix = 0.0f; -float volume = 1.0f; -float reverbmix = 0.0f; -float lfo_amount = 0.0f; -float pitch_val = 0.5f; -float midi_pitch_offset = 0.0f; - -float reverb_tone = 15000.0f; -float reverb_decay = 0.95f; -float lfo_speed = 8.0f; -int lfo_kind = 0; -float rand_pitch_mod = 0.0f; - -//float pressure = 0.0f; - -// Actual audio-processing is orchestrated here -void AudioCallback(float **in, float **out, size_t size) { - float output1, output2 = 0.0f; - float out1, out2; - reverb.SetFeedback(reverb_decay); - reverb.SetLpFreq(reverb_tone); - lfo.SetFreq(lfo_speed); - tick.SetFreq(lfo_speed); - - // Iterate through the samples in the buffer - for (size_t i = 0; i < size; i++) { - float lfo_value = 0.0f; - switch (lfo_kind) { - case LfoKind::LFO_KIND_TRI: - lfo.SetWaveform(Oscillator::WAVE_TRI); - lfo_value = lfo.Process(); - break; - case LfoKind::LFO_KIND_SQR: - lfo.SetWaveform(Oscillator::WAVE_SQUARE); - lfo_value = lfo.Process(); - break; - default: - break; - } - - tick.SetFreq(0.25f+lfo_speed*49.75f); - uint8_t trig = tick.Process(); - float noise_value = noise.Process(); - - switch (lfo_kind) { - case LfoKind::LFO_KIND_RAND: - easer.setFactor(0.01); - break; - case LfoKind::LFO_KIND_JUMP: - easer.setFactor(1.0); - break; - } - - float rand = easer.Process( - sample_and_hold.Process( - trig, - noise_value * 5.0f, - sample_and_hold.MODE_SAMPLE_HOLD - )); - - if (ui.rec_source == REC_SOURCE_NOISE) { - ui.activeLooper()->Record(noise_value * 0.5f); - } - // When the metro ticks, trigger the envelope to start. - float random_amount = lfo_amount * 2.0; - - if (trig) { - // Random LFO - switch (lfo_kind) { - case LfoKind::LFO_KIND_RAND: - rand_pitch_mod = rand * random_amount * 5.0f; - break; - case LfoKind::LFO_KIND_JUMP: - // Chance - if (drand(0.0f, 1.0f) < lfo_amount) { - ui.activeLooper()->addToPlayhead(rand * random_amount * 8000.0f); - } - break; - default: - break; - } - } - - // Add the LFO to the signal if it is active - switch (lfo_kind) { - case LfoKind::LFO_KIND_TRI: - case LfoKind::LFO_KIND_SQR: - ui.activeLooper()->setPlaybackSpeed(pitch_val + lfo_value * lfo_amount + midi_pitch_offset); - break; - case LfoKind::LFO_KIND_RAND: - ui.activeLooper()->setPlaybackSpeed(pitch_val + rand_pitch_mod + midi_pitch_offset); - break; - default: - ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset); - break; - } - - - float looper_out; - - if (ui.rec_source == REC_SOURCE_PRE) { - ui.activeLooper()->Record(in[1][i]); - } - - if (ui.rec_source == REC_SOURCE_LAST_BUF) { - // FIXME: This might process the previous looper twice later below, add check... - ui.activeLooper()->Record(ui.previousLooper()->Process()); - } - - // Process the input envelope - input_envelope_follower.Process(in[1][i]); - - // Process the Looper - switch (ui.buffer_summing_mode) { - case BUFFER_SUM_MODE_SOLO: - // Only play active looper - looper_out = ui.activeLooper()->Process(); - break; - case BUFFER_SUM_MODE_SUM: - // Sum all loopers - looper_out = looper_a.Process(); - looper_out += looper_b.Process(); - looper_out += looper_c.Process(); - looper_out += looper_d.Process(); - looper_out += looper_e.Process(); - looper_out = looper_out; - break; - case BUFFER_SUM_MODE_RING: - // Sum all loopers and ringmodulate with input - looper_out = looper_a.Process(); - looper_out += looper_b.Process(); - looper_out += looper_c.Process(); - looper_out += looper_d.Process(); - looper_out += looper_e.Process(); - float deadbanded_input; - if (in[1][i] < 0.0f) { - deadbanded_input = min(0.0f, in[1][i] + 0.05f); - } else { - deadbanded_input = max(0.0f, in[1][i] - 0.05f); - } - looper_out *= deadbanded_input*2.0f; - looper_out = looper_out; - break; - } - - // looper_out = pressure * looper_out; - looper_out = saturate(volume * looper_out); - - // Mix the dry/Wet of the looper - output1 = output2 = drywetmix * looper_out + in[1][i] * (1.0f - drywetmix); - - // Compress the signal - compressor.Process(output1); - - // Process reverb - reverb.Process(output1, output1, &out1, &out2); - - // Short decays are silent, so increase level here - float dec_fac = 1.0f + (1.0f - reverb_decay) * 2.0f; - out1 = out1 * dec_fac; - out2 = out2 * dec_fac; - - // Mix reverb with the dry signal depending on the amount dialed - output1 = output1 * (1.0f - reverbmix) + out1 * reverbmix; - output2 = output1 * (1.0f - reverbmix) + out2 * reverbmix; - - // Record the output if needed - if (ui.rec_source == REC_SOURCE_OUT) { - ui.activeLooper()->Record(output1); - } - - out[0][i] = output1; - out[1][i] = output2; - } -} - -// TODO: Add Voice Stealing/Multiple Playheads? -// TODO: Is USB Midi possible? https://github.com/electro-smith/DaisyExamples/blob/master/seed/USB_MIDI/USB_MIDI.cpp - -void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) { - #ifdef DEBUGMODE - Serial.print("[MIDI ON] chn<"); - Serial.print((int) inChannel); - Serial.print("> note<"); - Serial.print((int) inNote); - Serial.print("> velocity<"); - Serial.print((int) inVelocity); - Serial.println(">"); - #endif - // Note Off can come in as Note On w/ 0 Velocity - if (inVelocity == 0.0f) { - midi_pitch_offset = 0.0f; - } - else { - midi_pitch_offset = (int(inNote)-36.0)/12.0f; - } -} - - - -void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) { - #ifdef DEBUGMODE - Serial.print("[MIDI OFF] chn<"); - Serial.print((int) inChannel); - Serial.print("> note<"); - Serial.print((int) inNote); - Serial.print("> velocity<"); - Serial.print((int) inVelocity); - Serial.println(">"); - #endif - midi_pitch_offset = 0.0f; -} - -// void handleAftertouch(byte channel, byte channel_pressure) { -// #ifdef DEBUGMODE -// Serial.print("[MIDI AFTER] chn<"); -// Serial.print((int) channel); -// Serial.print("> pressure<"); -// Serial.print((int) channel_pressure); -// Serial.println(">"); -// #endif -// pressure = float(channel_pressure)/127.0f; -// } - - - -void setup() { - float sample_rate; - // Initialize for Daisy pod at 48kHz - hw = DAISY.init(DAISY_SEED, AUDIO_SR_48K); - DAISY.SetAudioBlockSize(64); - num_channels = hw.num_channels; - sample_rate = DAISY.get_samplerate(); - blocksize = 64.0f; - - // Create a Tick and a noise source for the Sample and Hold - tick.SetFreq(1.0f+lfo_amount*99.0f); - tick.Init(10, sample_rate); - noise.Init(); - - // Initialize Looper with the buffer - looper_a.Init(buffer, buffer_length); - looper_b.Init(buffer_b, buffer_length); - looper_c.Init(buffer_c, buffer_length); - looper_d.Init(buffer_d, buffer_length); - looper_e.Init(buffer_e, buffer_length); - - // Initialize Envelope Follower for the Level LED - input_envelope_follower.Init(sample_rate); - input_envelope_follower.SetAttack(100.0); - input_envelope_follower.SetDecay(1000.0); - - // Initialize Reverb - reverb.Init(sample_rate); - reverb.SetFeedback(reverb_decay); - reverb.SetLpFreq(reverb_tone); - - // Initialize Compressor - compressor.SetThreshold(-64.0f); - compressor.SetRatio(2.0f); - compressor.SetAttack(0.005f); - compressor.SetRelease(0.1250); - - // Initialize the LFO for modulations - lfo.Init(sample_rate); - lfo.SetWaveform(Oscillator::WAVE_TRI); - lfo.SetAmp(1); - lfo.SetFreq(lfo_speed); - - // Easer for the random jumps - easer.setFactor(0.001); - - // Start serial communications - Serial.begin(250000); - - // Initialize Display - display.begin(0x3C, true); - delay(50); - ui.Render(); - - // Initialize the LED - rgb_led.init(); - - // Set the analog read and write resolution to 12 bits - analogReadResolution(12); - - // Setup MIDI handlers - MIDI.setHandleNoteOn(handleNoteOn); - MIDI.setHandleNoteOff(handleNoteOff); - // MIDI.setHandleAfterTouchChannel(handleAftertouch); - - MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages - - // Set Knob names and display functions - pot_1.name = "Start"; - pot_2.name = "Length"; - pot_3.setDisplayMode("Speed", 1000.0f, POT_DISPLAY_MODE_PERCENT); - pot_4.setDisplayMode("Mix", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_5.setDisplayMode("LFO", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_6.setDisplayMode("Volume", 400.0f, POT_DISPLAY_MODE_PERCENT); - pot_7.setDisplayMode("Reverb", 100.0f, POT_DISPLAY_MODE_PERCENT); - - // Set Knob Scaling Modes - // pot_3.setBipolar(); - pot_3.setPitch(); - - // Initialize Buttons (callbacks are assigned in the Ui class) - button_1.init(); - button_2.init(); - button_3.init(); - button_4.init(); - button_5.init(); - button_6.init(); - - // Start the audio Callback - DAISY.begin(AudioCallback); -} - -void loop() { - // Read the values from the potentiometers - float p1 = pot_1.read(); - float p2 = pot_2.read(); - float p3 = pot_3.read(); - float p4 = pot_4.read(); - float p5 = pot_5.read(); - float p6 = pot_6.read(); - float p7 = pot_7.read(); - - // Update the UI - ui.update(); - - // Read the buttons - button_1.read(); - button_2.read(); - button_3.read(); - button_4.read(); - button_5.read(); - button_6.read(); - - // Set loop-start and loop-length with the potentiometers - ui.setLoop(p1, p2); - - // Tune the pitch to the ten-fold of the potentiometer 3, - // a bipolar pot, so it returns values from -1.0 to +1.0 - // Pitch should go 10 octaves up/down (10*1.0 = 1000%) - if (!isnan(p3)) {pitch_val = 10.0f * p3; } - - // Set other parameters (from 0.0 to 1.0) - if (!isnan(p4)) { drywetmix = p4; } - - switch (ui.fx_mode) { - case FX_MODE_ALL: - if (!isnan(p5)) { lfo_amount = p5; } - if (!isnan(p6)) { volume = p6 * 4.0f; } - if (!isnan(p7)) { reverbmix = p7; } - break; - case FX_MODE_REVERB: - if (!isnan(p5)) { reverb_tone = 50.0f + p5 * 20000.0f; } - // TODO: Short Reverb Decay times are too silent? - if (!isnan(p6)) { reverb_decay = 0.05f + p6 * 0.94f; } - if (!isnan(p7)) { reverbmix = p7; } - break; - case FX_MODE_LFO: - if (!isnan(p5)) { lfo_kind = p5; } - if (!isnan(p6)) { lfo_speed = (p6 * p6 *p6) * 100.0f; } - if (!isnan(p7)) { lfo_amount = p7; } - break; - case FX_MODE_GRAIN: - if (!isnan(p5)) { ui.activeLooper()->grain_count = 1+int(p5); } - if (!isnan(p6)) { ui.activeLooper()->grain_spread = p6*10.0f; } - if (!isnan(p7)) { ui.activeLooper()->grain_variation = p7; } - break; - } - - // Render the UI (frame rate limited by UI_MAX_FPS in ui.h) - // double start = millis(); - ui.Render(); - // Serial.print("ui Render took "); - // Serial.print(millis()-start); - // Serial.println("ms"); - - // Set the Color and brightness of the RGB LED in 8 bits - rgb_led.setAudioLevelIndicator(input_envelope_follower.getValue()); - - // MIDI - MIDI.read(); -} - - - - diff --git a/code/daisy-looper/env_follower.h b/code/daisy-looper/env_follower.h deleted file mode 100644 index b3a123969be9a41e91c5ed3ab17b8b5f3265ad5d..0000000000000000000000000000000000000000 --- a/code/daisy-looper/env_follower.h +++ /dev/null @@ -1,91 +0,0 @@ -#include "wiring_constants.h" -#ifndef Env_follower_h -#define Env_follower_h -#include "Arduino.h" - -namespace atoav { - -class SmoothingFilter { - public: - void Init(float smoothing_time_ms, float sample_rate) { - a = exp(-TWO_PI / (smoothing_time_ms * 0.001f * sample_rate)); - b = 1.0f - a; - z = 0.0f; - } - - inline float Process(float in) { - z = (in * b) + (z * a); - return z; - } - - void setSmoothing(float smoothing_time_ms, float sample_rate) { - a = exp(-TWO_PI / (smoothing_time_ms * 0.001f * sample_rate)); - b = 1.0f - a; - } - - private: - float a; - float b; - float z; -}; - - -class EnvelopeFollower { - public: - void Init(float sample_rate) { - sample_rate = sample_rate; - attack = 200.0f; - decay = 4000.0f; - smoothing = 50.0f; - value = 0.0f; - smoothing_filter.Init(smoothing, sample_rate); - } - - void SetAttack(float attack_ms) { - attack = pow(0.01, 1.0 / (attack_ms * sample_rate * 0.001)); - } - - void SetDecay(float decay_ms) { - decay = pow(0.01, 1.0 / (decay_ms * sample_rate * 0.001)); - } - - void SetSmoothing(float smoothing_ms) { - smoothing_filter.setSmoothing(smoothing_ms, sample_rate); - } - - float Process(float in) { - abs_value = smoothing_filter.Process(abs(in)); - if (abs_value > value) { - value = attack * (value - abs_value) + abs_value; - } else { - value = decay * (value - abs_value) + abs_value; - } - return value; - } - - float value; - - float getValue() { - if (value == 0.0f || value == -0.0f) { - return value; - } - return max(0.0f, (3.0f + log10(value)) / 3.0f); - // return value; - } - - - private: - float sample_rate; - float attack; - float decay; - float smoothing; - float abs_value = 0.0f; - SmoothingFilter smoothing_filter; -}; -}; - - - - - -#endif \ No newline at end of file diff --git a/code/daisy-looper/helpers.h b/code/daisy-looper/helpers.h deleted file mode 100644 index 2f6601ac9522345ecb78328232270987587998ba..0000000000000000000000000000000000000000 --- a/code/daisy-looper/helpers.h +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef Helpers_h -#define Helpers_h - -#include "Adafruit_SH110X.h" -#include "Adafruit_GFX.h" -extern Adafruit_SH1106G display; - - - -int centeredText(const char *buf, int x, int y, int color, int lineheight=8) { - int16_t x1, y1; - uint16_t w, h; - char *line_pointer = strchr(buf, '\n'); - display.setTextColor(color); - if (!line_pointer) { - display.getTextBounds(buf, 0, 0, &x1, &y1, &w, &h); //calc width of new string - display.setCursor(x - (w / 2), y - (h / 2)); - display.print(buf); - }else { - char *tmp = strdup(buf); - char* d = strtok(tmp, "\n"); - int line = 0; - while (d != NULL) { - display.getTextBounds(d, 0, 0, &x1, &y1, &w, &h); //calc width of new string - display.setCursor(x - (w / 2), y - (h / 2)-lineheight/2 + (line*lineheight)); - display.print(d); - d = strtok(NULL, ","); - line++; - } - free(tmp); - } - return w; -} - -int centeredTextMark(const char *buf, int x, int y, int color, int underline_line=0, int lineheight=8) { - int16_t x1, y1; - uint16_t w, h; - char *line_pointer = strchr(buf, '\n'); - display.setTextColor(color); - if (!line_pointer) { - display.getTextBounds(buf, 0, 0, &x1, &y1, &w, &h); //calc width of new string - int x_start = x - (w / 2); - int y_start = y - (h / 2); - display.setCursor(x_start, y_start); - if (underline_line == 1) { - display.drawFastHLine(x_start-2, y_start+lineheight, w+4, color); - } - display.print(buf); - }else { - char *tmp = strdup(buf); - char* d = strtok(tmp, "\n"); - int line = 0; - while (d != NULL) { - display.getTextBounds(d, 0, 0, &x1, &y1, &w, &h); //calc width of new string - int x_start = x - (w / 2); - int y_start = y - (h / 2)-lineheight/2 + (line*lineheight); - display.setCursor(x_start, y_start); - display.print(d); - d = strtok(NULL, ","); - if (underline_line == 1) { - display.drawFastHLine(x_start-2, y_start+lineheight, w+4, color); - } - line++; - } - free(tmp); - } - return w; -} - - -int centeredTextMarkMulti(const char *buf, int x, int y, int color, int underline_line=0, int lineheight=8) { - int16_t x1, y1; - uint16_t w, h; - char *line_pointer = strchr(buf, '\n'); - display.setTextColor(color); - if (!line_pointer) { - display.getTextBounds(buf, 0, 0, &x1, &y1, &w, &h); //calc width of new string - int x_start = x - (w / 2); - int y_start = y - (h / 2); - display.setCursor(x_start, y_start); - if (underline_line == 1) { - display.drawFastHLine(x_start-2, y_start, w+4, color); - } - display.print(buf); - }else { - char *tmp = strdup(buf); - char* d = strtok(tmp, "\n"); - int line = 0; - while (d != NULL) { - display.getTextBounds(d, 0, 0, &x1, &y1, &w, &h); //calc width of new string - int x_start = x - (w / 2); - int y_start = y - (h / 2)-lineheight/2 + (line*lineheight); - display.setCursor(x_start, y_start); - display.print(d); - d = strtok(NULL, ","); - if (line == underline_line) { - display.drawFastHLine(x_start-2, y_start+lineheight-3, w+4, color); - } - line++; - } - free(tmp); - } - return w; -} - -int button_multi(const char *buf, int x, int y, int color, int underline_line=0, int lines=0) { - int16_t x1, y1; - uint16_t w, h; - display.setTextColor(color); - char *tmp = strdup(buf); - int line = 0; - char* pch = NULL; - pch = strtok(tmp, "\n"); - - int radius = 2; - int cell_width = 128/3; - int left_x = x - (cell_width/2); - int margin = 10; - int circle_start_x = left_x + margin; - int spacing = (cell_width - margin - margin) / lines; - - while (pch != NULL){ - // Draw Option-Circles - display.drawCircle(circle_start_x + line*spacing, y+7, radius, SH110X_WHITE); - // Only display the active text - if (line == underline_line) { - display.getTextBounds(pch, 0, 0, &x1, &y1, &w, &h); //calc width of new string - int x_start = x - (w / 2); - int y_start = y - (h / 2)-7; - display.setCursor(x_start, y_start); - display.print(pch); - // On the active option, draw a filled circle - display.fillCircle(circle_start_x + line*spacing, y+7, radius, SH110X_WHITE); - } - line++; - pch = strtok(NULL, "\n"); - } - free(tmp); - return w; -} - -float saturate(float x) { - if (x < -3.0f) { - return -1.0f; - } else if (x > 3.0f) { - return 1.0f; - } else { - return x * (27.0f + x * x ) / (27.0f + 9.0f * x * x); - } -} - - -double drand(double minf, double maxf){ - return minf + random(1UL << 31) * (maxf - minf) / (1UL << 31); // use 1ULL<<63 for max double values) -} - - -#endif \ No newline at end of file diff --git a/code/daisy-looper/leds.h b/code/daisy-looper/leds.h deleted file mode 100644 index 5c4a8168eed032f26197954220287731a493f39f..0000000000000000000000000000000000000000 --- a/code/daisy-looper/leds.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef Leds_h -#define Leds_h -#include "Arduino.h" -#include "luts.h" - - -// Lookup Curves LED Red b -float red_lut_x[] = {170.0, 173.5, 177.0, 180.5, 184.0, 187.5, 191.0, 194.5, 198.0, 201.5, 205.0, 208.5, 212.0, 215.5, 219.0, 222.5, 240.0, 240.75, 241.5, 242.25, 243.0, 243.75, 244.5, 245.25, 246.0, 246.75, 247.5, 248.25, 249.0, 249.75, 250.5, 251.25, 255}; -float red_lut_y[] = {0.0, 0.0037500000000000007, 0.030000000000000006, 0.10125000000000003, 0.24000000000000005, 0.46875, 0.8100000000000003, 1.2862500000000003, 1.9200000000000004, 2.7337500000000006, 3.75, 4.991250000000002, 6.480000000000002, 8.238750000000001, 10.290000000000003, 12.65625, 30.0, 31.1503125, 32.4525, 34.0584375, 36.12, 38.7890625, 42.2175, 46.5571875, 51.96, 58.5778125, 66.5625, 76.06593750000002, 87.24000000000001, 100.23656250000002, 115.20750000000002, 132.3046875, 255}; -size_t red_lut_len = 33; - -// Lookup Curves LED Green b -float green_lut_x[] = {10.0, 18.5, 27.0, 35.5, 44.0, 52.5, 61.00000000000001, 69.5, 78.0, 86.5, 95.0, 103.50000000000001, 112.00000000000001, 120.5, 129.0, 137.5, 180.0, 183.5, 187.0, 190.5, 194.0, 197.5, 201.0, 204.5, 208.0, 211.5, 215.0, 218.5, 222.0, 225.5, 229.0, 232.5, 250}; -float green_lut_y[] = {0.0, 0.0075000000000000015, 0.06000000000000001, 0.20250000000000007, 0.4800000000000001, 0.9375, 1.6200000000000006, 2.5725000000000007, 3.8400000000000007, 5.467500000000001, 7.5, 9.982500000000003, 12.960000000000004, 16.477500000000003, 20.580000000000005, 25.3125, 60.0, 56.43, 52.92, 49.47, 46.08, 42.75, 39.48, 36.269999999999996, 33.120000000000005, 30.029999999999998, 27.000000000000007, 24.03, 21.119999999999997, 18.269999999999996, 15.480000000000004, 12.750000000000007, 0}; -size_t green_lut_len = 33; - -// Lookup Curves LED Blue b -float blue_lut_x[] = {0.0, 2.0, 4.0, 6.000000000000001, 8.0, 10.0, 12.000000000000002, 14.000000000000002, 16.0, 18.0, 20.0, 22.0, 24.000000000000004, 26.0, 28.000000000000004, 30.0, 40.0, 46.5, 53.0, 59.5, 66.0, 72.5, 79.0, 85.5, 92.0, 98.5, 105.0, 111.5, 118.00000000000001, 124.5, 131.0, 137.5, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0, 170.0}; -float blue_lut_y[] = {0.0, 0.10099999999999998, 0.20799999999999996, 0.327, 0.46399999999999997, 0.6249999999999999, 0.8160000000000001, 1.0430000000000001, 1.312, 1.629, 1.9999999999999998, 2.4310000000000005, 2.928000000000001, 3.497, 4.144000000000001, 4.875, 10.0, 9.025, 8.1, 7.225, 6.4, 5.625, 4.9, 4.225, 3.5999999999999996, 3.0250000000000004, 2.5, 2.0249999999999986, 1.5999999999999996, 1.2250000000000014, 0.9000000000000004, 0.625, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; -size_t blue_lut_len = 48; - - - - - -class RGBLed { - int pin_red; - int pin_green; - int pin_blue; - float level = 0.0f; - Easer easer; - - public: - RGBLed(int pin_red, int pin_green, int pin_blue); - void init(); - void off(); - void setColor(int r, int g, int b); - void setAudioLevelIndicator(float envelope_value); -}; - -RGBLed::RGBLed(int pin_red, int pin_green, int pin_blue) { - this->pin_red = pin_red; - this->pin_green = pin_green; - this->pin_blue = pin_blue; - this->easer.setFactor(0.7); -} - -void RGBLed::init() { - pinMode(this->pin_red, OUTPUT); - pinMode(this->pin_green, OUTPUT); - pinMode(this->pin_blue, OUTPUT); -} - -void RGBLed::off() { - digitalWrite(this->pin_red, LOW); - digitalWrite(this->pin_green, LOW); - digitalWrite(this->pin_blue, LOW); -} - -void RGBLed::setColor(int r, int g, int b) { - r = min(255, max(0, r)); - g = min(255, max(0, g)); - b = min(255, max(0, b)); - analogWrite(this->pin_red, r); - analogWrite(this->pin_green, g); - analogWrite(this->pin_blue, b); -} - -void RGBLed::setAudioLevelIndicator(float envelope_value) { - level = easer.Process(envelope_value); - int brightness = int(min(255.0f, max(0.0f, level * 255))); - int red = get_from_xy_table(red_lut_x, red_lut_y, brightness, red_lut_len); - int green = get_from_xy_table(green_lut_x, green_lut_y, brightness, green_lut_len); - int blue = get_from_xy_table(blue_lut_x, blue_lut_y, brightness, blue_lut_len); - this->setColor(red, green, blue); -} - -#endif \ No newline at end of file diff --git a/code/daisy-looper/lfo.h b/code/daisy-looper/lfo.h deleted file mode 100644 index a91cdb6d496e4ed4b3da4cecc5eeaeb31ca85869..0000000000000000000000000000000000000000 --- a/code/daisy-looper/lfo.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -enum LfoKind { - LFO_KIND_TRI, - LFO_KIND_SQR, - LFO_KIND_RAND, - LFO_KIND_JUMP, - LFO_KIND_NONE, -}; \ No newline at end of file diff --git a/code/daisy-looper/looper.h b/code/daisy-looper/looper.h deleted file mode 100644 index 594ae1a14efdbb6f58c40446bedd2e89cee50300..0000000000000000000000000000000000000000 --- a/code/daisy-looper/looper.h +++ /dev/null @@ -1,468 +0,0 @@ -#pragma once - -#include "luts.h" - -namespace atoav { - -enum RecPitchMode { - REC_PITCH_MODE_NORMAL, - REC_PITCH_MODE_UNPITCHED, - REC_PITCH_MODE_LAST, -}; - -enum RecStartMode { - REC_START_MODE_BUFFER, - REC_START_MODE_LOOP, - REC_START_MODE_PLAYHEAD, - REC_START_MODE_LAST, -}; - -enum PlaybackState { - PLAYBACK_STATE_STOPPED, - PLAYBACK_STATE_LOOP, - PLAYBACK_STATE_MULTILOOP, - PLAYBACK_STATE_MIDI, - PLAYBACK_STATE_LAST, -}; - -enum RecordingState { - REC_STATE_EMPTY, - REC_STATE_RECORDING, - REC_STATE_OVERDUBBING, - REC_STATE_ERASING, - REC_STATE_NONE, - REC_STATE_LAST, -}; - -// ================================================= -// = H E A D = -// ================================================= - -class Head { - public: - Head(); - void activate(); - void deactivate(); - bool isActive(); - void setPosition(float value); - void reset(); - void setIncrement(float value); - void incrementBy(float value); - void slowDown(); - void reverse(); - void speedUp(); - void update(); - float read(); - float increment = 1.0f; - float variation = 0.0f; - float variation_amount = 0.0f; - float speed_multiplier = 1.0f; - private: - bool active = true; - float position = 0.0f; -}; - -Head::Head() { - variation = random(-0.1f, 0.1f); -} -void Head::activate() { - this->active = true; -} -void Head::deactivate() { - this->active = false; -} -bool Head::isActive() { - return active; -} -void Head::reset() { - this->position = 0.0f; -} -void Head::setPosition(float value) { - this->position = value * speed_multiplier; -} -void Head::setIncrement(float value) { - this->increment = value; -} -void Head::incrementBy(float value) { - this->position += value; -} -void Head::slowDown() { - this->speed_multiplier = max(0.0f, this->speed_multiplier - 0.0025f); -} -void Head::reverse() { - this->speed_multiplier = -abs(this->speed_multiplier ); -} -void Head::speedUp() { - this->speed_multiplier = 1.0f; -} -void Head::update() { - this->position += (this->increment + (variation * variation_amount)) * speed_multiplier; -} -float Head::read() { - return this->position; -} - - -// ================================================= -// = L O O P E R = -// ================================================= - -class Looper { - public: - Looper(); - void Init(float *buf, size_t buf_size); - void SetRecord(); - void SetOverdub(); - void SetErase(); - void SetStopWriting(); - bool isWriting(); - void ResetRecHead(); - void SetLoop(float loop_start_time, float loop_length_time); - void Record(float in); - float Process(); - float* getBuffer(); - size_t getBufferLength(); - void setRecPitchMode(RecPitchMode mode); - void setRecStartMode(RecStartMode mode); - float GetPlayhead(); - float* GetPlayheads(); - uint8_t GetPlayheadCount(); - float GetRecHead(); - bool toggleRecMode(); - void setRecModeFull(); - void setRecModeLoop(); - void setRecModeFullShot(); - void setRecModeLoopShot(); - void setPlaybackSpeed(float increment); - void addToPlayhead(float value); - void slowDown(); - void reverse(); - void restart(); - void speedUp(); - float loop_start_f = 0.0f; - float loop_length_f = 1.0f; - uint8_t grain_count = 8; - float grain_spread = 2.0f; - float grain_variation = 0.0f; - RecPitchMode rec_pitch_mode = REC_PITCH_MODE_NORMAL; - RecStartMode rec_start_mode = REC_START_MODE_BUFFER; - PlaybackState playback_state = PLAYBACK_STATE_LOOP; - RecordingState recording_state = REC_STATE_EMPTY; - - private: - static const size_t kFadeLength = 200; - static const size_t kMinLoopLength = 2 * kFadeLength; - - float* buffer; - size_t buffer_length = 0; - - Head playheads[9]; - Head rec_head; - - size_t loop_start = 0; - size_t loop_length = 48000; - - bool stop_after_recording = false; - bool stay_within_loop = true; - -}; - - -Looper::Looper() {} - -void Looper::Init(float *buf, size_t buf_size) { - buffer = buf; - buffer_length = buf_size; - memset(buffer, 0, sizeof(float) * buffer_length); -} - -void Looper::SetRecord() { - recording_state = REC_STATE_RECORDING; - ResetRecHead(); - rec_head.activate(); -} - -void Looper::SetOverdub() { - recording_state = REC_STATE_OVERDUBBING; - ResetRecHead(); - rec_head.activate(); -} - -void Looper::SetErase() { - recording_state = REC_STATE_ERASING; - ResetRecHead(); - rec_head.activate(); -} - -void Looper::SetStopWriting() { - recording_state = REC_STATE_NONE; - rec_head.deactivate(); -} - -bool Looper::isWriting() { - return recording_state == REC_STATE_RECORDING - || recording_state == REC_STATE_OVERDUBBING - || recording_state == REC_STATE_ERASING; -} - -void Looper::ResetRecHead() { - if (isWriting()) { - switch (rec_start_mode) { - case REC_START_MODE_LOOP: - rec_head.setPosition(loop_start % buffer_length); - break; - case REC_START_MODE_BUFFER: - rec_head.setPosition(0.0f); - break; - case REC_START_MODE_PLAYHEAD: - rec_head.setPosition(fmod(loop_start + playheads[0].read(), float(buffer_length))); - break; - } - } -} - -void Looper::SetLoop(float loop_start_time, float loop_length_time) { - if (!isnan(loop_start_time)) { - loop_start_f = loop_start_time; - loop_start = static_cast<size_t>(loop_start_time * (buffer_length - 1)); - } - if (!isnan(loop_length_time)) { - loop_length_f = loop_length_time; - loop_length = max(kMinLoopLength, static_cast<size_t>(loop_length_time * buffer_length)); - } -} - -void Looper::Record(float in) { - // Overwrite/Add/Erase the buffer depending on the mode - switch (recording_state) { - case REC_STATE_RECORDING: - buffer[int(rec_head.read())] = in; - break; - case REC_STATE_OVERDUBBING: - buffer[int(rec_head.read())] += in; - break; - case REC_STATE_ERASING: - buffer[int(rec_head.read())] = 0.0f; - break; - } - - // Advance the recording head if needed - if (isWriting()) { - // Set Recording head increment depending on the mode - switch (rec_pitch_mode) { - case REC_PITCH_MODE_NORMAL: - rec_head.setIncrement(1.0f); - break; - case REC_PITCH_MODE_UNPITCHED: - rec_head.setIncrement(playheads[0].increment); - break; - } - // Increment recording head - rec_head.update(); - - // Limit the position of the rec head depending on the active mode - if (!stop_after_recording) { - if (!stay_within_loop) { - // record into whole buffer - rec_head.setPosition(fmod(rec_head.read(), float(buffer_length))); - } else { - // Limit rec head to stay inside the loop - rec_head.setPosition(fmod(rec_head.read(), float(loop_start + loop_length))); - rec_head.setPosition(max(float(loop_start), rec_head.read())); - } - } else { - // Stop at end (either end of buffer or end of loop) - if (!stay_within_loop) { - if (rec_head.read() > buffer_length) { SetStopWriting(); } - } else { - if (rec_head.read() > loop_start + loop_length) { SetStopWriting(); } - } - } - - // Ensure the Rec-Head is never without bounds, even when running backwards - if (rec_head.read() > buffer_length) { - rec_head.setPosition(0.0f); - } else if (rec_head.read() < 0) { - rec_head.setPosition(buffer_length); - } - } -} - -float Looper::Process() { - // Early return if buffer is empty or not playing to save performance - if (recording_state == REC_STATE_EMPTY - || playback_state == PLAYBACK_STATE_STOPPED) { - return 0; - } - - // Deactivate all playheads except first if playback state is Loop - switch (playback_state) { - case PLAYBACK_STATE_LOOP: - playheads[0].activate(); - for (size_t i=1; i<9; i++) { - playheads[i].deactivate(); - } - break; - case PLAYBACK_STATE_MULTILOOP: - for (size_t i=0; i<9; i++) { - playheads[i].activate(); - } - break; - case PLAYBACK_STATE_STOPPED: - for (size_t i=0; i<9; i++) { - playheads[i].deactivate(); - } - break; - } - - double mix = 0.0; - - for (size_t i=0; i<grain_count-1; i++) { - // Skip inactive playheads - if (!playheads[i].isActive()) continue; - - // Ensure we are actually inside the buffer - int from_start = int(playheads[i].read()) % loop_length; - int play_pos = loop_start + from_start; - - float vol = 1.0f; - if (from_start <= kFadeLength) { - vol = from_start / float(kFadeLength); - } - int from_end = abs(int(loop_length) - from_start); - if (from_end <= kFadeLength) { - vol = from_end / float(kFadeLength); - } - - // Read from the buffer - mix += buffer[play_pos] * vol; - - // Advance the playhead - playheads[i].update(); - - // Ensure the playhead stays within bounds of the loop - float pos = playheads[i].read(); - if (pos >= loop_length || pos <= 0.0f) { - playheads[i].setPosition(fmod(pos, float(loop_length))); - } - } - return mix; -} - -float Looper::GetPlayhead() { - return float(int(playheads[0].read()) % loop_length) / float(buffer_length); -} - -float* Looper::GetPlayheads() { - static float playhead_positions[9]; - for (size_t i=0; i<9; i++) { - playhead_positions[i] = float(int(playheads[i].read()) % loop_length) / float(buffer_length); - } - return playhead_positions; -} - -uint8_t Looper::GetPlayheadCount() { - return grain_count; -} - -float Looper::GetRecHead() { - return float(rec_head.read()) / float(buffer_length); -} - -bool Looper::toggleRecMode() { - stay_within_loop = !stay_within_loop; - return stay_within_loop; -} - -void Looper::setRecModeFull() { - stay_within_loop = false; - stop_after_recording = false; -} - -void Looper::setRecModeLoop() { - stay_within_loop = true; - stop_after_recording = false; -} - -void Looper::setRecModeFullShot() { - stay_within_loop = false; - stop_after_recording = true; -} - -void Looper::setRecModeLoopShot() { - stay_within_loop = true; - stop_after_recording = true; -} - -void Looper::setPlaybackSpeed(float increment) { - switch (playback_state) { - case PLAYBACK_STATE_LOOP: - playheads[0].setIncrement(increment); - break; - case PLAYBACK_STATE_MULTILOOP: - playheads[0].setIncrement(increment); - for (size_t i=1; i<9; i++) { - playheads[i].variation_amount = grain_variation; - playheads[i].setIncrement(increment + increment*grain_spread); - } - break; - } -} - -void Looper::addToPlayhead(float value) { - switch (playback_state) { - case PLAYBACK_STATE_LOOP: - playheads[0].incrementBy(value); - break; - case PLAYBACK_STATE_MULTILOOP: - playheads[0].incrementBy(value); - for (size_t i=1; i<9; i++) { - playheads[i].incrementBy(value + value/(1+i)); - } - break; - } -} - -void Looper::slowDown() { - for (size_t i=0; i<9; i++) { - playheads[i].slowDown(); - } -} - -void Looper::reverse() { - for (size_t i=0; i<9; i++) { - playheads[i].reverse(); - } -} - -void Looper::restart() { - for (size_t i=0; i<9; i++) { - playheads[i].reset(); - } -} - -void Looper::speedUp() { - for (size_t i=0; i<9; i++) { - playheads[i].speedUp(); - } -} - -float* Looper::getBuffer() { - return buffer; -} - -size_t Looper::getBufferLength() { - return buffer_length; -} - -void Looper::setRecPitchMode(RecPitchMode mode) { - rec_pitch_mode = mode; -} - -void Looper::setRecStartMode(RecStartMode mode) { - rec_start_mode = mode; -} - - - -}; // namespace atoav \ No newline at end of file diff --git a/code/daisy-looper/luts.h b/code/daisy-looper/luts.h deleted file mode 100644 index b332ed6da58ed786d58f0a815e43280de8186beb..0000000000000000000000000000000000000000 --- a/code/daisy-looper/luts.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef LUTs_h -#define LUTs_h - -#include <MultiMap.h> - -// Lookup Table for Pitch Knob -float pitch_knob_lookup_x[] = {0.0, 0.0025, 0.0175, 0.0225, 0.0375, 0.0425, 0.057499999999999996, 0.0625, 0.0775, 0.0825, 0.0975, 0.10250000000000001, 0.1175, 0.1225, 0.1375, 0.14250000000000002, 0.1575, 0.1625, 0.1975, 0.2025, 0.2475, 0.2525, 0.2975, 0.3025, 0.3775, 0.3825, 0.4175, 0.4225, 0.4575, 0.4625, 0.4975, 0.5025, 0.5375000000000001, 0.5425, 0.5775000000000001, 0.5825, 0.6175, 0.6224999999999999, 0.6975, 0.7024999999999999, 0.7475, 0.7525, 0.7975000000000001, 0.8025, 0.8375, 0.8424999999999999, 0.8575, 0.8624999999999999, 0.8775000000000001, 0.8825, 0.8975000000000001, 0.9025, 0.9175000000000001, 0.9225, 0.9375, 0.9424999999999999, 0.9575, 0.9624999999999999, 0.9775, 0.9824999999999999, 0.9975, 1.0}; -float pitch_knob_lookup_y[] = {-1.0, -1.0, -0.9, -0.9, -0.8, -0.8, -0.7, -0.7, -0.6, -0.6, -0.5, -0.5, -0.4, -0.4, -0.3, -0.3, -0.2, -0.2, -0.1, -0.1, -0.05, -0.05, -0.025, -0.025, -0.0125, -0.0125, -0.00625, -0.00625, -0.003125, -0.003125, 0.0, 0.0, 0.003125, 0.003125, 0.00625, 0.00625, 0.0125, 0.0125, 0.025, 0.025, 0.05, 0.05, 0.1, 0.1, 0.2, 0.2, 0.3, 0.3, 0.4, 0.4, 0.5, 0.5, 0.6, 0.6, 0.7, 0.7, 0.8, 0.8, 0.9, 0.9, 1.0, 1.0}; -size_t pitch_knob_lookup_length = 62; - - -class Easer { - float output = 0.0f; - float delta = 0.0f; - float easing = 0.1f; - public: - Easer(); - - float Process(float input) { - delta = input - output; - output += delta * easing; - return output; - } - - void setFactor(float factor) { - easing = min(max(0.00001f, factor), 1.0f); - } -}; - -Easer::Easer() { - -}; - -float lerp(float a, float b, float f) { - f = min(1.0f, max(0.0f, f)); - - if (f == 0.0) { return a; } - else if (f == 1.0f) { return b; } - else { return a * (1.0f-f) + b * f; } -} - -float get_from_xy_table(float* xtable, float* ytable, float f, size_t length) { - return multiMap<float>(f, xtable, ytable, length); -} - - -#endif \ No newline at end of file diff --git a/code/daisy-looper/menu.ods b/code/daisy-looper/menu.ods deleted file mode 100644 index 2b8391b9cf03447b0e7b7cf66da2c734faf8ed40..0000000000000000000000000000000000000000 Binary files a/code/daisy-looper/menu.ods and /dev/null differ diff --git a/code/daisy-looper/potentiometers.h b/code/daisy-looper/potentiometers.h deleted file mode 100644 index 055264c4d6e2de012b6d3dbbb39f072120a5487a..0000000000000000000000000000000000000000 --- a/code/daisy-looper/potentiometers.h +++ /dev/null @@ -1,257 +0,0 @@ -#include "WCharacter.h" -#include "wiring_analog.h" -#include <stdint.h> -#include <limits> -#ifndef Potentiometers_h -#define Potentiometers_h -#include "Arduino.h" -#include "luts.h" -#include "helpers.h" - -#include "Adafruit_SH110X.h" -#include "Adafruit_GFX.h" -extern Adafruit_SH1106G display; - -// The length of the moving average filter that smooths the -// controls. Higher number is smoother, but less responsive -// and needs more memory. -#define POT_MOVING_AVERAGE_SIZE 2 - -// Length of the Textbuffer for floats in the UI -#define UI_TEXTBUFFER_LENGTH 6 - -// Modes -enum PotMode { - POT_MODE_LIN, - POT_MODE_BIP, - POT_MODE_PITCH, - POT_MODE_SWITCH, - POT_MODE_LAST -}; - -// Display Modes -enum PotDisplayMode { - POT_DISPLAY_MODE_DEFAULT, - POT_DISPLAY_MODE_PITCH, - POT_DISPLAY_MODE_PERCENT, - POT_DISPLAY_MODE_SWITCH, - POT_DISPLAY_MODE_SWITCH_NUMBERS, - POT_DISPLAY_MODE_LAST -}; - -typedef void (*callback_function)(void); - -class Potentiometer { - int pin; - int readings[POT_MOVING_AVERAGE_SIZE]; - PotMode mode = POT_MODE_LIN; - PotDisplayMode display_mode = POT_DISPLAY_MODE_DEFAULT; - float last_reading, last_normalized_reading = 0.0f; - float display_scale = 1.0f; - callback_function onChangeFunction; - Easer easer; - - public: - Potentiometer(int pin); - void init(); - void setLinear(); - void setPitch(); - void setSwitch(); - float read(); - void setOnChange(callback_function f); - void renderUi(); - void setDisplayMode(const char *name, float display_scale, PotDisplayMode display_mode); - const char *name; - double last_displayed = 0.0; - bool should_display = false; - bool display_value_changes = false; - bool last_was_nan = false; - uint8_t switch_positions; - uint8_t switch_offset = 0; - const char* const switch_labels[4] = {"TRI", "SQR", "RAND", "JUMP"}; -}; - -Potentiometer::Potentiometer(int pin) { - this->pin = pin; -} - -void Potentiometer::init() { - analogReadResolution(12); - easer.setFactor(0.001); -} - -void Potentiometer::setLinear() { - this->mode = POT_MODE_LIN; -} - -void Potentiometer::setPitch() { - this->mode = POT_MODE_PITCH; -} - -void Potentiometer::setSwitch() { - this->mode = POT_MODE_SWITCH; -} - -float Potentiometer::read() { - int reading = analogRead(this->pin); - // Shift all readings in the buffer over by one position, deleting the oldest - // and adding the newest - for (int i=0; i<POT_MOVING_AVERAGE_SIZE; i++) { - int next = i+1; - if (next < POT_MOVING_AVERAGE_SIZE) { - (this->readings)[i] = (this->readings)[next]; - } - } - (this->readings)[POT_MOVING_AVERAGE_SIZE-1] = reading; - - // Get the average of the last readings - reading = 0; - for (int i=0; i<POT_MOVING_AVERAGE_SIZE; i++) { - reading += (this->readings)[i]; - } - reading = reading / POT_MOVING_AVERAGE_SIZE; - - // Convert the last reading to a float and return - float current_reading = easer.Process(reading / 4096.0f); - float normalized_reading = current_reading; - - // Depending on the Mode - switch (this->mode) { - case POT_MODE_PITCH: - current_reading = get_from_xy_table(pitch_knob_lookup_x, pitch_knob_lookup_y, current_reading, pitch_knob_lookup_length); - break; - case POT_MODE_SWITCH: - current_reading = int(current_reading * switch_positions); - break; - } - - bool changed = abs(normalized_reading - this->last_normalized_reading) > 0.002; - - // If the difference to the last reading is big enough assume the knob has been touched - if (this->last_normalized_reading && changed) { - if (display_value_changes) { - last_displayed = millis(); - should_display = true; - } - if (this->onChangeFunction) { this->onChangeFunction(); } - } - - if (this->last_normalized_reading && !changed) { - // if (!last_was_nan) { - // Serial.print(this->name); - // Serial.println(" returned NaN"); - // } - last_was_nan = true; - return std::numeric_limits<float>::quiet_NaN(); - } - last_was_nan = false; - this->last_reading = current_reading; - this->last_normalized_reading = normalized_reading; - return current_reading; -} - -void Potentiometer::setOnChange(callback_function f) { - this->onChangeFunction = f; -} - -void Potentiometer::setDisplayMode(const char *name, float display_scale, PotDisplayMode display_mode) { - this->display_value_changes = true; - this->name = name; - this->display_scale = display_scale; - this->display_mode = display_mode; -} - -void Potentiometer::renderUi() { - double now = millis(); - if (this->should_display) { - int x_margin = 28; - int y_margin = 13; - int x_center = display.width()/2; - int y_center = display.height()/2; - // Render a rectangle Backdrop for the text - display.fillRect(3+x_margin, 3+y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE); - display.fillRect(x_margin, y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE); - display.fillRect(x_margin+1, y_margin+1, display.width()-(x_margin+1)*2, display.height()-(y_margin+1)*2, SH110X_BLACK); - - - // Render the name of the parameter (e.g. "Pitch") - centeredText(this->name, x_center, y_center-4, SH110X_WHITE); - - // Choose how many digits to display depending on the mode - int digits = 2; - if (this->display_mode == POT_DISPLAY_MODE_PERCENT) { digits = 0; } - // Allocate a buffer for the float and convert it into characters - char value_buffer[UI_TEXTBUFFER_LENGTH]; // Buffer big enough for 7-character float - dtostrf(this->last_reading*display_scale, 6, digits, value_buffer); // Leave room for too large numbers! - - // If we are on a bipolar pot display an indicator if we are in the center - if (this->mode == POT_MODE_BIP && this->last_reading > -0.0001 && this->last_reading < 0.0001) { - display.fillTriangle(x_center, y_center+10, x_center+3, y_center+15, x_center-3, y_center+15, SH110X_WHITE); - } - - // If we are on a pitch pot display an indicator if we are in the the right steps - if (this->mode == POT_MODE_PITCH) { - float reading_mod = fmod(abs(this->last_reading), 0.05f); - if (reading_mod > 0.999f || reading_mod < 0.001f) { - display.fillTriangle(x_center, y_center+10, x_center+3, y_center+15, x_center-3, y_center+15, SH110X_WHITE); - } - } - - // The float value may contain some empty whitespace characters, remove them by - // first figuring out which the first actual character is - int nonwhite = 0; - for (int i=0; i<UI_TEXTBUFFER_LENGTH; i++) { - if (value_buffer[i] == ' ') { - nonwhite++; - } - } - - // Create a new buffer that can hold everything - char text_buffer[UI_TEXTBUFFER_LENGTH+6]; - - // Copy all non-white characters over - for (int i = 0; i<UI_TEXTBUFFER_LENGTH; i++) { - text_buffer[i] = value_buffer[i+int(nonwhite)]; - } - - // Figure out where the last character (\0) in our new buffer is - int last = UI_TEXTBUFFER_LENGTH+6; - for (int i=16; i>0; i--) { - if (text_buffer[i] == '\0') { - last = i; - } - } - - // Add units depending on the display mode : ) - if (this->display_mode == POT_DISPLAY_MODE_PERCENT) { - text_buffer[last] = ' '; - text_buffer[last+1] = '%'; - text_buffer[last+2] = '\0'; - } else if (this->display_mode == POT_DISPLAY_MODE_PITCH) { - text_buffer[last] = ' '; - text_buffer[last+1] = 'S'; - text_buffer[last+2] = 'e'; - text_buffer[last+3] = 'm'; - text_buffer[last+4] = 'i'; - text_buffer[last+5] = '\0'; - } - - // Render that new text - if (this->display_mode == POT_DISPLAY_MODE_SWITCH) { - centeredText(switch_labels[int(last_reading+switch_offset)], x_center, y_center+4, SH110X_WHITE); - } else if (this->display_mode == POT_DISPLAY_MODE_SWITCH_NUMBERS) { - sprintf(text_buffer, "%d", int(last_reading)); - centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE); - } else { - centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE); - } - } - - // Show this for 700 ms after it has been last touched - if ((now - this->last_displayed) > 700.0) { - this->should_display = false; - } -} - - -#endif \ No newline at end of file diff --git a/code/daisy-looper/ui.h b/code/daisy-looper/ui.h deleted file mode 100644 index 030f2661b16dce56fe0b4c8d2a6348f97cbfdfc6..0000000000000000000000000000000000000000 --- a/code/daisy-looper/ui.h +++ /dev/null @@ -1,884 +0,0 @@ -#include <stdint.h> -#include "WSerial.h" -#include "usbd_def.h" -#ifndef Ui_h -#define Ui_h - -#include "Adafruit_SH110X.h" -#include "Adafruit_GFX.h" -#include "potentiometers.h" -#include "buttons.h" -#include "looper.h" -#include "button_grid.h" - -#define UI_MAX_FPS 10 -#define WAVEFORM_OVERSAMPLING 2 -#define WAVEFORM_LIN true - -extern Potentiometer pot_1, pot_2, pot_3, pot_4, pot_5, pot_6, pot_7; -extern Button button_1, button_2, button_3, button_4, button_5, button_6; -extern Adafruit_SH1106G display; -extern atoav::Looper looper_a, looper_b, looper_c, looper_d, looper_e; - -// Should the splash-screen be shown on boot? -bool show_splash = false; - -// Represents the possible states of the UI -enum UiMode { - UI_MODE_SPLASH, // A splash screen that is shown on startup - UI_MODE_DEFAULT, // Default screen: Show Waveform and Parameters - UI_MODE_REC_MENU, // ButtonGrid Menu: Recording Settings - UI_MODE_PLAY_MENU, // ButtonGrid Menu: Playback Settings - UI_MODE_TRIGGER_MENU, // ButtonGrid Menu: Trigger Settings - UI_MODE_FX_MENU, // ButtonGrid Menu: FX Settings - UI_MODE_BUFFER_MENU, // ButtonGrid Menu: Buffer Settings - UI_MODE_LAST -}; - -// Represents possible recording modes -enum RecMode { - REC_MODE_FULL, // Record into full buffer (looping back to start) - REC_MODE_LOOP, // Limit recording to the loop - REC_MODE_FULL_SHOT, // Record into full buffer, stop at the end - REC_MODE_LAST -}; - -// Represents possible recording sources -enum RecSource { - REC_SOURCE_PRE, // Record Incoming audio - REC_SOURCE_LAST_BUF, // Record Last selected Buffer - REC_SOURCE_OUT, // Record the buffer output - REC_SOURCE_NOISE, // Record Noise - REC_SOURCE_LAST -}; - -// Represents possible playback modes -enum PlayMode { - PLAY_MODE_DRUNK, // Drunken Walk - PLAY_MODE_WINDOW, // Sliding window - PLAY_MODE_LOOP, // Loop - PLAY_MODE_GRAIN, // Granular ? - PLAY_MODE_ONESHOT, // Play it once - PLAY_MODE_LAST -}; - -// Represents possible recording states -enum RecordingState { - REC_STATE_NOT_RECORDING, // Not recording - REC_STATE_RECORDING, // Recording (replace what is in the buffer) - REC_STATE_OVERDUBBING, // Overdubbing (mix recorded values with the existing samples) - REC_STATE_LAST -}; - -enum ActiveBuffer { - ACTIVE_BUFFER_A, - ACTIVE_BUFFER_B, - ACTIVE_BUFFER_C, - ACTIVE_BUFFER_D, - ACTIVE_BUFFER_E, - ACTIVE_BUFFER_LAST, -}; - -enum BufferSummingMode { - BUFFER_SUM_MODE_SOLO, - BUFFER_SUM_MODE_SUM, - BUFFER_SUM_MODE_RING, - BUFFER_SUM_MODE_LAST, -}; - -enum FXMode { - FX_MODE_ALL, - FX_MODE_REVERB, - FX_MODE_NONE, - FX_MODE_LFO, - FX_MODE_GRAIN, - FX_MODE_FILTER, - FX_MODE_LAST, -}; - -// The Ui is _the_ coordinating class for the whole interaction. -// The default mode -// Note Descriptions get a space of 21 chars and 8 lines -class Ui { - public: - Ui() : button_grids { - ButtonGrid((int) UI_MODE_REC_MENU, { - GridButton("REC\nMENU", &button_1, true), - GridButton("MOM\nTOGGLE", &button_2, false, BUTTON_TYPE_TOGGLE, 0), - GridButton("PRE\nLAST\nOUT\nNOISE", &button_3, false, BUTTON_TYPE_MULTITOGGLE, 0), - GridButton("FULL\nLOOP\nSHOT", &button_4, false, BUTTON_TYPE_MULTITOGGLE, 1), - GridButton("NORMAL\nUNPTCH", &button_5, false, BUTTON_TYPE_MULTITOGGLE, 0), - GridButton("START\nLOOPST\nPLAYHD", &button_6, false, BUTTON_TYPE_MULTITOGGLE, 0), - }), - ButtonGrid((int) UI_MODE_PLAY_MENU, { - GridButton("STOP\nLOOP\nMULTI\nMIDI", &button_1, false, BUTTON_TYPE_MULTITOGGLE, 1), - GridButton("PLAY\nMENU", &button_2, true), - GridButton("ACTIVE\nSUM\nRING", &button_3, false, BUTTON_TYPE_MULTITOGGLE, 0), - GridButton("RE\nSTART", &button_4, false), - GridButton("SLOW\nDOWN", &button_5, false), - GridButton("REV\nERSE", &button_6, false), - }), - ButtonGrid((int) UI_MODE_TRIGGER_MENU, { - GridButton("MIDI\nTRIG.", &button_1, false), - GridButton("MIDI\nUNMUTE", &button_2, false), - GridButton("TRIG.\nMENU", &button_3, true), - GridButton("MANUAL\nTRIG.", &button_4, false), - GridButton("MANUAL\nUNMUTE", &button_5, false), - GridButton("AUTO", &button_6, false), - }), - ButtonGrid((int) UI_MODE_FX_MENU, { - GridButton("ALL", &button_1, false, BUTTON_TYPE_ENUM, 1), - GridButton("REVERB", &button_2, false, BUTTON_TYPE_ENUM, 0), - GridButton("FX\nMENU", &button_3, true), - GridButton("LFO", &button_4, false, BUTTON_TYPE_ENUM, 0), - GridButton("GRAIN", &button_5, false, BUTTON_TYPE_ENUM, 0), - GridButton("-", &button_6, false, BUTTON_TYPE_ENUM, 0), - }), - ButtonGrid((int) UI_MODE_BUFFER_MENU, { - GridButton("A", &button_1, false, BUTTON_TYPE_ENUM, 1), - GridButton("B", &button_2, false, BUTTON_TYPE_ENUM, 0), - GridButton("C", &button_3, false, BUTTON_TYPE_ENUM, 0), - GridButton("D", &button_4, false, BUTTON_TYPE_ENUM, 0), - GridButton("E", &button_5, false, BUTTON_TYPE_ENUM, 0), - GridButton("BUFFER\nMENU", &button_6, true), - }), - } {}; - - // Store the Button Grids declared above (make sure the lenght matches!) - ButtonGrid button_grids[5]; - - // Stores the current Ui Mode - UiMode ui_mode = UI_MODE_SPLASH; - - // Default Recording Mode - RecMode rec_mode = REC_MODE_LOOP; - - // Default Recording Source - RecSource rec_source = REC_SOURCE_PRE; - - // Default active buffer - ActiveBuffer active_buffer = ACTIVE_BUFFER_A; - ActiveBuffer previous_buffer = ACTIVE_BUFFER_A; - - // Default active summing mode - BufferSummingMode buffer_summing_mode = BUFFER_SUM_MODE_SOLO; - - FXMode fx_mode = FX_MODE_ALL; - - // Render the UI - // Except for the splash screen this is expected to be called - // repeatedly in a loop - void Render() { - double now = millis(); - // Serial.println(1000.0/UI_MAX_FPS); - if ((now - last_render) > (1000.0/UI_MAX_FPS)) { - switch (ui_mode) { - case UI_MODE_SPLASH: - renderSplash(); - break; - case UI_MODE_DEFAULT: - renderDefault(); - break; - case UI_MODE_REC_MENU: - renderGrid(0, rec_mode); - break; - case UI_MODE_PLAY_MENU: - renderGrid(1); - break; - case UI_MODE_TRIGGER_MENU: - renderGrid(2); - break; - case UI_MODE_FX_MENU: - renderGrid(3, fx_mode); - break; - case UI_MODE_BUFFER_MENU: - renderGrid(4, active_buffer); - break; - } - last_render = now; - } - } - - // Helper method to render a certain button grid - void renderGrid(size_t num, int button_enum=0) { - display.clearDisplay(); - button_grids[num].render(button_enum); - display.display(); - } - - // Renders a splash screen (runs once) - void renderSplash() { - display.setTextSize(1); - - // Splash rendering is now done, go to next UI Mode - setMode(UI_MODE_DEFAULT); - } - - // Helper method to reset the controls - void resetControls() { - button_1.reset(); - button_2.reset(); - button_3.reset(); - button_4.reset(); - button_5.reset(); - button_6.reset(); - } - - Button* setupButtonGrid(int n) { - // Find the index of the home button - int home_button_index = button_grids[n].homeButtonIndex(); - - // Create a pointer to the hoime button - Button* home_button = button_grids[n].grid_buttons_[home_button_index].button; - - // Reset the controls - resetControls(); - - // Setup the button grid - button_grids[n].setup(); - - // Return to default mode on release - home_button->onReleased([this, n](){ - this->setMode(UI_MODE_DEFAULT); - this->button_grids[n].hideAllDescriptions(); - activeLooper()->speedUp(); - }); - - // Return pointer to the home button - return home_button; - } - - // Setup the Recording Menu - void setupRecMenu() { - // Only run once when the ui_mode changed - if (ui_mode == UI_MODE_REC_MENU && last_ui_mode != UI_MODE_REC_MENU) { - int n = 0; - - // Setup button Grid - Button* home_button = setupButtonGrid(n); - - // Toggle between momentary and toggle recording modes - button_2.onPress([this, n](){ - rec_button_momentary = !rec_button_momentary; - button_grids[n].grid_buttons_[1].active = !rec_button_momentary; - }); - - // Set Recording Source (Pre/Post/Out/Noise) - button_3.onPress([this, n](){ - button_grids[n].grid_buttons_[2].next(); - rec_source = (RecSource) button_grids[n].grid_buttons_[2].active; - }); - - // Switch Recording modes (Full/Loop/Oneshot) - button_4.onPress([this, n](){ - button_grids[n].grid_buttons_[3].next(); - // Button.active returns number according to mode, we cast it to a RecMode enum - rec_mode = (RecMode) button_grids[n].grid_buttons_[3].active; - switch (rec_mode) { - case REC_MODE_FULL: - looper_a.setRecModeFull(); - looper_b.setRecModeFull(); - looper_c.setRecModeFull(); - looper_d.setRecModeFull(); - looper_e.setRecModeFull(); - break; - case REC_MODE_LOOP: - looper_a.setRecModeLoop(); - looper_b.setRecModeLoop(); - looper_c.setRecModeLoop(); - looper_d.setRecModeLoop(); - looper_e.setRecModeLoop(); - break; - case REC_MODE_FULL_SHOT: - looper_a.setRecModeFullShot(); - looper_b.setRecModeFullShot(); - looper_c.setRecModeFullShot(); - looper_d.setRecModeFullShot(); - looper_e.setRecModeFullShot(); - break; - } - }); - - // Set Recording Pitch mode (Normal/Pitched/Unpitched) - button_5.onPress([this, n](){ - button_grids[n].grid_buttons_[4].next(); - looper_a.setRecPitchMode((atoav::RecPitchMode) button_grids[n].grid_buttons_[4].active); - looper_b.setRecPitchMode((atoav::RecPitchMode) button_grids[n].grid_buttons_[4].active); - looper_c.setRecPitchMode((atoav::RecPitchMode) button_grids[n].grid_buttons_[4].active); - looper_d.setRecPitchMode((atoav::RecPitchMode) button_grids[n].grid_buttons_[4].active); - looper_e.setRecPitchMode((atoav::RecPitchMode) button_grids[n].grid_buttons_[4].active); - }); - - // Set Recording Start Option (Buffer Start/Loop Start/Playhead) - button_6.onPress([this, n](){ - button_grids[n].grid_buttons_[5].next(); - looper_a.setRecStartMode((atoav::RecStartMode) button_grids[n].grid_buttons_[5].active); - looper_b.setRecStartMode((atoav::RecStartMode) button_grids[n].grid_buttons_[5].active); - looper_c.setRecStartMode((atoav::RecStartMode) button_grids[n].grid_buttons_[5].active); - looper_d.setRecStartMode((atoav::RecStartMode) button_grids[n].grid_buttons_[5].active); - looper_e.setRecStartMode((atoav::RecStartMode) button_grids[n].grid_buttons_[5].active); - }); - - // Store the last ui mode, for the check on top - last_ui_mode = ui_mode; - } - } - - // Setup the Buffer Menu - void setupBufferMenu() { - // Only run once when the ui_mode changed - if (ui_mode == UI_MODE_BUFFER_MENU && last_ui_mode != UI_MODE_BUFFER_MENU) { - int n = 4; - - // Setup button Grid - Button* home_button = setupButtonGrid(n); - - button_1.onPress([this, n](){ - previous_buffer = active_buffer; - active_buffer = ACTIVE_BUFFER_A; - waveform_cache_dirty = true; - }); - button_2.onPress([this, n](){ - previous_buffer = active_buffer; - active_buffer = ACTIVE_BUFFER_B; - waveform_cache_dirty = true; - }); - button_3.onPress([this, n](){ - previous_buffer = active_buffer; - active_buffer = ACTIVE_BUFFER_C; - waveform_cache_dirty = true; - }); - button_4.onPress([this, n](){ - previous_buffer = active_buffer; - active_buffer = ACTIVE_BUFFER_D; - waveform_cache_dirty = true; - }); - button_5.onPress([this, n](){ - previous_buffer = active_buffer; - active_buffer = ACTIVE_BUFFER_E; - waveform_cache_dirty = true; - }); - - // Store the last ui mode, for the check on top - last_ui_mode = ui_mode; - } - } - - // Setup the Play Menu - void setupPlayMenu() { - // Only run once when the ui_mode changed - if (ui_mode == UI_MODE_PLAY_MENU && last_ui_mode != UI_MODE_PLAY_MENU) { - int n = 1; - - // Ensure the UI showes the play mode of the active looper - button_grids[n].grid_buttons_[0].active = (int) activeLooper()->playback_state ; - - // Setup button Grid - Button* home_button = setupButtonGrid(n); - - // Change the way in which buffers are summed - button_3.onPress([this, n](){ - button_grids[n].grid_buttons_[2].next(); - buffer_summing_mode = (BufferSummingMode) button_grids[n].grid_buttons_[2].active; - }); - - // Change playback state (mode) of the current looper - button_1.onPress([this, n](){ - button_grids[n].grid_buttons_[0].next(); - activeLooper()->playback_state = (atoav::PlaybackState) (button_grids[n].grid_buttons_[0].active); - }); - - // Restart - button_4.onPress([this, n](){ - activeLooper()->restart(); - }); - - // DJ-style slow-down effect - button_5.onHold([this, n](){ - activeLooper()->slowDown(); - }); - button_5.onReleased([this, n](){ - activeLooper()->speedUp(); - }); - - button_6.onHold([this, n](){ - activeLooper()->reverse(); - }); - button_6.onReleased([this, n](){ - activeLooper()->speedUp(); - }); - - // Store the last ui mode, for the check on top - last_ui_mode = ui_mode; - } - } - - // Setup the FX Menu - void setupFXMenu() { - // Only run once when the ui_mode changed - if (ui_mode == UI_MODE_FX_MENU && last_ui_mode != UI_MODE_FX_MENU) { - int n = 3; - - // Ensure the UI showes the play mode of the active looper - // button_grids[n].grid_buttons_[0].active = (int) fx_mode; - - // Setup button Grid - Button* home_button = setupButtonGrid(n); - - // Select the active Effect (All) - button_1.onPress([this, n](){ - fx_mode = FX_MODE_ALL; - pot_5.setDisplayMode("LFO", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_5.setLinear(); - pot_6.setDisplayMode("Volume", 400.0f, POT_DISPLAY_MODE_PERCENT); - pot_7.setDisplayMode("Reverb", 100.0f, POT_DISPLAY_MODE_PERCENT); - }); - - // Select the active Effect (Reverb) - button_2.onPress([this, n](){ - fx_mode = FX_MODE_REVERB; - pot_5.setDisplayMode("Rev. Tone", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_5.setLinear(); - pot_6.setDisplayMode("Rev. Decay", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_7.setDisplayMode("Reverb Mix", 100.0f, POT_DISPLAY_MODE_PERCENT); - }); - - // Select the active Effect (LFO) - button_4.onPress([this, n](){ - fx_mode = FX_MODE_LFO; - pot_5.setDisplayMode("LFO Mode", 100.0f, POT_DISPLAY_MODE_SWITCH); - pot_5.setSwitch(); - pot_5.switch_positions = 4; - pot_5.switch_offset = 0; - pot_6.setDisplayMode("LFO Speed", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_7.setDisplayMode("LFO Amount", 100.0f, POT_DISPLAY_MODE_PERCENT); - }); - - // Select the active Effect (GRAIN) - button_5.onPress([this, n](){ - fx_mode = FX_MODE_GRAIN; - pot_5.setDisplayMode("Grain Num", 100.0f, POT_DISPLAY_MODE_SWITCH_NUMBERS); - pot_5.setSwitch(); - pot_5.switch_positions = 8; - pot_6.setDisplayMode("Grn. Spread", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_7.setDisplayMode("Grain Var.", 100.0f, POT_DISPLAY_MODE_PERCENT); - }); - - // Select the active Effect (FILTER) - button_6.onPress([this, n](){ - fx_mode = FX_MODE_FILTER; - pot_5.setDisplayMode("Lowpass", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_6.setDisplayMode("Highpass", 100.0f, POT_DISPLAY_MODE_PERCENT); - pot_7.setDisplayMode("Resonance", 100.0f, POT_DISPLAY_MODE_PERCENT); - }); - - // Store the last ui mode, for the check on top - last_ui_mode = ui_mode; - } - } - - // Setup the default (waveform) screen - void setupDefault() { - // Only run once on mode change - if (ui_mode == UI_MODE_DEFAULT && last_ui_mode != UI_MODE_DEFAULT) { - // Reset controls - resetControls(); - - // Set up the initial recording mode - switch (rec_mode) { - case REC_MODE_FULL: - looper_a.setRecModeFull(); - looper_b.setRecModeFull(); - looper_c.setRecModeFull(); - looper_d.setRecModeFull(); - looper_e.setRecModeFull(); - break; - case REC_MODE_LOOP: - looper_a.setRecModeLoop(); - looper_b.setRecModeLoop(); - looper_c.setRecModeLoop(); - looper_d.setRecModeLoop(); - looper_e.setRecModeLoop(); - break; - case REC_MODE_FULL_SHOT: - looper_a.setRecModeFullShot(); - looper_b.setRecModeFullShot(); - looper_c.setRecModeFullShot(); - looper_d.setRecModeFullShot(); - looper_e.setRecModeFullShot(); - break; - }; - - // Setup Button functions (these should enter the ButtonGrid Menus) - button_1.onHold([this](){ this->setMode(UI_MODE_REC_MENU); }); - button_2.onHold([this](){ this->setMode(UI_MODE_PLAY_MENU); }); - button_3.onHold([this](){ this->setMode(UI_MODE_FX_MENU); }); - button_6.onHold([this](){ this->setMode(UI_MODE_BUFFER_MENU); }); - - // Set the recording/overdub buttons to toggle or momentary - // depending on the value of the option - if (rec_button_momentary) { - button_4.onHold([this](){ this->activateRecording(); }); - button_5.onHold([this](){ this->activateOverdub(); }); - button_4.onReleased([this](){ this->stopRecording(); }); - button_5.onReleased([this](){ this->stopRecording(); }); - } else { - button_4.onReleased([this](){ this->toggleRecording(); }); - button_5.onReleased([this](){ this->toggleOverdub(); }); - } - - // Store the last ui mode, for the check on top - last_ui_mode = ui_mode; - } - } - - // Render the default screen (waveform) - void renderDefault() { - // Store the current time and check how long ago the last frame was - // in ms - - // Clear the display - display.clearDisplay(); - - // Waveform should be maximum screen-heigh - int wave_height = display.height() * 1.0f; - // Ensure that when stepping from left to right we fit the waveform on the screen - int step = activeLooper()->getBufferLength() / (display.width() * WAVEFORM_OVERSAMPLING); - // Helper variable for the bottom of the screen - int bottom = display.height()-1; - - // Render the waveform by iterating through the samples (oversampled by a factor - // defined on top of this file). Average the samples for each pixel of the 128 px - // wide screen and cache the resulting heights so we only have to recalculate when - // the waveform changes - for (int i=0; i<display.width()*WAVEFORM_OVERSAMPLING; i+=WAVEFORM_OVERSAMPLING) { - uint16_t x = int(i / WAVEFORM_OVERSAMPLING); - // Only recalculate if the cahce is dirty, else use cache - if (waveform_cache_dirty) { - float sig = 0.0f; - float scale = 1.0f; - if (!WAVEFORM_LIN) { - scale = 10.0f; - } - // Step through the buffer and sum the absolute values - for (int s=0; s<WAVEFORM_OVERSAMPLING; s++) { - float abs_sig = activeLooper()->getBuffer()[step*i]; - abs_sig = abs(abs_sig) * scale; - sig += abs_sig; - } - // We oversampled so divide here - sig = sig / float(WAVEFORM_OVERSAMPLING); - - if (!WAVEFORM_LIN) { - // Volume is logarithmic (hiding silent noises) - if (sig != 0.0f) { - sig = log10(sig); - } - } - waveform_cache[x] = int(sig * wave_height); - } - - // Draw the vertical lines from bottom up, depending on the level of the - // calulcated wave on this point of the screen - display.drawFastVLine(x, bottom, -waveform_cache[x], SH110X_WHITE); - - } - // Draw one horizontal line on bottom - display.drawFastHLine(0, bottom, display.width(), SH110X_WHITE); - - // Cache is now marked as clean - waveform_cache_dirty = false; - - // Draw Indicator for loop start - int x_start_loop = int(activeLooper()->loop_start_f * display.width()); - display.drawLine(x_start_loop, 0, x_start_loop, bottom, SH110X_WHITE); - display.fillTriangle(x_start_loop, 6, x_start_loop, 0, x_start_loop+3, 0, SH110X_WHITE); - - // Draw Indicator for Loop End - int x_loop_length = int(activeLooper()->loop_length_f * display.width()); - int x_loop_end = (x_start_loop + x_loop_length) % display.width(); - display.drawLine(x_loop_end, 0, x_loop_end, bottom, SH110X_WHITE); - display.fillTriangle(x_loop_end, 6, x_loop_end-3, 0, x_loop_end, 0, SH110X_WHITE); - - // Draw connecting line for start and end - if (x_loop_end >= x_start_loop) { - display.drawLine(x_start_loop, 0, x_loop_end, 0, SH110X_WHITE); - } else { - display.drawLine(x_start_loop, 0, display.width(), 0, SH110X_WHITE); - display.drawLine(0, 0, x_loop_end, 0, SH110X_WHITE); - } - - // Draw Playhead - switch (activeLooper()->playback_state) { - case atoav::PLAYBACK_STATE_LOOP: - { - int x_playhead = int(activeLooper()->GetPlayhead() * display.width()) + x_start_loop; - display.drawFastVLine(x_playhead, 6, 24, SH110X_WHITE); - break; - } - case atoav::PLAYBACK_STATE_MULTILOOP: - { - float* playheads = activeLooper()->GetPlayheads(); - uint8_t count = activeLooper()->GetPlayheadCount(); - int x_playhead = 0; - for (size_t i=0; i<count-1; i++) { - x_playhead = int(playheads[i] * display.width()) + x_start_loop; - int h = 6 + i*3; - display.drawFastVLine(x_playhead, h, 3, SH110X_WHITE); - } - break; - } - case atoav::PLAYBACK_STATE_MIDI: - { - int x_playhead = int(activeLooper()->GetPlayhead() * display.width()) + x_start_loop; - display.drawFastVLine(x_playhead, 6, 24, SH110X_WHITE); - break; - } - } - - - // Draw Recording Indicator and Recording Head - if (recording_state == REC_STATE_RECORDING) { - // Draw Rec Head - int x_rec_head = int(activeLooper()->GetRecHead() * display.width()); - display.drawFastVLine(x_rec_head, 10, bottom, SH110X_WHITE); - display.fillCircle(x_rec_head, 10, 3, SH110X_WHITE); - // Record sign - display.fillRect(0, 0, 13, 13, SH110X_WHITE); - display.fillRect(2, 2, 12, 12, SH110X_WHITE); - display.fillRect(1, 1, 11, 11, SH110X_BLACK); - display.fillCircle(6, 6, 3, SH110X_WHITE); - } - - // Draw Overdub Indicator and Recording Head - if (recording_state == REC_STATE_OVERDUBBING) { - // Draw Rec Head - int x_rec_head = int(activeLooper()->GetRecHead() * display.width()); - display.drawFastVLine(x_rec_head, 10, bottom, SH110X_WHITE); - display.fillCircle(x_rec_head, 10, 3, SH110X_WHITE); - - // Overdub sign (a "plus") - display.fillRect(0, 0, 13, 13, SH110X_WHITE); - display.fillRect(2, 2, 12, 12, SH110X_WHITE); - display.fillRect(1, 1, 11, 11, SH110X_BLACK); - display.drawLine(6, 2, 6, 10, SH110X_WHITE); - display.drawLine(2, 6, 10, 6, SH110X_WHITE); - } - - // Render potentiometer UIs in case a knob is changed - pot_1.renderUi(); - pot_2.renderUi(); - pot_3.renderUi(); - pot_4.renderUi(); - pot_5.renderUi(); - pot_6.renderUi(); - pot_7.renderUi(); - - // Display all the things done above - display.display(); - } - - // Activate recording and set the waveform cache to dirty - void activateRecording() { - if (recording_state != REC_STATE_RECORDING) { - activeLooper()->SetRecord(); - waveform_cache_dirty = true; - recording_state = REC_STATE_RECORDING; - } - } - - // Toggle recording - void toggleRecording() { - switch (recording_state) { - case REC_STATE_NOT_RECORDING: - activateRecording(); - break; - case REC_STATE_RECORDING: - stopRecording(); - break; - case REC_STATE_OVERDUBBING: - activateRecording(); - break; - } - } - - // Activates overdubbing - void activateOverdub() { - if (recording_state != REC_STATE_OVERDUBBING) { - waveform_cache_dirty = true; - recording_state = REC_STATE_OVERDUBBING; - activeLooper()->SetOverdub(); - } - } - - // Stop the recording - void stopRecording() { - if (recording_state != REC_STATE_NOT_RECORDING) { - recording_state = REC_STATE_NOT_RECORDING; - activeLooper()->SetStopWriting(); - } - } - - // Toggle overdub off and on - void toggleOverdub() { - switch (recording_state) { - case REC_STATE_NOT_RECORDING: - activateOverdub(); - break; - case REC_STATE_OVERDUBBING: - stopRecording(); - break; - case REC_STATE_RECORDING: - activateOverdub(); - break; - } - } - - // Reset the recording state (mark waveform cahce dirty) - void resetRecordingState() { - if (recording_state == REC_STATE_RECORDING || recording_state == REC_STATE_OVERDUBBING) { - waveform_cache_dirty = true; - } - } - - // Set the mode of the UI (and thus change the screen) - void setMode(UiMode mode) { - if (last_ui_mode == mode) { return; } - last_ui_mode = ui_mode; - ui_mode = mode; - switch (ui_mode) { - case UI_MODE_SPLASH: - break; - case UI_MODE_DEFAULT: - break; - case UI_MODE_REC_MENU: - this->button_grids[0].hideAllDescriptions(); - break; - case UI_MODE_PLAY_MENU: - this->button_grids[1].hideAllDescriptions(); - break; - case UI_MODE_TRIGGER_MENU: - this->button_grids[2].hideAllDescriptions(); - break; - case UI_MODE_FX_MENU: - this->button_grids[3].hideAllDescriptions(); - break; - case UI_MODE_BUFFER_MENU: - this->button_grids[4].hideAllDescriptions(); - break; - } - } - - - // Update the Ui variables (expected to run repeatedly) - void update() { - resetRecordingState(); - - switch (ui_mode) { - case UI_MODE_SPLASH: - break; - case UI_MODE_DEFAULT: - setupDefault(); - break; - case UI_MODE_REC_MENU: - setupRecMenu(); - break; - case UI_MODE_PLAY_MENU: - setupPlayMenu(); - break; - case UI_MODE_TRIGGER_MENU: - break; - case UI_MODE_FX_MENU: - setupFXMenu(); - break; - case UI_MODE_BUFFER_MENU: - setupBufferMenu(); - break; - } - } - - // Returns a pointer to the currently active looper - atoav::Looper * activeLooper() { - switch(active_buffer) { - case ACTIVE_BUFFER_A: { - atoav::Looper * ptr = &looper_a; - return ptr; - break; - } - case ACTIVE_BUFFER_B: { - atoav::Looper * ptr = &looper_b; - return ptr; - break; - } - case ACTIVE_BUFFER_C: { - atoav::Looper * ptr = &looper_c; - return ptr; - break; - } - case ACTIVE_BUFFER_D: { - atoav::Looper * ptr = &looper_d; - return ptr; - break; - } - case ACTIVE_BUFFER_E: { - atoav::Looper * ptr = &looper_e; - return ptr; - break; - } - } - // Unreachable, but makes the compiler shut up - atoav::Looper * ptr = &looper_a; - return ptr; - } - - // Returns a pointer to the currently active looper - atoav::Looper * previousLooper() { - switch(previous_buffer) { - case ACTIVE_BUFFER_A: { - atoav::Looper * ptr = &looper_a; - return ptr; - break; - } - case ACTIVE_BUFFER_B: { - atoav::Looper * ptr = &looper_b; - return ptr; - break; - } - case ACTIVE_BUFFER_C: { - atoav::Looper * ptr = &looper_c; - return ptr; - break; - } - case ACTIVE_BUFFER_D: { - atoav::Looper * ptr = &looper_d; - return ptr; - break; - } - case ACTIVE_BUFFER_E: { - atoav::Looper * ptr = &looper_e; - return ptr; - break; - } - } - // Unreachable, but makes the compiler shut up - atoav::Looper * ptr = &looper_a; - return ptr; - } - - // Set the Looper start/length to a given value - void setLoop(float start, float length) { - activeLooper()->SetLoop(start, length); - } - - private: - double last_render = 0.0; - uint16_t waveform_cache[128] = {0}; - bool waveform_cache_dirty = true; - RecordingState recording_state = REC_STATE_NOT_RECORDING; - UiMode last_ui_mode = UI_MODE_LAST; - bool rec_button_momentary = true; -}; - - - -#endif \ No newline at end of file