From 21984c931035a16a3a03f13744a217c983fb2c71 Mon Sep 17 00:00:00 2001 From: David Huss <dh@atoav.com> Date: Fri, 2 Feb 2024 16:59:10 +0100 Subject: [PATCH] Add LFO Modes and more This commit adds: - A Square, Random and Jump LFO-Waveform - A possibility to record the last active buffer into the new one - Crude crossfading at the beginning and end of the loop - Some minor fixes to reverb levels - Fixes to the saturation function (was too silent before) --- circuitsim/lookup-tables.ipynb | 50 ++++++++------ code/daisy-looper/daisy-looper.ino | 103 +++++++++++++++++++++-------- code/daisy-looper/helpers.h | 16 +++++ code/daisy-looper/lfo.h | 9 +++ code/daisy-looper/looper.h | 35 +++++----- code/daisy-looper/luts.h | 13 ---- code/daisy-looper/potentiometers.h | 2 +- code/daisy-looper/ui.h | 76 +++++++++++++-------- 8 files changed, 196 insertions(+), 108 deletions(-) create mode 100644 code/daisy-looper/lfo.h diff --git a/circuitsim/lookup-tables.ipynb b/circuitsim/lookup-tables.ipynb index 61b523f..8d29c76 100644 --- a/circuitsim/lookup-tables.ipynb +++ b/circuitsim/lookup-tables.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "52c8bec3-db2d-4522-b692-b035a71410de", "metadata": {}, "outputs": [ @@ -16,7 +16,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "41b995dfbdf246859dc4a9991f85540b", + "model_id": "ffe8405ccce2405c84171b7c83e66e65", "version_major": 2, "version_minor": 0 }, @@ -136,14 +136,14 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 2, "id": "41562cc6-9911-4fb1-87ec-f2b3c8bfb3c2", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "efcba863f7d145b390f1118f16070ad5", + "model_id": "eddca77aa50c47da8c24ed47bc863c0c", "version_major": 2, "version_minor": 0 }, @@ -160,7 +160,7 @@ "16" ] }, - "execution_count": 41, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" }, @@ -246,14 +246,14 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 3, "id": "ecc666b0-8195-4276-a576-39d41753b540", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2c77d02991b94167ae4191faa991c4c6", + "model_id": "e17619a0890a4ab081293b27925c743d", "version_major": 2, "version_minor": 0 }, @@ -270,7 +270,7 @@ "0.0" ] }, - "execution_count": 44, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" }, @@ -363,14 +363,14 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 4, "id": "2ecfda42-4a3c-489a-bc90-8576648c339c", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0a1fd320498041f5b5d7edd19c64d36a", + "model_id": "56f51bfc443847a0adda4da0ff439e8a", "version_major": 2, "version_minor": 0 }, @@ -387,7 +387,7 @@ "0.9999999999999999" ] }, - "execution_count": 42, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" }, @@ -480,14 +480,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 5, "id": "e51416f3-f34d-4513-9f0c-fa52c468274e", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "098c400f3b684499968bef484bd18f3d", + "model_id": "74318935b12f4752aec3807327bea0fe", "version_major": 2, "version_minor": 0 }, @@ -501,7 +501,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "59ad9d541c5340b2980b7531a9a1a285", + "model_id": "51bcd6adaa2a492d9f61fdd4dcca7232", "version_major": 2, "version_minor": 0 }, @@ -730,14 +730,14 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 26, "id": "f35f1609-3a10-4dce-b7dd-201d79f2c39c", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2cffc24c8bc34b569e6e7c74e064bcbb", + "model_id": "b0e2832a8efa405790ad1cfd4529aa3b", "version_major": 2, "version_minor": 0 }, @@ -751,7 +751,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c7d59ce714284b299d5406aab8d5ae5e", + "model_id": "1dcfe3c0ccad4e4a92ea2d43a92ad01f", "version_major": 2, "version_minor": 0 }, @@ -761,6 +761,13 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "19\n" + ] } ], "source": [ @@ -769,17 +776,17 @@ "# X / Y / Curvature\n", "lines_orig = [\n", " [-1.5, -1.0, 1.0],\n", - " [-0.7, -0.7, 0.0],\n", + " [-0.9, -0.8, 0.0],\n", " [0.0, 0.0, 0.0],\n", - " [0.7, 0.7, -1.4],\n", + " [0.9, 0.8, -1.1],\n", " [1.5, 1.0, 0.0],\n", "]\n", "\n", - "lines = make_lines(lines_orig, 20)\n", + "lines = make_lines(lines_orig, 8)\n", "\n", "\n", "x = [a[0] for a in lines]\n", - "y = [a[1]*0.5 for a in lines]\n", + "y = [min(1.0, a[1]) for a in lines]\n", "c = [a[2] for a in lines]\n", "# draw(x, y, 0.45/2)\n", "\n", @@ -803,6 +810,7 @@ "mybtn.on_click(mybtn_event_handler)\n", "\n", "display(mybtn)\n", + "print(len(x))\n", "\n" ] }, diff --git a/code/daisy-looper/daisy-looper.ino b/code/daisy-looper/daisy-looper.ino index 3287a84..621936e 100644 --- a/code/daisy-looper/daisy-looper.ino +++ b/code/daisy-looper/daisy-looper.ino @@ -12,6 +12,7 @@ #include "helpers.h" #include "luts.h" #include "ui.h" +#include "lfo.h" MIDI_CREATE_DEFAULT_INSTANCE(); #define BUFFER_LENGTH_SECONDS 5 @@ -84,8 +85,9 @@ 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; +//float pressure = 0.0f; // Actual audio-processing is orchestrated here void AudioCallback(float **in, float **out, size_t size) { @@ -98,10 +100,33 @@ void AudioCallback(float **in, float **out, size_t size) { // 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 lfo_value = lfo.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, @@ -114,21 +139,36 @@ void AudioCallback(float **in, float **out, size_t size) { } // When the metro ticks, trigger the envelope to start. float random_amount = lfo_amount * 2.0; + if (trig) { // Random LFO - if (lfo_kind == 1) { - // Chance - if (random(0.0f, 1.0f) < lfo_amount) { - ui.activeLooper()->addToPlayhead(rand * random_amount * 48000.0f); - } + 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 - if (lfo_kind == 0) { - ui.activeLooper()->setPlaybackSpeed(pitch_val + lfo_value * lfo_amount + midi_pitch_offset); - } else { - ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset); + 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; } @@ -137,6 +177,11 @@ void AudioCallback(float **in, float **out, size_t size) { 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]); @@ -145,7 +190,7 @@ void AudioCallback(float **in, float **out, size_t size) { switch (ui.buffer_summing_mode) { case BUFFER_SUM_MODE_SOLO: // Only play active looper - looper_out = saturate(ui.activeLooper()->Process()); + looper_out = ui.activeLooper()->Process(); break; case BUFFER_SUM_MODE_SUM: // Sum all loopers @@ -154,7 +199,7 @@ void AudioCallback(float **in, float **out, size_t size) { looper_out += looper_c.Process(); looper_out += looper_d.Process(); looper_out += looper_e.Process(); - looper_out = saturate(looper_out); + looper_out = looper_out; break; case BUFFER_SUM_MODE_RING: // Sum all loopers and ringmodulate with input @@ -170,12 +215,12 @@ void AudioCallback(float **in, float **out, size_t size) { deadbanded_input = max(0.0f, in[1][i] - 0.05f); } looper_out *= deadbanded_input*2.0f; - looper_out = saturate(looper_out); + looper_out = looper_out; break; } // looper_out = pressure * looper_out; - looper_out = volume * looper_out; + looper_out = saturate(volume * looper_out); // Mix the dry/Wet of the looper output = drywetmix * looper_out + in[1][i] * (1.0f - drywetmix); @@ -187,7 +232,8 @@ void AudioCallback(float **in, float **out, size_t size) { reverb.Process(output, output, &out1, &out2); // Short decays are silent, so increase level here - out1 = out1 * map(reverb_decay, 0.0f, 1.0f, 2.0f, 1.0f); + float dec_fac = 1.0f + (1.0f - reverb_decay) * 2.0f; + out1 = out1 * dec_fac; // Mix reverb with the dry signal depending on the amount dialed output = output * (1.0f - reverbmix) + out1 * reverbmix; @@ -238,16 +284,16 @@ void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) { 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 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; +// } @@ -261,6 +307,7 @@ void setup() { 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(); @@ -313,14 +360,14 @@ void setup() { // Setup MIDI handlers MIDI.setHandleNoteOn(handleNoteOn); MIDI.setHandleNoteOff(handleNoteOff); - MIDI.setHandleAfterTouchChannel(handleAftertouch); + // 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("Pitch", 1000.0f, POT_DISPLAY_MODE_PERCENT); + 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); diff --git a/code/daisy-looper/helpers.h b/code/daisy-looper/helpers.h index 964bfa3..2f6601a 100644 --- a/code/daisy-looper/helpers.h +++ b/code/daisy-looper/helpers.h @@ -139,4 +139,20 @@ int button_multi(const char *buf, int x, int y, int color, int underline_line=0, 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/lfo.h b/code/daisy-looper/lfo.h new file mode 100644 index 0000000..a91cdb6 --- /dev/null +++ b/code/daisy-looper/lfo.h @@ -0,0 +1,9 @@ +#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 index 5cb2016..a9a0575 100644 --- a/code/daisy-looper/looper.h +++ b/code/daisy-looper/looper.h @@ -6,7 +6,6 @@ namespace atoav { enum RecPitchMode { REC_PITCH_MODE_NORMAL, - REC_PITCH_MODE_PITCHED, REC_PITCH_MODE_UNPITCHED, REC_PITCH_MODE_LAST, }; @@ -236,11 +235,6 @@ void Looper::Record(float in) { case REC_PITCH_MODE_UNPITCHED: rec_head.setIncrement(playheads[0].increment); break; - case REC_PITCH_MODE_PITCHED: - if (playheads[0].increment != 0.0) { - rec_head.setIncrement(1.0f/playheads[0].increment); - } - break; } // Increment recording head rec_head.update(); @@ -307,32 +301,41 @@ float Looper::Process() { if (!playheads[i].isActive()) continue; // Ensure we are actually inside the buffer - int play_pos = int(loop_start + playheads[i].read()) % buffer_length; + 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]; + mix += buffer[play_pos] * vol; // Advance the playhead playheads[i].update(); // Ensure the playhead stays within bounds of the loop - if ((playheads[i].read()) >= loop_length) { - playheads[i].setPosition(0.0f); - } else if (playheads[i].read() <= 0.0f) { - playheads[i].setPosition(loop_length); - } + float pos = playheads[i].read(); + if (pos >= loop_length || pos <= 0.0f) { + playheads[i].setPosition(fmod(pos, float(loop_length))); + } } - return saturate(mix); + return mix; } float Looper::GetPlayhead() { - return float(playheads[0].read()) / float(buffer_length); + 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(playheads[i].read()) / float(buffer_length); + playhead_positions[i] = float(int(playheads[i].read()) % loop_length) / float(buffer_length); } return playhead_positions; } diff --git a/code/daisy-looper/luts.h b/code/daisy-looper/luts.h index 7974c6d..3a2e1df 100644 --- a/code/daisy-looper/luts.h +++ b/code/daisy-looper/luts.h @@ -13,11 +13,6 @@ float pitch_knob_lookup_y[] = {-1.0, -1.0, -0.9, -0.9, -0.8, -0.8, -0.7, -0.7, - size_t pitch_knob_lookup_length = 62; -// Lookup Curves for Saturation b -float saturation_lookup_x[] = {-1.5, -1.46, -1.42, -1.38, -1.3399999999999999, -1.3, -1.26, -1.22, -0.7, 0.0, 0.7, 0.74, 0.78, 0.82, 0.86, 0.8999999999999999, 0.94, 0.98, 1.5}; -float saturation_lookup_y[] = {-0.5, -0.49998125, -0.49985, -0.49949375, -0.4988, -0.49765625, -0.49595, -0.49356875, -0.35, 0.0, 0.35, 0.367475, 0.38389999999999996, 0.399275, 0.41359999999999997, 0.426875, 0.4391, 0.450275, 0.5}; -size_t saturation_lookup_length = 19; - class Easer { float output = 0.0f; @@ -67,13 +62,5 @@ float get_from_xy_table(float* xtable, float* ytable, float f, size_t length) { return multiMap<float>(f, xtable, ytable, length); } -float saturate(float input) { - return get_from_xy_table( - saturation_lookup_x, - saturation_lookup_y, - min(1.5f, max(-1.5f, input)), - saturation_lookup_length - ); -} #endif \ No newline at end of file diff --git a/code/daisy-looper/potentiometers.h b/code/daisy-looper/potentiometers.h index 0af8ca4..f5e6aa3 100644 --- a/code/daisy-looper/potentiometers.h +++ b/code/daisy-looper/potentiometers.h @@ -69,7 +69,7 @@ class Potentiometer { bool last_was_nan = false; uint8_t switch_positions; uint8_t switch_offset = 0; - const char* const switch_labels[2] = {"LFO", "RANDOM"}; + const char* const switch_labels[4] = {"TRI", "SQR", "RAND", "JUMP"}; }; Potentiometer::Potentiometer(int pin) { diff --git a/code/daisy-looper/ui.h b/code/daisy-looper/ui.h index 0421807..19c4720 100644 --- a/code/daisy-looper/ui.h +++ b/code/daisy-looper/ui.h @@ -46,7 +46,7 @@ enum RecMode { // Represents possible recording sources enum RecSource { REC_SOURCE_PRE, // Record Incoming audio - REC_SOURCE_POST, // Record effects + REC_SOURCE_LAST_BUF, // Record Last selected Buffer REC_SOURCE_OUT, // Record the buffer output REC_SOURCE_NOISE, // Record Noise REC_SOURCE_LAST @@ -104,11 +104,11 @@ class Ui { 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, "REC/OVERDUB BUTTONS\n\nMOM->Record while \n Button is held\n\nTOGGLE->Press once to\n start, press \n again to stop"), - GridButton("PRE\nPOST\nOUT\nNOISE", &button_3, false, BUTTON_TYPE_MULTITOGGLE, 0, "REC/OVERDUB SOURCE\n\nPRE----->Direct Input\nPOST---->With Effects\nOUT---->Looper Output\nNOISE--->Noise Source"), - GridButton("FULL\nLOOP\nSHOT", &button_4, false, BUTTON_TYPE_MULTITOGGLE, 1, "RECORDING REGION\n\nFULL---->Whole Buffer\nLOOP----->Loop Bounds\nSHOT->Full Buffer but\n stop at the end"), - GridButton("NORMAL\nPITCHD\nUNPTCH", &button_5, false, BUTTON_TYPE_MULTITOGGLE, 0, "SPEED OF THE REC HEAD\n\nNORMAL--->Fixed Speed\nPITCHD->Inverse Playh\nUNPITCH----->Playhead\n Speed"), - GridButton("START\nLOOPST\nPLAYHD", &button_6, false, BUTTON_TYPE_MULTITOGGLE, 0, "START RECORDING AT\n\nSTART--->Start of the\n Buffer\nLOOP---->Start of the\n Loop\nPLAYH--->Position of\n the Playhead"), + 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), @@ -158,6 +158,7 @@ class Ui { // 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; @@ -208,28 +209,6 @@ class Ui { // Renders a splash screen (runs once) void renderSplash() { display.setTextSize(1); - if (show_splash) { - display.setTextColor(SH110X_BLACK); - // Play a fancy intro splash screen - for (int i=0; i < 91; i++) { - display.clearDisplay(); - display.fillCircle(display.width()/2, display.height()/2, i, SH110X_WHITE); - display.fillCircle(display.width()/2, display.height()/2, max(0, i-2), SH110X_BLACK); - if (i < 50) { - centeredText("DAISYY", display.width()/2, display.height()/2-4, SH110X_WHITE); - centeredText("LOOPER", display.width()/2, display.height()/2+4, SH110X_WHITE); - } - - display.display(); - delay(1); - } - display.invertDisplay(true); - display.display(); - delay(800); - display.clearDisplay(); - display.invertDisplay(false); - display.display(); - } // Splash rendering is now done, go to next UI Mode setMode(UI_MODE_DEFAULT); @@ -354,22 +333,27 @@ class Ui { 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; }); @@ -443,7 +427,7 @@ class Ui { fx_mode = FX_MODE_LFO; pot_5.setDisplayMode("LFO Mode", 100.0f, POT_DISPLAY_MODE_SWITCH); pot_5.setSwitch(); - pot_5.switch_positions = 2; + 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); @@ -818,6 +802,40 @@ class Ui { 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); -- GitLab