From e82cdcbf696be6c415ac5f0ae91b38aeb403f415 Mon Sep 17 00:00:00 2001 From: David Huss <dh@atoav.com> Date: Fri, 8 Dec 2023 23:20:20 +0100 Subject: [PATCH] Implements FX Menu, better pitch curve --- circuitsim/lookup-tables.ipynb | 139 ++++++++++++++-------- code/daisy-looper/daisy-looper.ino | 85 +++++++------ code/daisy-looper/looper.h | 22 ++-- code/daisy-looper/luts.h | 7 +- code/daisy-looper/potentiometers.h | 38 ++++-- code/daisy-looper/ui.h | 185 +++++++++++++++++++---------- 6 files changed, 308 insertions(+), 168 deletions(-) diff --git a/circuitsim/lookup-tables.ipynb b/circuitsim/lookup-tables.ipynb index 5844a1e..61b523f 100644 --- a/circuitsim/lookup-tables.ipynb +++ b/circuitsim/lookup-tables.ipynb @@ -480,45 +480,19 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 15, "id": "e51416f3-f34d-4513-9f0c-fa52c468274e", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-0.9\n", - "-0.8\n", - "-0.7\n", - "-0.6\n", - "-0.5\n", - "-0.4\n", - "-0.3\n", - "-0.2\n", - "-0.1\n", - "0.0\n", - "0.1\n", - "0.2\n", - "0.3\n", - "0.4\n", - "0.5\n", - "0.6\n", - "0.7\n", - "0.8\n", - "0.9\n", - "1.0\n" - ] - }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "336acd5c2a274f49b7edab41a67e6469", + "model_id": "098c400f3b684499968bef484bd18f3d", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.2, description='f', max=1.005, step=0.001), Output()), _dom_classes=…" + "interactive(children=(FloatSlider(value=0.2, description='f', max=1.0, step=0.001), Output()), _dom_classes=('…" ] }, "metadata": {}, @@ -527,7 +501,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "47a764c793a04e188a376cf6ba4f4853", + "model_id": "59ad9d541c5340b2980b7531a9a1a285", "version_major": 2, "version_minor": 0 }, @@ -607,27 +581,94 @@ " return lerp(ya, yb, new_f)\n", " \n", " \n", - "lines_orig = [\n", - " [0.0, 0.0, -0.5],\n", - " [0.45, 0.5, 0.0],\n", - " [0.55, 0.5, 1.0],\n", - " [1.0, 1.0, 0.0],\n", - "]\n", - "\n", - "half_deadband = 0.005\n", + "# lines_orig = [\n", + "# [0.0, 0.0, -0.5],\n", + "# [0.45, 0.5, 0.0],\n", + "# [0.55, 0.5, 1.0],\n", + "# [1.0, 1.0, 0.0],\n", + "# ]\n", + "\n", + "# half_deadband = 0.005\n", + "\n", + "# lines_orig = [\n", + "# [0.0, -1.0, 0.0],\n", + "# [0.0+half_deadband, -1.0, 0.0],\n", + "# ] \n", + "\n", + "# steps = list(range(-9, 11))\n", + "# for i in steps:\n", + "# f = float(i)\n", + "# print(f/10)\n", + "# lines_orig.append([0.5+f/20-half_deadband, f/10, 0.0])\n", + "# lines_orig.append([0.5+f/20+half_deadband, f/10, 0.0])\n", + "\n", + "db = 0.005\n", + "hb = db/2.0\n", "\n", "lines_orig = [\n", - " [0.0, -1.0, 0.0],\n", - " [0.0+half_deadband, -1.0, 0.0],\n", - "] \n", - "\n", - "steps = list(range(-9, 11))\n", - "for i in steps:\n", - " f = float(i)\n", - " print(f/10)\n", - " lines_orig.append([0.5+f/20-half_deadband, f/10, 0.0])\n", - " lines_orig.append([0.5+f/20+half_deadband, f/10, 0.0])\n", - "\n", + " [0.0, -1.0, 0.0], # -1000%\n", + " [0.0+hb, -1.0, 0.0], # -1000%\n", + " [0.02-hb, -0.9, 0.0], # -900%\n", + " [0.02+hb, -0.9, 0.0], # -900%\n", + " [0.04-hb, -0.8, 0.0], # -800%\n", + " [0.04+hb, -0.8, 0.0], # -800%\n", + " [0.06-hb, -0.7, 0.0], # -700%\n", + " [0.06+hb, -0.7, 0.0], # -700%\n", + " [0.08-hb, -0.6, 0.0], # -600%\n", + " [0.08+hb, -0.6, 0.0], # -600%\n", + " [0.1-hb, -0.5, 0.0], # -500%\n", + " [0.1+hb, -0.5, 0.0], # -500%\n", + " [0.12-hb, -0.4, 0.0], # -400%\n", + " [0.12+hb, -0.4, 0.0], # -400%\n", + " [0.14-hb, -0.3, 0.0], # -300%\n", + " [0.14+hb, -0.3, 0.0], # -300%\n", + " [0.16-hb, -0.2, 0.0], # -200%\n", + " [0.16+hb, -0.2, 0.0], # -200%\n", + " [0.2-hb, -0.1, 0.0], # -100%\n", + " [0.2+hb, -0.1, 0.0], # -100%\n", + " [0.25-hb, -0.05, 0.0], # -50%\n", + " [0.25+hb, -0.05, 0.0], # -50%\n", + " [0.3-hb, -0.025, 0.0], # -25%\n", + " [0.3+hb, -0.025, 0.0], # -25%\n", + " [0.38-hb, -0.0125, 0.0], # -12.5%\n", + " [0.38+hb, -0.0125, 0.0], # -12.5%\n", + " [0.42-hb, -0.00625, 0.0], # -6.25%\n", + " [0.42+hb, -0.00625, 0.0], # -6.25%\n", + " [0.46-hb, -0.003125, 0.0], # -3.125%\n", + " [0.46+hb, -0.003125, 0.0], # -3.125%\n", + " [0.5-hb, 0.0, 0.0], # 0%\n", + " [0.5+hb, 0.0, 0.0], # 0%\n", + " [1.0-0.46-hb, 0.003125, 0.0], # 3.125%\n", + " [1.0-0.46+hb, 0.003125, 0.0], # 3.125%\n", + " [1.0-0.42-hb, 0.00625, 0.0], # 6.25%\n", + " [1.0-0.42+hb, 0.00625, 0.0], # 6.25%\n", + " [1.0-0.38-hb, 0.0125, 0.0], # 12.5%\n", + " [1.0-0.38+hb, 0.0125, 0.0], # 12.5%\n", + " [1.0-0.3-hb, 0.025, 0.0], # 25%\n", + " [1.0-0.3+hb, 0.025, 0.0], # 25%\n", + " [1.0-0.25-hb, 0.05, 0.0], # 50%\n", + " [1.0-0.25+hb, 0.05, 0.0], # 50%\n", + " [1.0-0.2-hb, 0.1, 0.0], # 100%\n", + " [1.0-0.2+hb, 0.1, 0.0], # 100%\n", + " [1.0-0.16-hb, 0.2, 0.0], # 200%\n", + " [1.0-0.16+hb, 0.2, 0.0], # 200%\n", + " [1.0-0.14-hb, 0.3, 0.0], # 300%\n", + " [1.0-0.14+hb, 0.3, 0.0], # 300%\n", + " [1.0-0.12-hb, 0.4, 0.0], # 400%\n", + " [1.0-0.12+hb, 0.4, 0.0], # 400%\n", + " [1.0-0.1-hb, 0.5, 0.0], # 500%\n", + " [1.0-0.1+hb, 0.5, 0.0], # 500%\n", + " [1.0-0.08-hb, 0.6, 0.0], # 600%\n", + " [1.0-0.08+hb, 0.6, 0.0], # 600%\n", + " [1.0-0.06-hb, 0.7, 0.0], # 700%\n", + " [1.0-0.06+hb, 0.7, 0.0], # 700%\n", + " [1.0-0.04-hb, 0.8, 0.0], # 800%\n", + " [1.0-0.04+hb, 0.8, 0.0], # 800%\n", + " [1.0-0.02-hb, 0.9, 0.0], # 900%\n", + " [1.0-0.02+hb, 0.9, 0.0], # 900%\n", + " [1.0-0.0-hb, 1.0, 0.0], # 1000%\n", + " [1.0-0.0, 1.0, 0.0], # 1000%\n", + "]\n", "\n", "# Calculate curves for points of curvature\n", "def make_lines(lines_orig, resolution=20):\n", diff --git a/code/daisy-looper/daisy-looper.ino b/code/daisy-looper/daisy-looper.ino index 3d48e15..ab07cfa 100644 --- a/code/daisy-looper/daisy-looper.ino +++ b/code/daisy-looper/daisy-looper.ino @@ -29,7 +29,6 @@ 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; -DelayLine<float, 24000> delayline; DSY_SDRAM_BSS ReverbSc reverb; static Compressor compressor; Oscillator lfo; @@ -75,29 +74,34 @@ DaisyHardware hw; size_t num_channels; float blocksize; float drywetmix = 0.0f; -float delaymix = 0.0f; -float delaytime = 100.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 pressure = 0.0f; // Actual audio-processing is orchestrated here void AudioCallback(float **in, float **out, size_t size) { float output = 0.0f; - float no_delay = 0.0f; - float wet_delay; float out1, out2; - // set the delay - delayline.SetDelay(delaytime); + 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++) { uint8_t trig = tick.Process(); float lfo_value = lfo.Process(); float noise_value = noise.Process(); + float rand = easer.Process( sample_and_hold.Process( trig, @@ -110,25 +114,23 @@ void AudioCallback(float **in, float **out, size_t size) { // When the metro ticks, trigger the envelope to start. float random_amount = (lfo_amount -0.5f) * 2.0; if (trig) { - // tick.SetFreq(rand / (0.1 + random_amount*1000.0f) + 1); - // If the dial is over 50% jump instead - if (lfo_amount > 0.5f) { - ui.activeLooper()->addToPlayhead(rand * random_amount* 48000.0f); + // Random LFO + if (lfo_kind == 1) { + ui.activeLooper()->addToPlayhead(rand * random_amount * 48000.0f); ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset); } } - // Add the LFO to the - if (lfo_amount > 0.5f) { - ui.activeLooper()->setPlaybackSpeed(pitch_val + midi_pitch_offset); - } else { + // 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); } + float looper_out; - // res.SetDamping(0.1+delaymix*0.2f); - // Record into the buffer if (ui.rec_source == REC_SOURCE_PRE) { ui.activeLooper()->Record(in[1][i]); } @@ -170,17 +172,10 @@ void AudioCallback(float **in, float **out, size_t size) { } // looper_out = pressure * looper_out; + looper_out = volume * looper_out; // Mix the dry/Wet of the looper output = drywetmix * looper_out + in[1][i] * (1.0f - drywetmix); - - // output = output * (1.0f - resmix) + res.Process(output*resmix); - no_delay = output; - wet_delay = delayline.Read(); - delayline.Write((wet_delay * delaymix * 0.95f)/2.0f + (no_delay*delaymix)/2.0f); - - // Add the delay - output += wet_delay * delaymix; // Compress the signal compressor.Process(output); @@ -277,8 +272,8 @@ void setup() { // Initialize Reverb reverb.Init(sample_rate); - reverb.SetFeedback(0.95f); - reverb.SetLpFreq(18000.0f); + reverb.SetFeedback(reverb_decay); + reverb.SetLpFreq(reverb_tone); // Initialize Compressor compressor.SetThreshold(-64.0f); @@ -290,11 +285,7 @@ void setup() { lfo.Init(sample_rate); lfo.SetWaveform(Oscillator::WAVE_TRI); lfo.SetAmp(1); - lfo.SetFreq(8.0); - - // Initialize the Delay at a length of 1 second - delayline.Init(); - delayline.SetDelay(sample_rate); + lfo.SetFreq(lfo_speed); // Easer for the random jumps easer.setFactor(0.001); @@ -326,7 +317,7 @@ void setup() { pot_3.setDisplayMode("Pitch", 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("Delay", 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 @@ -376,11 +367,29 @@ void loop() { // Set other parameters (from 0.0 to 1.0) if (!isnan(p4)) { drywetmix = p4; } - if (!isnan(p5)) { delaymix = p5; } - // Delaytime is in samples - if (!isnan(p5)) { lfo_amount = p5; } - if (!isnan(p6)) { delaytime = 100.0f + p6 * 23900.0f; } - if (!isnan(p7)) { reverbmix = p7; } + + 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; } + 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(); diff --git a/code/daisy-looper/looper.h b/code/daisy-looper/looper.h index 1914c7f..9524360 100644 --- a/code/daisy-looper/looper.h +++ b/code/daisy-looper/looper.h @@ -51,13 +51,15 @@ class Head { void update(); float read(); float increment = 1.0f; + float variation = 0.0f; + float variation_amount = 0.0f; private: bool active = true; float position = 0.0f; }; Head::Head() { - + variation = random(); } void Head::activate() { this->active = true; @@ -78,7 +80,7 @@ void Head::incrementBy(float value) { this->position += value; } void Head::update() { - this->position += this->increment; + this->position += this->increment + (variation * variation_amount); } float Head::read() { return this->position; @@ -108,6 +110,7 @@ class Looper { void setRecStartMode(RecStartMode mode); float GetPlayhead(); float* GetPlayheads(); + uint8_t GetPlayheadCount(); float GetRecHead(); bool toggleRecMode(); void setRecModeFull(); @@ -118,6 +121,9 @@ class Looper { void addToPlayhead(float value); float loop_start_f = 0.0f; float loop_length_f = 1.0f; + uint8_t grain_count = 9; + float grain_spread = 1.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; @@ -296,7 +302,7 @@ float Looper::Process() { double mix = 0.0; - for (size_t i=0; i<9; i++) { + for (size_t i=0; i<grain_count-1; i++) { // Skip inactive playheads if (!playheads[i].isActive()) continue; @@ -327,13 +333,14 @@ 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); - Serial.print(playhead_positions[i]); - Serial.print(" "); } - Serial.println(""); return playhead_positions; } +uint8_t Looper::GetPlayheadCount() { + return grain_count; +} + float Looper::GetRecHead() { return float(rec_head.read()) / float(buffer_length); } @@ -371,7 +378,8 @@ void Looper::setPlaybackSpeed(float increment) { case PLAYBACK_STATE_MULTILOOP: playheads[0].setIncrement(increment); for (size_t i=1; i<9; i++) { - playheads[i].setIncrement(increment + increment/(1+i)); + playheads[i].variation_amount = grain_variation; + playheads[i].setIncrement(increment + increment*grain_spread); } break; } diff --git a/code/daisy-looper/luts.h b/code/daisy-looper/luts.h index a549f51..a1d2684 100644 --- a/code/daisy-looper/luts.h +++ b/code/daisy-looper/luts.h @@ -8,9 +8,10 @@ float bip_lookup[] = {0.0, 0.08060869565217388, 0.1597391304347826, 0.2388695652 size_t bip_lookup_length = 16; // Lookup Table for Pitch Knob -float pitch_knob_lookup_x[] = {0.0, 0.005, 0.04499999999999999, 0.054999999999999986, 0.09499999999999997, 0.10499999999999998, 0.14500000000000002, 0.15500000000000003, 0.195, 0.20500000000000002, 0.245, 0.255, 0.295, 0.305, 0.345, 0.355, 0.395, 0.405, 0.445, 0.455, 0.495, 0.505, 0.545, 0.555, 0.595, 0.605, 0.645, 0.655, 0.695, 0.705, 0.745, 0.755, 0.795, 0.805, 0.845, 0.855, 0.895, 0.905, 0.945, 0.955, 0.995, 1.005}; -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.0, 0.0, 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 = 42; +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; + // 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}; diff --git a/code/daisy-looper/potentiometers.h b/code/daisy-looper/potentiometers.h index 2ad20ca..0af8ca4 100644 --- a/code/daisy-looper/potentiometers.h +++ b/code/daisy-looper/potentiometers.h @@ -15,16 +15,17 @@ 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 4 +#define POT_MOVING_AVERAGE_SIZE 2 // Length of the Textbuffer for floats in the UI -#define UI_TEXTBUFFER_LENGTH 8 +#define UI_TEXTBUFFER_LENGTH 6 // Modes enum PotMode { POT_MODE_LIN, POT_MODE_BIP, POT_MODE_PITCH, + POT_MODE_SWITCH, POT_MODE_LAST }; @@ -33,6 +34,8 @@ 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 }; @@ -54,6 +57,7 @@ class Potentiometer { void setLinear(); void setBipolar(); void setPitch(); + void setSwitch(); float read(); void setOnChange(callback_function f); void renderUi(); @@ -62,6 +66,10 @@ class Potentiometer { 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[2] = {"LFO", "RANDOM"}; }; Potentiometer::Potentiometer(int pin) { @@ -81,11 +89,13 @@ void Potentiometer::setBipolar() { this->mode = POT_MODE_BIP; } - void Potentiometer::setPitch() { this->mode = POT_MODE_PITCH; } +void Potentiometer::setSwitch() { + this->mode = POT_MODE_SWITCH; +} float Potentiometer::read() { int reading = analogRead(this->pin); @@ -119,9 +129,12 @@ float Potentiometer::read() { 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.001; + 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) { @@ -133,10 +146,14 @@ float Potentiometer::read() { } if (this->last_normalized_reading && !changed) { - // Serial.print(this->name); - // Serial.println(" returned NaN"); + // 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; @@ -229,7 +246,14 @@ void Potentiometer::renderUi() { } // Render that new text - centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE); + 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 diff --git a/code/daisy-looper/ui.h b/code/daisy-looper/ui.h index 288812c..b389749 100644 --- a/code/daisy-looper/ui.h +++ b/code/daisy-looper/ui.h @@ -21,7 +21,7 @@ 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 = true; +bool show_splash = false; // Represents the possible states of the UI enum UiMode { @@ -86,6 +86,16 @@ enum BufferSummingMode { BUFFER_SUM_MODE_LAST, }; +enum FXMode { + FX_MODE_ALL, + FX_MODE_REVERB, + FX_MODE_NONE, + FX_MODE_LFO, + FX_MODE_GRAIN, + FX_MODE_EMPTY, + 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 @@ -101,10 +111,10 @@ class Ui { 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"), }), ButtonGrid((int) UI_MODE_PLAY_MENU, { - GridButton(" ", &button_1, false), + 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("STOP\nLOOP\nMULTI\nMIDI", &button_4, false, BUTTON_TYPE_MULTITOGGLE, 1), + GridButton(" ", &button_4, false), GridButton(" ", &button_5, false), GridButton(" ", &button_6, false), }), @@ -117,12 +127,12 @@ class Ui { GridButton("AUTO", &button_6, false), }), ButtonGrid((int) UI_MODE_FX_MENU, { - GridButton("DELAY", &button_1, false), - GridButton("REVERB", &button_2, false), - GridButton("-", &button_3, false), - GridButton("-", &button_4, false), - GridButton("-", &button_5, false), - GridButton("FX\nMENU", &button_6, true), + 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), @@ -146,37 +156,13 @@ class Ui { // Default Recording Source RecSource rec_source = REC_SOURCE_PRE; + // Default active buffer ActiveBuffer active_buffer = ACTIVE_BUFFER_A; + // Default active summing mode BufferSummingMode buffer_summing_mode = BUFFER_SUM_MODE_SOLO; - // 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; - } - } + FXMode fx_mode = FX_MODE_ALL; // Render the UI // Except for the splash screen this is expected to be called @@ -202,7 +188,7 @@ class Ui { renderGrid(2); break; case UI_MODE_FX_MENU: - renderGrid(3); + renderGrid(3, fx_mode); break; case UI_MODE_BUFFER_MENU: renderGrid(4, active_buffer); @@ -216,7 +202,6 @@ class Ui { void renderGrid(size_t num, int button_enum=0) { display.clearDisplay(); button_grids[num].render(button_enum); - // Display all that stuff and store the time of the last render display.display(); } @@ -230,24 +215,9 @@ class Ui { 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 < 10) { - centeredText("I", display.width()/2, display.height()/2-4, SH110X_WHITE); - centeredText("O", display.width()/2, display.height()/2+4, SH110X_WHITE); - } else if (i < 20) { - centeredText("I S", display.width()/2, display.height()/2-4, SH110X_WHITE); - centeredText("O P", display.width()/2, display.height()/2+4, SH110X_WHITE); - } else if (i < 40) { - centeredText("A I S", display.width()/2, display.height()/2-4, SH110X_WHITE); - centeredText("O O P", display.width()/2, display.height()/2+4, SH110X_WHITE); - } else if (i < 50) { - centeredText("A I S Y", display.width()/2, display.height()/2-4, SH110X_WHITE); - centeredText("O O P E", display.width()/2, display.height()/2+4, SH110X_WHITE); - } else if (i < 60) { - centeredText("D A I S Y", display.width()/2, display.height()/2-4, SH110X_WHITE); - centeredText("L O O P E", display.width()/2, display.height()/2+4, SH110X_WHITE); - } else { - centeredText("D A I S Y Y", display.width()/2, display.height()/2-4, SH110X_WHITE); - centeredText("L O O P E R", display.width()/2, display.height()/2+4, SH110X_WHITE); + 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(); @@ -415,21 +385,78 @@ class Ui { if (ui_mode == UI_MODE_PLAY_MENU && last_ui_mode != UI_MODE_PLAY_MENU) { int n = 1; - // Show the setting of the current buffer - button_grids[n].grid_buttons_[3].active = (int) activeLooper()->playback_state ; + // 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); + // Chang 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); + }); + + // 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("Delay", 100.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](){ - button_grids[n].grid_buttons_[3].next(); - // 0 is stop so we add one, check looper.h for definition of enum - activeLooper()->playback_state = (atoav::PlaybackState) (button_grids[n].grid_buttons_[3].active); + 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_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("Grain Spread", 100.0f, POT_DISPLAY_MODE_PERCENT); + pot_7.setDisplayMode("Grain Var.", 100.0f, POT_DISPLAY_MODE_PERCENT); }); // Store the last ui mode, for the check on top @@ -472,8 +499,7 @@ class Ui { // 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_TRIGGER_MENU); }); - // button_6.onHold([this](){ this->setMode(UI_MODE_FX_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 @@ -580,8 +606,9 @@ class Ui { case atoav::PLAYBACK_STATE_MULTILOOP: { float* playheads = activeLooper()->GetPlayheads(); + uint8_t count = activeLooper()->GetPlayheadCount(); int x_playhead = 0; - for (size_t i=0; i<9; i++) { + for (size_t i=0; i<count; i++) { x_playhead = int(playheads[i] * display.width()) + x_start_loop; int h = 6 + i*3; display.drawFastVLine(x_playhead, h, 3, SH110X_WHITE); @@ -701,6 +728,35 @@ class Ui { } } + // 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(); @@ -720,6 +776,7 @@ class Ui { case UI_MODE_TRIGGER_MENU: break; case UI_MODE_FX_MENU: + setupFXMenu(); break; case UI_MODE_BUFFER_MENU: setupBufferMenu(); -- GitLab