From 359485df980e2607f7a5b8462ea254386c313855 Mon Sep 17 00:00:00 2001 From: David Huss <dh@atoav.com> Date: Fri, 17 Nov 2023 11:51:14 +0100 Subject: [PATCH] Add snapping to Pitch Knob --- circuitsim/lookup-tables.ipynb | 179 +++++++++++++++++++++++++---- code/daisy-looper/daisy-looper.ino | 5 +- code/daisy-looper/luts.h | 15 ++- code/daisy-looper/potentiometers.h | 49 +++++--- 4 files changed, 209 insertions(+), 39 deletions(-) diff --git a/circuitsim/lookup-tables.ipynb b/circuitsim/lookup-tables.ipynb index 24ef2a6..11b6c68 100644 --- a/circuitsim/lookup-tables.ipynb +++ b/circuitsim/lookup-tables.ipynb @@ -480,19 +480,45 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "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": "570ed182aec64e329e88b59e79dc01a7", + "model_id": "0b41499f138548fba229b31a1166839c", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.2, description='f', max=1.02, step=0.001), Output()), _dom_classes=(…" + "interactive(children=(FloatSlider(value=0.2, description='f', max=1.005, step=0.001), Output()), _dom_classes=…" ] }, "metadata": {}, @@ -501,7 +527,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "7affea6bb3c84216bd6403065ae6415d", + "model_id": "4c45104815034f7b8d2a664c7c7e97f6", "version_major": 2, "version_minor": 0 }, @@ -511,16 +537,6 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "42" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -598,7 +614,7 @@ " [1.0, 1.0, 0.0],\n", "]\n", "\n", - "half_deadband = 0.02\n", + "half_deadband = 0.005\n", "\n", "lines_orig = [\n", " [0.0, -1.0, 0.0],\n", @@ -608,6 +624,7 @@ "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", @@ -645,10 +662,13 @@ "# draw(x, y, 0.45/2)\n", "\n", "interact(draw, f=FloatSlider(min=min(x), max=max(x), step=0.001, value=0.2))\n", + "\n", + "length = len(x)\n", "text = \"\"\n", "text += \"// Lookup Table for Pitch Knob\\n\"\n", - "text += f\"float pitch_knob_x[] = {{{', '.join(str(xv) for xv in x)}}};\\n\\n\"\n", - "text += f\"float pitch_knob_y[] = {{{', '.join(str(yv) for yv in y)}}};\\n\\n\"\n", + "text += f\"float pitch_knob_lookup_x[] = {{{', '.join(str(xv) for xv in x)}}};\\n\"\n", + "text += f\"float pitch_knob_lookup_y[] = {{{', '.join(str(yv) for yv in y)}}};\\n\"\n", + "text += f\"size_t pitch_knob_lookup_length = {length};\\n\"\n", "\n", "import ipywidgets as widgets\n", "from IPython.display import display, HTML, Javascript\n", @@ -661,8 +681,7 @@ "mybtn.on_click(mybtn_event_handler)\n", "\n", "display(mybtn)\n", - "\n", - "len(x)" + "\n" ] }, { @@ -769,9 +788,129 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "59b56dbc-6852-4989-bc19-3525ee7caf8b", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0 0.0\n", + "0.01 0.01\n", + "0.02 0.02\n", + "0.03 0.03\n", + "0.04 0.04\n", + "0.05 0.05\n", + "0.06 0.06\n", + "0.07 0.07\n", + "0.08 0.08\n", + "0.09 0.09\n", + "0.1 0.0\n", + "0.11 0.009999999999999995\n", + "0.12 0.01999999999999999\n", + "0.13 0.03\n", + "0.14 0.04000000000000001\n", + "0.15 0.04999999999999999\n", + "0.16 0.06\n", + "0.17 0.07\n", + "0.18 0.07999999999999999\n", + "0.19 0.09\n", + "0.2 0.0\n", + "0.21 0.009999999999999981\n", + "0.22 0.01999999999999999\n", + "0.23 0.03\n", + "0.24 0.03999999999999998\n", + "0.25 0.04999999999999999\n", + "0.26 0.06\n", + "0.27 0.07\n", + "0.28 0.08000000000000002\n", + "0.29 0.08999999999999997\n", + "0.3 0.09999999999999998\n", + "0.31 0.009999999999999981\n", + "0.32 0.01999999999999999\n", + "0.33 0.03\n", + "0.34 0.04000000000000001\n", + "0.35 0.04999999999999996\n", + "0.36 0.05999999999999997\n", + "0.37 0.06999999999999998\n", + "0.38 0.07999999999999999\n", + "0.39 0.09\n", + "0.4 0.0\n", + "0.41 0.009999999999999953\n", + "0.42 0.019999999999999962\n", + "0.43 0.02999999999999997\n", + "0.44 0.03999999999999998\n", + "0.45 0.04999999999999999\n", + "0.46 0.06\n", + "0.47 0.06999999999999995\n", + "0.48 0.07999999999999996\n", + "0.49 0.08999999999999997\n", + "0.5 0.09999999999999998\n", + "0.51 0.009999999999999981\n", + "0.52 0.01999999999999999\n", + "0.53 0.03\n", + "0.54 0.04000000000000001\n", + "0.55 0.05000000000000002\n", + "0.56 0.060000000000000026\n", + "0.57 0.06999999999999992\n", + "0.58 0.07999999999999993\n", + "0.59 0.08999999999999994\n", + "0.6 0.09999999999999995\n", + "0.61 0.009999999999999953\n", + "0.62 0.019999999999999962\n", + "0.63 0.02999999999999997\n", + "0.64 0.03999999999999998\n", + "0.65 0.04999999999999999\n", + "0.66 0.06\n", + "0.67 0.07\n", + "0.68 0.08000000000000002\n", + "0.69 0.08999999999999991\n", + "0.7 0.09999999999999992\n", + "0.71 0.009999999999999926\n", + "0.72 0.019999999999999934\n", + "0.73 0.029999999999999943\n", + "0.74 0.03999999999999995\n", + "0.75 0.04999999999999996\n", + "0.76 0.05999999999999997\n", + "0.77 0.06999999999999998\n", + "0.78 0.07999999999999999\n", + "0.79 0.09\n", + "0.8 0.0\n", + "0.81 0.010000000000000009\n", + "0.82 0.019999999999999907\n", + "0.83 0.029999999999999916\n", + "0.84 0.039999999999999925\n", + "0.85 0.04999999999999993\n", + "0.86 0.05999999999999994\n", + "0.87 0.06999999999999995\n", + "0.88 0.07999999999999996\n", + "0.89 0.08999999999999997\n", + "0.9 0.09999999999999998\n", + "0.91 0.009999999999999981\n", + "0.92 0.01999999999999999\n", + "0.93 0.03\n", + "0.94 0.0399999999999999\n", + "0.95 0.049999999999999906\n", + "0.96 0.059999999999999915\n", + "0.97 0.06999999999999992\n", + "0.98 0.07999999999999993\n", + "0.99 0.08999999999999994\n", + "1.0 0.09999999999999995\n" + ] + } + ], + "source": [ + "for i in range(0, 101):\n", + " x = i/100\n", + " print(x, x%0.1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a511d24-cc91-450f-83e4-8295647d9391", + "metadata": {}, "outputs": [], "source": [] } diff --git a/code/daisy-looper/daisy-looper.ino b/code/daisy-looper/daisy-looper.ino index 49250d5..9c0bc28 100644 --- a/code/daisy-looper/daisy-looper.ino +++ b/code/daisy-looper/daisy-looper.ino @@ -241,7 +241,7 @@ void setup() { // Set the analog read and write resolution to 12 bits analogReadResolution(12); - // + // Setup MIDI handlers MIDI.setHandleNoteOn(handleNoteOn); MIDI.setHandleNoteOff(handleNoteOff); MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages @@ -256,7 +256,8 @@ void setup() { pot_7.setDisplayMode("LFO", 100.0f, POT_DISPLAY_MODE_PERCENT); // Set Knob Scaling Modes - pot_3.setBipolar(); + // pot_3.setBipolar(); + pot_3.setPitch(); // Initialize Buttons (callbacks are assigned in the Ui class) button_1.init(); diff --git a/code/daisy-looper/luts.h b/code/daisy-looper/luts.h index 36c4d16..5becd33 100644 --- a/code/daisy-looper/luts.h +++ b/code/daisy-looper/luts.h @@ -1,12 +1,20 @@ #ifndef LUTs_h #define LUTs_h +#include "MultiMap.h" + // Lookup Table for Exponential Curve float exp_lookup[] = {0.0, 0.02040816326530612, 0.08163265306122448, 0.18367346938775508, 0.32653061224489793, 0.5102040816326531, 0.7346938775510203, 1.0}; +size_t exp_lookup_length = 8; // Lookup Table for Bipolar Curve with deadband float bip_lookup[] = {0.0, 0.08060869565217388, 0.1597391304347826, 0.23886956521739133, 0.318, 0.3971304347826087, 0.47626086956521746, 0.5, 0.5, 0.5237391304347826, 0.6028695652173913, 0.6819999999999999, 0.7611304347826088, 0.8402608695652175, 0.9193913043478261, 1.0}; +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; @@ -21,9 +29,8 @@ float lerp(float a, float b, float f) { else { return a * (1.0f-f) + b * f; } } -float get_from_table(float* table, float f) { +float get_from_table(float* table, float f, size_t length) { f = min(1.0f, max(0.0f, f)); - int length = sizeof(table)*sizeof(float); float pos = (length-1) * f; float pos_frac = fmod(pos, 1.0f); if (pos_frac == 0.0f) { @@ -35,4 +42,8 @@ float get_from_table(float* table, float f) { return lerp(a, b, pos_frac); } +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/potentiometers.h b/code/daisy-looper/potentiometers.h index a68e01b..07a4d99 100644 --- a/code/daisy-looper/potentiometers.h +++ b/code/daisy-looper/potentiometers.h @@ -24,6 +24,7 @@ enum PotMode { POT_MODE_LIN, POT_MODE_EXP, POT_MODE_BIP, + POT_MODE_PITCH, POT_MODE_LAST }; @@ -52,6 +53,7 @@ class Potentiometer { void setLinear(); void setExponential(); void setBipolar(); + void setPitch(); float read(); void setOnChange(callback_function f); void renderUi(); @@ -82,6 +84,12 @@ void Potentiometer::setBipolar() { this->mode = POT_MODE_BIP; } + +void Potentiometer::setPitch() { + this->mode = POT_MODE_PITCH; +} + + float Potentiometer::read() { int reading = analogRead(this->pin); // Shift all readings in the buffer over by one position, deleting the oldest @@ -103,27 +111,30 @@ float Potentiometer::read() { // Convert the last reading to a float and return float current_reading = reading / 4096.0f; + float normalized_reading = current_reading; // Depending on the Mode - if (this->mode == POT_MODE_EXP ) { - - current_reading = get_from_table(exp_lookup, current_reading); - } - if (this->mode == POT_MODE_BIP) { - current_reading = get_from_table(bip_lookup, current_reading); - - // Bipolar Knobs go from -1.0 to +1.0 (Centered = 0.0) - current_reading = (current_reading - 0.5f) * 2.0f; + switch (this->mode) { + case POT_MODE_EXP: + current_reading = get_from_table(exp_lookup, current_reading, exp_lookup_length); + break; + case POT_MODE_BIP: + current_reading = get_from_table(bip_lookup, current_reading, bip_lookup_length); + current_reading = (current_reading - 0.5f) * 2.0f; + break; + 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; } // If the difference to the last reading is big enough assume the knob has been touched if (this->last_reading && abs(current_reading - this->last_reading) > 0.002) { - // Serial.print("Touched Potentiometer "); - // Serial.print(this->name); - // Serial.print(": "); - // Serial.print(input); - // Serial.print(" --> "); - // Serial.println(current_reading); + Serial.print("Touched Potentiometer "); + Serial.print(this->name); + Serial.print(": "); + Serial.print(normalized_reading); + Serial.print(" --> "); + Serial.println(current_reading); if (display_value_changes) { last_displayed = millis(); should_display = true; @@ -173,6 +184,14 @@ void Potentiometer::renderUi() { 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; -- GitLab