From bf9d86e9dac2e783c2529888814ec89b23d22832 Mon Sep 17 00:00:00 2001
From: David Huss <dh@atoav.com>
Date: Sat, 11 Nov 2023 15:05:09 +0100
Subject: [PATCH] Refactor

---
 .../default-37a8.jupyterlab-workspace         |   2 +-
 circuitsim/envelope.ipynb                     |  35 ++-
 circuitsim/lookup-tables.ipynb                | 161 ++++++++++-
 code/daisy-looper/daisy-looper.ino            |  83 +++---
 code/daisy-looper/looper.h                    | 112 ++++----
 code/daisy-looper/ui.h                        | 255 ++++++++++++------
 menu.ods                                      | Bin 0 -> 18938 bytes
 7 files changed, 459 insertions(+), 189 deletions(-)
 create mode 100644 menu.ods

diff --git a/circuitsim/.jupyter/desktop-workspaces/default-37a8.jupyterlab-workspace b/circuitsim/.jupyter/desktop-workspaces/default-37a8.jupyterlab-workspace
index 04bc81b..79abff0 100644
--- a/circuitsim/.jupyter/desktop-workspaces/default-37a8.jupyterlab-workspace
+++ b/circuitsim/.jupyter/desktop-workspaces/default-37a8.jupyterlab-workspace
@@ -1 +1 @@
-{"data":{"layout-restorer:data":{"main":{"dock":{"type":"tab-area","currentIndex":2,"widgets":["notebook:circuit_sim.ipynb","notebook:lookup-tables.ipynb","notebook:envelope.ipynb"]},"current":"notebook:envelope.ipynb"},"down":{"size":0,"widgets":[]},"left":{"collapsed":false,"current":"filebrowser","widgets":["filebrowser","running-sessions","@jupyterlab/toc:plugin","extensionmanager.main-view"]},"right":{"collapsed":true,"widgets":["jp-property-inspector","debugger-sidebar"]},"relativeSizes":[0.26227795193312436,0.7377220480668757,0]},"notebook:circuit_sim.ipynb":{"data":{"path":"circuit_sim.ipynb","factory":"Notebook"}},"notebook:lookup-tables.ipynb":{"data":{"path":"lookup-tables.ipynb","factory":"Notebook"}},"notebook:envelope.ipynb":{"data":{"path":"envelope.ipynb","factory":"Notebook"}}},"metadata":{"id":"default"}}
\ No newline at end of file
+{"data":{"layout-restorer:data":{"main":{"dock":{"type":"tab-area","currentIndex":1,"widgets":["notebook:circuit_sim.ipynb","notebook:lookup-tables.ipynb","notebook:envelope.ipynb"]},"current":"notebook:lookup-tables.ipynb"},"down":{"size":0,"widgets":[]},"left":{"collapsed":false,"current":"filebrowser","widgets":["filebrowser","running-sessions","@jupyterlab/toc:plugin","extensionmanager.main-view"]},"right":{"collapsed":true,"widgets":["jp-property-inspector","debugger-sidebar"]},"relativeSizes":[0.13545601726929304,0.864543982730707,0]},"notebook:circuit_sim.ipynb":{"data":{"path":"circuit_sim.ipynb","factory":"Notebook"}},"notebook:lookup-tables.ipynb":{"data":{"path":"lookup-tables.ipynb","factory":"Notebook"}},"notebook:envelope.ipynb":{"data":{"path":"envelope.ipynb","factory":"Notebook"}}},"metadata":{"id":"default"}}
\ No newline at end of file
diff --git a/circuitsim/envelope.ipynb b/circuitsim/envelope.ipynb
index 24c5b4f..7bec7a5 100644
--- a/circuitsim/envelope.ipynb
+++ b/circuitsim/envelope.ipynb
@@ -258,9 +258,42 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 121,
    "id": "2e2c2c7f-4bc6-4a1f-b6bd-bf5558b8f03f",
    "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "947364e8d6aa4d1ba675e14ddf866148",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "interactive(children=(FloatSlider(value=0.2, description='f', max=1.0, step=0.001), Output()), _dom_classes=('…"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<function __main__.draw(f)>"
+      ]
+     },
+     "execution_count": 121,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": []
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6112aebb-dc20-45e3-9ca6-280c76523469",
+   "metadata": {},
    "outputs": [],
    "source": []
   }
diff --git a/circuitsim/lookup-tables.ipynb b/circuitsim/lookup-tables.ipynb
index 0a086e4..d3401e0 100644
--- a/circuitsim/lookup-tables.ipynb
+++ b/circuitsim/lookup-tables.ipynb
@@ -480,9 +480,168 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 43,
    "id": "e51416f3-f34d-4513-9f0c-fa52c468274e",
    "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "880451c044ba49d5a1686f3db7453888",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "interactive(children=(FloatSlider(value=0.2, description='f', max=10.1, min=-10.0, step=0.001), Output()), _do…"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/plain": [
+       "<function __main__.draw(f)>"
+      ]
+     },
+     "execution_count": 43,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from matplotlib import pyplot as plt\n",
+    "%matplotlib inline\n",
+    "from ipywidgets import interact, FloatSlider\n",
+    "import matplotlib.transforms as transforms\n",
+    "import math\n",
+    "\n",
+    "def draw(f):\n",
+    "    fig = plt.figure(figsize=(8, 4))\n",
+    "    ax = fig.add_axes([0, 0, 1, 1])\n",
+    "    b = scan2d(x, y, f)\n",
+    "    ax.axhline(y=b, color='red', linestyle='--')\n",
+    "    ax.axvline(x=f, color='red', linestyle='--')\n",
+    "    trans = transforms.blended_transform_factory(\n",
+    "    ax.get_yticklabels()[0].get_transform(), ax.transData)\n",
+    "    ax.text(0.95, b, \"{:.02f}\".format(b), color=\"red\", transform=trans, ha=\"right\", va=\"bottom\")\n",
+    "    ax.grid()\n",
+    "    ax.plot(x, y)\n",
+    "\n",
+    "def lerp(a, b, f=0.5) -> float:\n",
+    "    f = min(1.0, max(0.0, f))\n",
+    "    if f == 0.0:\n",
+    "        return a\n",
+    "    elif f == 1.0:\n",
+    "        return b\n",
+    "    else:\n",
+    "        return a * (1.0-f) + b * f\n",
+    "\n",
+    "def lerp2d(x1, y1, x2, y2, f=0.5):\n",
+    "    if f == 0.0:\n",
+    "        return [x1, x2]\n",
+    "    elif f == 1.0:\n",
+    "        return [x1, x2]\n",
+    "    else:\n",
+    "        x = lerp(x1, x2, f)\n",
+    "        y = lerp(y1, y2, f)\n",
+    "        return [x, y]\n",
+    "\n",
+    "def scan2d(x, y, f):\n",
+    "        # f = min(1.0, max(0.0, f))\n",
+    "        assert len(x) == len(y)\n",
+    "        # Find ax and bx for given factor\n",
+    "        xa = None\n",
+    "        last_value = None\n",
+    "        idx = None\n",
+    "        for i, v in enumerate(x):\n",
+    "            # this = abs(f-v)\n",
+    "            this = f-v\n",
+    "            if xa is None or this > 0:\n",
+    "                xa = this\n",
+    "                idx = i\n",
+    "        idx2 = min(idx+1, len(x)-1)\n",
+    "        if idx == idx2:\n",
+    "            return y[idx]\n",
+    "        xa = x[idx]\n",
+    "        xb = x[idx2]\n",
+    "        ya = y[idx]\n",
+    "        yb = y[idx2]\n",
+    "        xspan = xb-xa\n",
+    "        xscaler = 1/xspan\n",
+    "        new_f = (f-xa)*xscaler\n",
+    "        # print(f\"xa      {xa} [{idx}]\")\n",
+    "        # print(f\"xb      {xb} [{idx2}]\")\n",
+    "        # print(f\"ya      {ya} [{idx}]\")\n",
+    "        # print(f\"yb      {yb} [{idx2}]\")\n",
+    "        # print(f\"xspan   {xspan} [{xb} - {xa}]\")\n",
+    "        # print(f\"xscaler {xscaler} [1/{xspan}]\")\n",
+    "        # print(f\"new_f   {new_f} [({f}-{xa})/{xscaler}]\")\n",
+    "        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.1\n",
+    "\n",
+    "lines_orig = [\n",
+    "    [-10.0, -10.0, 0.0],\n",
+    "    [-10.0+half_deadband, -10.0, 0.0],\n",
+    "] \n",
+    "\n",
+    "steps = list(range(-9, 11))\n",
+    "for i in steps:\n",
+    "    f = float(i)\n",
+    "    lines_orig.append([f-half_deadband, f, 0.0])\n",
+    "    lines_orig.append([f+half_deadband, f, 0.0])\n",
+    "\n",
+    "\n",
+    "lines = []\n",
+    "for i, l in enumerate(lines_orig):\n",
+    "    i2 = min(len(lines_orig)-1, i+1)\n",
+    "    if l[2] == 0.0:\n",
+    "        lines.append(l)\n",
+    "    else:\n",
+    "        xa = lines_orig[i][0]\n",
+    "        xb = lines_orig[i2][0]\n",
+    "        ya = lines_orig[i][1]\n",
+    "        yb = lines_orig[i2][1]\n",
+    "        x_span = xb-xa\n",
+    "        y_span = yb-ya\n",
+    "        x_step = 1/20\n",
+    "        y_step = 1/20\n",
+    "        for j in range(20):\n",
+    "            x = x_step * j\n",
+    "            y = y_step * j\n",
+    "            y_curve = 0\n",
+    "            if l[2] > 0.0:\n",
+    "                y_curve = y*y*y\n",
+    "            else:\n",
+    "                y_curve = y*y\n",
+    "            y = (1.0-l[2]) * y + l[2] * y_curve\n",
+    "            lines.append([xa+x*x_span, ya+y*y_span, 0.0])\n",
+    "\n",
+    "\n",
+    "x = [a[0] for a in lines]\n",
+    "y = [a[1] for a in lines]\n",
+    "c = [a[2] for a in lines]\n",
+    "# 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",
+    "    "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f35f1609-3a10-4dce-b7dd-201d79f2c39c",
+   "metadata": {},
    "outputs": [],
    "source": []
   }
diff --git a/code/daisy-looper/daisy-looper.ino b/code/daisy-looper/daisy-looper.ino
index f69de38..e798e3a 100644
--- a/code/daisy-looper/daisy-looper.ino
+++ b/code/daisy-looper/daisy-looper.ino
@@ -20,13 +20,11 @@
 #include "helpers.h"
 #include "ui.h"
 
-#define UI_FPS 60.0
-
 static const size_t buffer_length = 48000 * 5;
 static float DSY_SDRAM_BSS buffer[buffer_length];
 
+// Create instances of audio stuff
 atoav::Looper looper;
-static PitchShifter pitch_shifter;
 static atoav::EnvelopeFollower input_envelope_follower;
 DelayLine<float, 24000> delayline;
 DSY_SDRAM_BSS ReverbSc reverb;
@@ -35,11 +33,8 @@ Oscillator lfo;
 static SampleHold sample_and_hold;
 static WhiteNoise noise;
 static Metro tick;
-// Resonator res;
-
-CpuLoadMeter load;
 
-// Buttons
+// Initialize Buttons
 Button button_1 = Button(D7);
 Button button_2 = Button(D8);
 Button button_3 = Button(D9);
@@ -47,7 +42,7 @@ Button button_4 = Button(D10);
 Button button_5 = Button(D13);
 Button button_6 = Button(D14);
 
-// Make Potentiometers
+// Initialize Potentiometers
 Potentiometer pot_1 = Potentiometer(A0);
 Potentiometer pot_2 = Potentiometer(A1);
 Potentiometer pot_3 = Potentiometer(A3);
@@ -56,7 +51,7 @@ Potentiometer pot_5 = Potentiometer(A4);
 Potentiometer pot_6 = Potentiometer(A5);
 Potentiometer pot_7 = Potentiometer(A6);
 
-// LED                  R    G    B
+// RGB LED               R    G    B
 RGBLed rgb_led = RGBLed(A10, A9, A11);
 
 
@@ -73,22 +68,25 @@ Ui ui;
 DaisyHardware hw;
 
 // Variables for the Audio-Callback
-size_t num_channels;
+size_t num_channels
+;
 float blocksize;
 float drywetmix = 0.0f;
 float delaymix = 0.0f;
 float delaytime = 100.0f;
 float reverbmix = 0.0f;
 float lfo_amount = 0.0f;
-auto pitch_val = 0.5f;
+float pitch_val = 0.5f;
 
 // 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;
-  delayline.SetDelay(delaytime);
   float out1, out2;
+  // set the delay
+  delayline.SetDelay(delaytime);
+  
   for (size_t i = 0; i < size; i++) {
     uint8_t trig = tick.Process();
     float lfo_value = lfo.Process();
@@ -111,9 +109,10 @@ void AudioCallback(float **in, float **out, size_t size) {
     
     // res.SetDamping(0.1+delaymix*0.2f);
     auto looper_out = looper.Process(in[1][i]);
-    // FIXME:
+
+    // 
     input_envelope_follower.Process(in[1][i]);
-    output = drywetmix * pitch_shifter.Process(looper_out) + in[1][i] * (1.0f - drywetmix);
+    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();
@@ -132,13 +131,7 @@ void AudioCallback(float **in, float **out, size_t size) {
 }
 
 
-// void activate_rec() {
-//   ui.activateRecording();
-// }
 
-// void activate_overdub() {
-//   ui.activateOverdub();
-// }
 
 void setup() {
   float sample_rate;
@@ -149,36 +142,38 @@ void setup() {
   sample_rate = DAISY.get_samplerate();
   blocksize = 64.0f;
 
+  // Create a Tick and a noise source for the Sample and Hold
   tick.Init(10, sample_rate);
+  noise.Init();
   
+  // Initialize Looper with the buffer
   looper.Init(buffer, 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(0.95f);
   reverb.SetLpFreq(18000.0f);
 
+  // Initialize Compressor
   compressor.SetThreshold(-64.0f);
   compressor.SetRatio(2.0f);
   compressor.SetAttack(0.005f);
   compressor.SetRelease(0.1250);
 
-  pitch_shifter.Init(sample_rate);
-
-  // set parameters for LFO oscillator object
+  // Initialize the LFO for modulations
   lfo.Init(sample_rate);
   lfo.SetWaveform(Oscillator::WAVE_TRI);
   lfo.SetAmp(1);
   lfo.SetFreq(8.0);
 
-  noise.Init();
-
+  // Initialize the Delay at a length of 1 second
   delayline.Init();
-  delayline.SetDelay(48000.0f);
-  // res.Init(.015, 24, sample_rate);
-  // res.SetStructure(-7.f);
+  delayline.SetDelay(samplerate);
 
   load.Init(sample_rate, blocksize);
 
@@ -189,10 +184,8 @@ void setup() {
   display.begin(0x3C, true);
   delay(50);
   ui.Render();
-
-  DAISY.begin(AudioCallback);
   
-
+  // Initialize the LED
   rgb_led.init();
 
   // Set the analog read and write resolution to 12 bits
@@ -210,21 +203,20 @@ void setup() {
   // Set Knob Scaling Modes
   pot_3.setBipolar();
 
-  // Initialize Buttons
+  // 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();
-  // button_1.onPressed(activate_rec);
-  // button_2.onPress(toggleRecMode);
-  // button_3.onPressed(activate_overdub);
-  // button_4.onPressed(display_fx_menu);
+
+  // Start the audio Callback
+  DAISY.begin(AudioCallback);
 }
 
 void loop() {
-  // hw.ProcessAllControls();
+  // Read the values from the potentiometers
   float p1 = pot_1.read();
   float p2 = pot_2.read();
   float p3 = pot_3.read();
@@ -233,10 +225,10 @@ void loop() {
   float p6 = pot_6.read();
   float p7 = pot_7.read();
 
-  // Deactivate recording and overdub before reading buttons
+  // Update the UI
   ui.update();
 
-  // Read buttons
+  // Read the buttons
   button_1.read();
   button_2.read();
   button_3.read();
@@ -244,21 +236,26 @@ void loop() {
   button_5.read();
   button_6.read();
 
-  // Set loop start and loop length
+  // Set loop-start and loop-length with the potentiometers
   ui.setLoop(p1, p2);
   
-  // Value should go 10 octaves up/down (10*1.0 = 1000%)
+  // 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%)
   pitch_val = 10.0f * p3;
-  // res.SetFreq(10.0f + p3 * 1500.0f);
 
-  // Set other parameters
+  // Set other parameters (from 0.0 to 1.0)
   drywetmix = p4;
   delaymix = p5;
+  // Delaytime is in samples
   delaytime = 100.0f + p5 * 23900.0f;
   reverbmix = p6;
   lfo_amount = p7;
 
+  // Render the UI (frame rate limited by UI_MAX_FPS in ui.h)
   ui.Render();
+
+  // Set the Color and brightness of the RGB LED in 8 bits
   rgb_led.setAudioLevelIndicator(int(input_envelope_follower.getValue() * 255));
 }
 
diff --git a/code/daisy-looper/looper.h b/code/daisy-looper/looper.h
index 9624a92..a71079b 100644
--- a/code/daisy-looper/looper.h
+++ b/code/daisy-looper/looper.h
@@ -20,10 +20,10 @@ enum RecStartMode {
 class Looper {
   public:
     void Init(float *buf, size_t length) {
-      _buffer = buf;
-      _buffer_length = length;
+      buffer = buf;
+      buffer_length = length;
       // Reset buffer contents to zero
-      memset(_buffer, 0, sizeof(float) * _buffer_length);
+      memset(buffer, 0, sizeof(float) * buffer_length);
     }
 
     RecPitchMode rec_pitch_mode = REC_PITCH_MODE_NORMAL;
@@ -33,62 +33,62 @@ class Looper {
       this->is_overdub = is_overdub;
       this->is_recording = is_recording || is_overdub;
       //Initialize recording head position on start
-      if (_rec_env_pos_inc <= 0 && is_recording) {
-        // rec_head = (_loop_start + play_head) % _buffer_length;
+      if (rec_env_pos_inc <= 0 && is_recording) {
+        // rec_head = (loop_start + play_head) % buffer_length;
         switch (rec_start_mode) {
           case REC_START_MODE_LOOP:
-            rec_head = (_loop_start) % _buffer_length;
+            rec_head = (loop_start) % buffer_length;
             break;
           case REC_START_MODE_BUFFER:
             rec_head = 0.0f;
             break;
           case REC_START_MODE_PLAYHEAD:
-            rec_head = fmod(_loop_start + play_head, float(_buffer_length));
+            rec_head = fmod(loop_start + play_head, float(buffer_length));
             break;
         }
           
-        _is_empty = false;
+        is_empty = false;
       }
       // When record switch changes state it effectively
       // sets ramp to rising/falling, providing a
       // fade in/out in the beginning and at the end of 
       // the recorded region.
-      _rec_env_pos_inc = is_recording ? 1 : -1;
+      rec_env_pos_inc = is_recording ? 1 : -1;
     }
 
     void SetLoop(const float loop_start, const float loop_length) {
       // Set the start of the next loop
-      _pending_loop_start = static_cast<size_t>(loop_start * (_buffer_length - 1));
+      pending_loop_start = static_cast<size_t>(loop_start * (buffer_length - 1));
 
       // If the current loop start is not set yet, set it too
-      if (!_is_loop_set) _loop_start = _pending_loop_start;
+      if (!is_loop_set) loop_start = pending_loop_start;
 
       // Set the length of the next loop
-      _pending_loop_length = max(kMinLoopLength, static_cast<size_t>(loop_length * _buffer_length));
+      pendingloop_length = max(kMinLoopLength, static_cast<size_t>(loop_length * buffer_length));
 
       // CHECK if this is truly good
-      // _loop_length = _pending_loop_length;
-      // _loop_length = _pending_loop_length;
+      // loop_length = pendingloop_length;
+      // loop_length = pendingloop_length;
 
       //If the current loop length is not set yet, set it too
-      if (!_is_loop_set) _loop_length = _pending_loop_length;
-      _is_loop_set = true;
+      if (!is_loop_set) loop_length = pendingloop_length;
+      is_loop_set = true;
     }
   
     float Process(float in) {
       // Calculate iterator position on the record level ramp.
-      if (_rec_env_pos_inc > 0 && _rec_env_pos < kFadeLength
-       || _rec_env_pos_inc < 0 && _rec_env_pos > 0) {
-          _rec_env_pos += _rec_env_pos_inc;
+      if (rec_env_pos_inc > 0 && rec_env_pos < kFadeLength
+       || rec_env_pos_inc < 0 && rec_env_pos > 0) {
+          rec_env_pos += rec_env_pos_inc;
       }
       // If we're in the middle of the ramp - record to the buffer.
-      if (_rec_env_pos > 0) {
+      if (rec_env_pos > 0) {
         // Calculate fade in/out
-        float rec_attenuation = static_cast<float>(_rec_env_pos) / static_cast<float>(kFadeLength);
+        float rec_attenuation = static_cast<float>(rec_env_pos) / static_cast<float>(kFadeLength);
         if (this->is_overdub) {
-          _buffer[int(rec_head)] += in * rec_attenuation;
+          buffer[int(rec_head)] += in * rec_attenuation;
         } else {
-          _buffer[int(rec_head)] = in * rec_attenuation + _buffer[int(rec_head)] * (1.f - rec_attenuation);
+          buffer[int(rec_head)] = in * rec_attenuation + buffer[int(rec_head)] * (1.f - rec_attenuation);
         }
 
         // Set recording pitch mode
@@ -110,29 +110,29 @@ class Looper {
         if (!stop_after_recording) {
           if (!stay_within_loop) {
             // record into whole buffer
-            rec_head = fmod(rec_head, float(_buffer_length));
+            rec_head = fmod(rec_head, float(buffer_length));
           } else {
             // Limit rec head to stay inside the loop
-            rec_head = fmod(rec_head, float(_loop_start + _loop_length));
-            rec_head = max(float(_loop_start), rec_head);
+            rec_head = fmod(rec_head, float(loop_start + loop_length));
+            rec_head = max(float(loop_start), rec_head);
           }
         } else {
           if (!stay_within_loop) {
-            if (rec_head > _buffer_length) { SetRecording(false, false); }
+            if (rec_head > buffer_length) { SetRecording(false, false); }
           } else {
-            if (rec_head > _loop_start + _loop_length) { SetRecording(false, false); }
+            if (rec_head > loop_start + loop_length) { SetRecording(false, false); }
           }
         }
 
-        if (rec_head > _buffer_length) {
+        if (rec_head > buffer_length) {
           rec_head = 0.0f;
         } else if (rec_head < 0) {
-          rec_head = _buffer_length;
+          rec_head = buffer_length;
         }
         
       }
       
-      if (_is_empty) {
+      if (is_empty) {
         return 0;
       }
 
@@ -144,25 +144,25 @@ class Looper {
       if (play_head < kFadeLength) {
         attenuation = static_cast<float>(play_head) / static_cast<float>(kFadeLength);
       }
-      else if (play_head >= _loop_length - kFadeLength) {
-        attenuation = static_cast<float>(_loop_length - play_head) / static_cast<float>(kFadeLength);
+      else if (play_head >= loop_length - kFadeLength) {
+        attenuation = static_cast<float>(loop_length - play_head) / static_cast<float>(kFadeLength);
       }
       
       // Read from the buffer
-      auto play_pos = int(_loop_start + play_head) % _buffer_length;
-      output = _buffer[play_pos] * attenuation;
+      auto play_pos = int(loop_start + play_head) % buffer_length;
+      output = buffer[play_pos] * attenuation;
 
       // Advance playhead
       play_head += playback_increment;
       // Ensure the playhead stays within bounds
-      if (play_head >= _loop_length) {
-        _loop_start = _pending_loop_start;
-        _loop_length = _pending_loop_length;
+      if (play_head >= loop_length) {
+        loop_start = pending_loop_start;
+        loop_length = pendingloop_length;
         play_head = 0;
       } else if (play_head <= 0) {
-        _loop_start = _pending_loop_start;
-        _loop_length = _pending_loop_length;
-        play_head = _loop_length;
+        loop_start = pending_loop_start;
+        loop_length = pendingloop_length;
+        play_head = loop_length;
       }
       
       
@@ -170,11 +170,11 @@ class Looper {
     }
 
     float GetPlayhead() {
-      return  float(play_head) / float(_buffer_length);
+      return  float(play_head) / float(buffer_length);
     }
 
     float GetRecHead() {
-      return  float(rec_head) / float(_buffer_length);
+      return  float(rec_head) / float(buffer_length);
     }
 
     bool toggleRecMode() {
@@ -216,11 +216,11 @@ class Looper {
     }
 
     float* getBuffer() {
-      return _buffer;
+      return buffer;
     }
 
     size_t getBufferLength() {
-      return _buffer_length;
+      return buffer_length;
     }
 
     bool isRecording() {
@@ -248,26 +248,26 @@ class Looper {
     }
 
   private:
-    static const size_t kFadeLength = 200; //orig: 600
+    static const size_t kFadeLength = 200;
     static const size_t kMinLoopLength = 2 * kFadeLength;
 
-    float* _buffer;
+    float* buffer;
 
-    size_t _buffer_length       = 0;
-    size_t _loop_length         = 0;
-    size_t _pending_loop_length = 0;
-    size_t _loop_start          = 0;
-    size_t _pending_loop_start  = 0;
+    size_t buffer_length       = 0;
+    size_t loop_length         = 0;
+    size_t pendingloop_length = 0;
+    size_t loop_start          = 0;
+    size_t pending_loop_start  = 0;
 
     float play_head = 0.0f;
     float rec_head  = 0.0f;
 
     float playback_increment = 1.0f;
 
-    size_t _rec_env_pos      = 0;
-    int32_t _rec_env_pos_inc = 0;
-    bool _is_empty  = true;
-    bool _is_loop_set = false;
+    size_t rec_env_pos      = 0;
+    int32_t rec_env_pos_inc = 0;
+    bool is_empty  = true;
+    bool is_loop_set = false;
     bool stay_within_loop = false;
     bool is_overdub = false;
     bool is_recording = false;
diff --git a/code/daisy-looper/ui.h b/code/daisy-looper/ui.h
index 2d27771..06134d7 100644
--- a/code/daisy-looper/ui.h
+++ b/code/daisy-looper/ui.h
@@ -18,52 +18,62 @@ extern Button button_1, button_2, button_3, button_4, button_5, button_6;
 extern Adafruit_SH1106G display;
 extern atoav::Looper looper;
 
+// Should the splash-screen be shown on boot?
+bool show_splash = true;
 
-
+// Represents the possible states of the UI
 enum UiMode {
-  UI_MODE_SPLASH,
-  UI_MODE_DEFAULT,
-  UI_MODE_REC_MENU,
-  UI_MODE_PLAY_MENU,
-  UI_MODE_TRIGGER_MENU,
-  UI_MODE_FX_MENU,
+  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_LAST
 };
 
+// Represents possible recording modes
 enum RecMode {
-  REC_MODE_FULL,
-  REC_MODE_LOOP,
-  REC_MODE_FULL_SHOT,
-  REC_MODE_LAST = 6
+  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 playback modes
 enum PlayMode {
-  PLAY_MODE_DRUNK,
-  PLAY_MODE_WINDOW,
-  PLAY_MODE_LOOP,
-  PLAY_MODE_GRAIN,
-  PLAY_MODE_ONESHOT,
+  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,
-  REC_STATE_RECORDING,
-  REC_STATE_OVERDUBBING,
+  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
 };
 
-// Different types of buttons
+// 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" insided)
-  BUTTON_TYPE_MULTITOGGLE,  // Toggles between two or more values
+  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;
     GridButton(const char* name, Button& button, bool is_home=false, ButtonType type=BUTTON_TYPE_SIMPLE, int default_value=0)
@@ -81,11 +91,12 @@ class GridButton {
       }
     }
     Button button;
+    ButtonType type;
     bool is_home;
     int active;
-    ButtonType type;
     int lines = 0;
     
+    // Go to the next option
     void next() {
       active++;
       if (active > lines) {
@@ -94,6 +105,7 @@ class GridButton {
     }
 };
 
+// The ButtonGrid is a grid of 2×3 = 6 buttons
 class ButtonGrid {
   public:
     ButtonGrid(const GridButton (&grid_buttons)[6]) 
@@ -109,10 +121,13 @@ class ButtonGrid {
       // 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;
@@ -140,11 +155,11 @@ class ButtonGrid {
             centeredText(name, xc, yc, text_color);
           }
           
-          // Counter for the number of the button
+          // Increase ounter for the index of the button
           i++;
         }
       }
-      // Draw divider Line
+      // 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);
@@ -152,6 +167,8 @@ class ButtonGrid {
 
 };
 
+// The Ui is _the_ coordinating class for the whole interaction.
+// The default mode
 class Ui {
   public:
     Ui() : button_grids {
@@ -188,11 +205,17 @@ class Ui {
         GridButton("FX\nMENU", button_6, true),
       }),
     } {};
-    UiMode ui_mode = UI_MODE_SPLASH;
+
+    // Store the Button Grids declared above (make sure the lenght matches!)
     ButtonGrid button_grids[4];
+
+    // Stores the current Ui Mode
+    UiMode ui_mode = UI_MODE_SPLASH;
+
     // RecMode rec_mode = REC_MODE_FULL;
     RecMode rec_mode = REC_MODE_LOOP;
 
+    // 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;
@@ -213,6 +236,9 @@ class Ui {
       }
     }
 
+    // Render the UI
+    // Except for the splash screen this is expected to be called
+    // repeatedly in a loop
     void Render() {
       switch (ui_mode) {
         case UI_MODE_SPLASH:
@@ -238,10 +264,10 @@ class Ui {
       }
     }
 
-    // Render button grids
+    // Helper method to render a certain button grid
     void renderGrid(size_t num, int button_enum=0) {
       double now = millis();
-      if (now - last_render > UI_MAX_FPS) {
+      if (now - last_render > (1000.0/UI_MAX_FPS)) {
         display.clearDisplay();
         button_grids[num].render(button_enum);
         // Display all that stuff and store the time of the last render
@@ -250,49 +276,52 @@ class Ui {
       }
     }
 
-    // Renders a splash screen
+    // Renders a splash screen (runs once)
     void renderSplash() {
       display.setTextSize(1);
-      // 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 < 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);
-      //   }
-        
-      //   display.display();
-      //   delay(1);
-      // }
-      // display.invertDisplay(true);
-      // display.display();
-      // delay(800);
-      // display.clearDisplay();
-      // display.invertDisplay(false);
-      // display.display();
+      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 < 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);
+          }
+          
+          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);
     }
 
+    // Helper method to reset the controls
     void resetControls() {
       button_1.reset();
       button_2.reset();
@@ -302,18 +331,21 @@ class Ui {
       button_6.reset();
     }
 
+    // Setup the Recording Menu
     void setupRecMenu() {
-      // Only run once
+      // Only run once when the ui_mode changed
       if (ui_mode == UI_MODE_REC_MENU && last_ui_mode != UI_MODE_REC_MENU) {
         Serial.println("[UI] Setup Rec Menu");
+
+        // Reset controls
         resetControls();
+
         // Stay in this menu as long as the button is pressed, otherwise return
         button_1.onPressed([this](){ this->setMode(UI_MODE_REC_MENU); });
         button_1.onReleased([this](){ this->setMode(UI_MODE_DEFAULT); });
 
         // Toggle between momentary and toggle recording modes
-        button_2.onPress([this](){ 
-          
+        button_2.onPress([this](){       
           Serial.print("[UI] Mom/Toggle option ");
           if (rec_button_momentary) {
             Serial.println("momentary -> toggle");
@@ -330,10 +362,10 @@ class Ui {
           button_grids[0].grid_buttons_[2].next();
         }); // FULL ONESHOT
 
-        // Switch Recording modes
+        // Switch Recording modes (Full/Loop/Oneshot)
         button_4.onPress([this](){ 
           button_grids[0].grid_buttons_[3].next();
-          // Button.active returns number according to mode:
+          // Button.active returns number according to mode, we cast it to a RecMode enum
           rec_mode = (RecMode) button_grids[0].grid_buttons_[3].active;
           switch (rec_mode) {
             case REC_MODE_FULL: looper.setRecModeFull(); break;
@@ -353,26 +385,36 @@ class Ui {
           button_grids[0].grid_buttons_[5].next();
           looper.setRecStartMode((atoav::RecStartMode) button_grids[0].grid_buttons_[5].active);
         });
+
+        // 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
+      // Only run once on mode change
       if (ui_mode == UI_MODE_DEFAULT && last_ui_mode != UI_MODE_DEFAULT) {
         Serial.println("[UI] Setup Default mode");
+
+        // Reset controls
         resetControls();
+
         // Set up the initial recording mode
         switch (rec_mode) {
           case REC_MODE_FULL: looper.setRecModeFull(); break;
           case REC_MODE_LOOP: looper.setRecModeLoop(); break;;
           case REC_MODE_FULL_SHOT: looper.setRecModeFullShot(); break;
         };
-        // Setup Button functions
+
+        // Setup Button functions (these should enter the ButtonGrid Menus)
         button_1.onPressed([this](){ this->setMode(UI_MODE_REC_MENU); });
         button_2.onPressed([this](){ this->setMode(UI_MODE_PLAY_MENU); });
         button_3.onPressed([this](){ this->setMode(UI_MODE_TRIGGER_MENU); });
-        // Momentary/Toggle Recording modes
+        button_6.onPressed([this](){ this->setMode(UI_MODE_FX_MENU); });
+
+        // Set the recording/overdub buttons to toggle or momentary
+        // depending on the value of the option
         if (rec_button_momentary) {
           Serial.println("[UI] Set to momentary mode");
           button_4.onPressed([this](){ this->activateRecording(); });
@@ -384,55 +426,77 @@ class Ui {
           button_4.onReleased([this](){ this->toggleRecording(); });
           button_5.onReleased([this](){ this->toggleOverdub(); });
         }
-        button_6.onPressed([this](){ this->setMode(UI_MODE_FX_MENU); });
+        
+        // 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
       double now = millis();
-      if (now - last_render > UI_MAX_FPS) {
+      if (now - last_render > (1000.0/UI_MAX_FPS)) {
+
+        // Clear the display
         display.clearDisplay();
-        // Render the waveform
+
+        // 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 = looper.getBufferLength() / (display.width() * WAVEFORM_OVERSAMPLING);
+        // Helper variable for the bottom of the screen
         int bottom = display.height()-1;
-        // Render the waveform
+
+        // 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 needed, else use cache
+          // Only recalculate if the cahce is dirty, else use cache
           if (waveform_cache_dirty) {
             float sig = 0.0f;
+            // Step through the buffer and sum the absolute values
             for (int s=0; s<WAVEFORM_OVERSAMPLING; s++) {
               float abs_sig = looper.getBuffer()[step*i];
               abs_sig = abs(abs_sig) * 100.0f;
               sig += abs_sig;
             }
+            // We oversampled so divide here
             sig = sig / float(WAVEFORM_OVERSAMPLING);
+
+            // Volume is logarithmic, we want to see silent noises as well
             if (sig != 0.0f) {
               sig = log10(sig)/3.6f;
             }
             waveform_cache[x] = int(sig * wave_height);
           }
-          // Serial.print(waveform_cache[x]);
-          // Serial.print(",");
+
+          // 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);
-          display.drawFastHLine(0, bottom, display.width(), 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 Line for loop start 
+        // Draw Indicator for loop start 
         int x_start_loop = int(loop_start * 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 Line for Loop End
+        // Draw Indicator for Loop End
         int x_loop_length = int(loop_length * 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);
 
-        // Connecting line for start and end
+        // 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 {
@@ -444,7 +508,7 @@ class Ui {
         int x_playhead = int(looper.GetPlayhead() * display.width()) + x_start_loop;
         display.drawLine(x_playhead, 6, x_playhead, 24, SH110X_WHITE);
 
-        // Draw Recording stuff
+        // Draw Recording Indicator and Recording Head
         if (recording_state == REC_STATE_RECORDING) {
           // Draw Rec Head
           int x_rec_head = int(looper.GetRecHead() * display.width());
@@ -457,8 +521,13 @@ class Ui {
           display.fillCircle(6, 6, 3, SH110X_WHITE);
         }
 
-        // Draw Overdub stuff
+        // Draw Overdub Indicator and Recording Head
         if (recording_state == REC_STATE_OVERDUBBING) {
+          // Draw Rec Head
+          int x_rec_head = int(looper.GetRecHead() * display.width());
+          display.drawLine(x_rec_head, 10, x_rec_head, 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);
@@ -476,11 +545,15 @@ class Ui {
         pot_6.renderUi();
         pot_7.renderUi();
 
+        // Display all the things done above
         display.display();
+
+        // Store the time of when we started rendering
         last_render = now;
       }
     }
 
+    // Activate recording and set the waveform cache to dirty
     void activateRecording() {
       if (recording_state != REC_STATE_RECORDING) {
         Serial.println("[UI] Activate Recording");
@@ -490,6 +563,7 @@ class Ui {
       }
     }
 
+    // Toggle recording
     void toggleRecording() {
       Serial.print("[UI] Toggle Recording ");
       Serial.print(recording_state);
@@ -508,10 +582,12 @@ class Ui {
       Serial.println(recording_state);
     }
 
+    // Returns true if we are recording
     bool isRecording() {
       return looper.isRecording();
     }
 
+    // Activates overdubbing
     void activateOverdub() {
       if (recording_state != REC_STATE_OVERDUBBING) {
         Serial.println("[UI] Activate Overdub");
@@ -521,6 +597,7 @@ class Ui {
       }
     }
 
+    // Stop the recording
     void stopRecording() {
       if (recording_state != REC_STATE_NOT_RECORDING) {
         Serial.println("[UI] Stop Recording");
@@ -529,6 +606,7 @@ class Ui {
       }
     }
 
+    // Toggle overdub off and on
     void toggleOverdub() {
       Serial.print("[UI] Toggle Overdub ");
       Serial.print(recording_state);
@@ -547,21 +625,24 @@ class Ui {
       Serial.println(recording_state);
     }
 
-
+    // Return true if overdub is running
     bool isOverdubbing() {
       return looper.isOverdubbing();
     }
 
+    // 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; 
       }
     }
 
+    // Update the Ui variables (expected to run repeatedly)
     void update() {
       resetRecordingState();
     }
 
+    // Set the Looper start/length to a given value
     void setLoop(float start, float length) {
       loop_start = start;
       loop_length = length;
diff --git a/menu.ods b/menu.ods
new file mode 100644
index 0000000000000000000000000000000000000000..1b5d3f6346eea013ef11936ddfbd7c26749045e6
GIT binary patch
literal 18938
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%0D4HGQZ_RiT|S=lJ!bd3AJZDF@dPC1R@
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

literal 0
HcmV?d00001

-- 
GitLab