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