diff --git a/code/daisy-looper/buttons.h b/code/daisy-looper/buttons.h
index 04d27bf144d83d57c6f6262fc352f7030aff1e05..3868069f205d3dd82263db4b04dd67a65e15708c 100644
--- a/code/daisy-looper/buttons.h
+++ b/code/daisy-looper/buttons.h
@@ -12,7 +12,6 @@ extern Adafruit_SH1106G display;
 
 
 
-typedef void (*callback_function)(void);
 
 
 class Button {
@@ -20,10 +19,11 @@ class Button {
   bool has_been_pressed;
   unsigned long press_start;
   unsigned long release_start;
-  callback_function onPressFunction;
-  callback_function onPressedFunction;
-  callback_function onLongPressFunction;
-  callback_function onVeryLongPressFunction;
+  std::function<void()> onPressFunction;
+  std::function<void()> onPressedFunction;
+  std::function<void()> onLongPressFunction;
+  std::function<void()> onVeryLongPressFunction;
+  std::function<void()> onReleasedFunction;
 
   public:
     Button(int pin);
@@ -32,10 +32,13 @@ class Button {
     unsigned long pressed_since();
     unsigned long released_since();
 
-    void onPress(callback_function f);
-    void onPressed(callback_function f);
-    void onLongPress(callback_function f);
-    void onVeryLongPress(callback_function f);
+    void onPress(std::function<void()> f);
+    void onPressed(std::function<void()> f);
+    void onLongPress(std::function<void()> f);
+    void onVeryLongPress(std::function<void()> f);
+    void onReleased(std::function<void()> f);
+
+    void reset();
 
     bool display_value_changes = false;
     void setDisplayMode(const char *description);
@@ -62,11 +65,9 @@ void Button::read() {
   
   if (is_pressed && this->press_start == 0) {
     this->press_start = millis();
-    Serial.println("Set a new start");
   }
   if (!is_pressed && this->has_been_pressed && this->release_start == 0) {
     this->release_start = millis();
-    Serial.println("Set a new release");
   }
 
   unsigned long pressed_since = this->pressed_since();
@@ -85,28 +86,25 @@ void Button::read() {
     if (!this->has_been_pressed) {
       if (pressed_since > 0 && pressed_since < DURATION_SHORT_PRESS) {
         if (this->onPressFunction) { this->onPressFunction(); }
-        Serial.print("Short Press (released after ");
-        Serial.print(pressed_since);
-        Serial.print(", released since ");
-        Serial.print(released_since);
-        if (released_since < 200 ) {
-          Serial.print(", Doublepress? ");
-        }
-        Serial.println(")");
+        // Serial.print("Short Press (released after ");
+        // Serial.print(pressed_since);
+        // Serial.print(", released since ");
+        // Serial.print(released_since);
       } else if (pressed_since > 0 &&  pressed_since < DURATION_VERY_LONG_PRESS) {
         if (this->onLongPressFunction) { this->onLongPressFunction(); }
-        Serial.print("Long Press (released after ");
-        Serial.print(pressed_since);
-        Serial.println(")");
+        // Serial.print("Long Press (released after ");
+        // Serial.print(pressed_since);
+        // Serial.println(")");
       } else if (pressed_since > 0 && pressed_since >= DURATION_VERY_LONG_PRESS) {
         if (this->onVeryLongPressFunction) { this->onVeryLongPressFunction(); }
-        Serial.print("Very Long Press (released after ");
-        Serial.print(pressed_since);
-        Serial.println(")");
+        // Serial.print("Very Long Press (released after ");
+        // Serial.print(pressed_since);
+        // Serial.println(")");
       }
       this->press_start = 0;
       this->has_been_pressed = true;
       this->release_start = millis();
+      if (this->onReleasedFunction) { this->onReleasedFunction(); }
     }
   }
 }
@@ -125,26 +123,37 @@ unsigned long Button::released_since() {
   return millis() - this->release_start;
 }
 
-void Button::onPress(callback_function f) {
+void Button::onPress(std::function<void()> f) {
   this->onPressFunction = f;
 }
 
-void Button::onPressed(callback_function f) {
+void Button::onPressed(std::function<void()> f) {
   this->onPressedFunction = f;
 }
 
-void Button::onLongPress(callback_function f) {
+void Button::onLongPress(std::function<void()> f) {
   this->onLongPressFunction = f;
 }
 
-void Button::onVeryLongPress(callback_function f) {
+void Button::onVeryLongPress(std::function<void()> f) {
   this->onVeryLongPressFunction = f;
 }
 
+void Button::onReleased(std::function<void()> f) {
+  this->onReleasedFunction = f;
+}
+
 void Button::setDisplayMode(const char *description) {
   this->display_value_changes = true;
   this->description = description;
 }
 
+void Button::reset() {
+  this->onPressFunction = NULL;
+  this->onPressedFunction = NULL;
+  this->onLongPressFunction = NULL;
+  this->onVeryLongPressFunction = NULL;
+}
+
 
 #endif
\ No newline at end of file
diff --git a/code/daisy-looper/daisy-looper.ino b/code/daisy-looper/daisy-looper.ino
index 02e4ed872d519469fb7928e35616cf7eb77dd074..f69de3883337a405757ffc084851f6b2502d862d 100644
--- a/code/daisy-looper/daisy-looper.ino
+++ b/code/daisy-looper/daisy-looper.ino
@@ -18,19 +18,23 @@
 #include "looper.h"
 #include "env_follower.h"
 #include "helpers.h"
+#include "ui.h"
 
 #define UI_FPS 60.0
-#define WAVEFORM_OVERSAMPLING 2
 
 static const size_t buffer_length = 48000 * 5;
 static float DSY_SDRAM_BSS buffer[buffer_length];
 
-static atoav::Looper looper;
+atoav::Looper looper;
 static PitchShifter pitch_shifter;
-static atoav::EnvelopeFollower output_envelope_follower;
+static atoav::EnvelopeFollower input_envelope_follower;
 DelayLine<float, 24000> delayline;
 DSY_SDRAM_BSS ReverbSc reverb;
 static Compressor compressor;
+Oscillator lfo;
+static SampleHold sample_and_hold;
+static WhiteNoise noise;
+static Metro tick;
 // Resonator res;
 
 CpuLoadMeter load;
@@ -56,23 +60,29 @@ Potentiometer pot_7 = Potentiometer(A6);
 RGBLed rgb_led = RGBLed(A10, A9, A11);
 
 
+// OLED Display
 #define SCREEN_WIDTH 128 // OLED display width, in pixels
 #define SCREEN_HEIGHT 64 // OLED display height, in pixels
 #define OLED_RESET -1   //   QT-PY / XIAO
 Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
 
-uint16_t waveform_cache[SCREEN_WIDTH] = {0};
+// User Interface
+Ui ui;
 
+// Daisy
 DaisyHardware hw;
 
+// Variables for the Audio-Callback
 size_t num_channels;
 float blocksize;
-double last_render = 0.0;
 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;
 
-
+// Actual audio-processing is orchestrated here
 void AudioCallback(float **in, float **out, size_t size) {
   float output = 0.0f;
   float no_delay = 0.0f;
@@ -80,10 +90,29 @@ void AudioCallback(float **in, float **out, size_t size) {
   delayline.SetDelay(delaytime);
   float out1, out2;
   for (size_t i = 0; i < size; i++) {
+    uint8_t trig = tick.Process();
+    float lfo_value = lfo.Process();
+    float rand = sample_and_hold.Process(trig, noise.Process() * 5.0f, sample_and_hold.MODE_SAMPLE_HOLD);
+    // 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) {
+        looper.addToPlayhead(rand * random_amount* 48000.0f);
+        looper.setPlaybackSpeed(pitch_val);
+      }
+    }
+    if (lfo_amount > 0.5f) {
+      looper.setPlaybackSpeed(pitch_val);
+    } else {
+      looper.setPlaybackSpeed(pitch_val + lfo_value * lfo_amount);
+    }
+    
     // res.SetDamping(0.1+delaymix*0.2f);
     auto looper_out = looper.Process(in[1][i]);
     // FIXME:
-    float input_envelope = input_envelope_follower.Process(in[1][i]);
+    input_envelope_follower.Process(in[1][i]);
     output = drywetmix * pitch_shifter.Process(looper_out) + in[1][i] * (1.0f - drywetmix);
     // output = output * (1.0f - resmix) + res.Process(output*resmix);
     no_delay = output;
@@ -92,38 +121,24 @@ void AudioCallback(float **in, float **out, size_t size) {
 
     output += wet_delay * delaymix;
     compressor.Process(output);
+
+    // Process reverb
     reverb.Process(output, output, &out1, &out2);
+    // Mix reverb with the dry signal depending on the amount dialed
+    output = output * (1.0f - reverbmix) + out1 * reverbmix;
     
-    out[0][i] = out[1][i] = out1;
+    out[0][i] = out[1][i] = output;
   }
 }
 
-bool waveform_cache_dirty = false;
-auto record_on = false;
-auto overdub_on = false;
-auto loop_start = 0.0f;
-auto loop_length = 1.0f;
-auto pitch_val = 0.5f;
-bool rec_mode_limit_length_active = false;
-bool display_rec_mode = false;
-double last_displayed_rec_mode = 0.0;
 
-void activate_rec() {
-  record_on = true;
-  waveform_cache_dirty = true;
-}
-
-void toggleRecMode() {
-  rec_mode_limit_length_active = looper.toggleRecMode();
-  last_displayed_rec_mode = millis();
-  display_rec_mode = true;
-}
+// void activate_rec() {
+//   ui.activateRecording();
+// }
 
-void activate_overdub() {
-  overdub_on = true;
-  record_on = true;
-  waveform_cache_dirty = true;
-}
+// void activate_overdub() {
+//   ui.activateOverdub();
+// }
 
 void setup() {
   float sample_rate;
@@ -134,13 +149,15 @@ void setup() {
   sample_rate = DAISY.get_samplerate();
   blocksize = 64.0f;
 
+  tick.Init(10, sample_rate);
+  
   looper.Init(buffer, buffer_length);
   input_envelope_follower.Init(sample_rate);
   input_envelope_follower.SetAttack(100.0);
   input_envelope_follower.SetDecay(1000.0);
 
   reverb.Init(sample_rate);
-  reverb.SetFeedback(0.85f);
+  reverb.SetFeedback(0.95f);
   reverb.SetLpFreq(18000.0f);
 
   compressor.SetThreshold(-64.0f);
@@ -150,6 +167,14 @@ void setup() {
 
   pitch_shifter.Init(sample_rate);
 
+  // set parameters for LFO oscillator object
+  lfo.Init(sample_rate);
+  lfo.SetWaveform(Oscillator::WAVE_TRI);
+  lfo.SetAmp(1);
+  lfo.SetFreq(8.0);
+
+  noise.Init();
+
   delayline.Init();
   delayline.SetDelay(48000.0f);
   // res.Init(.015, 24, sample_rate);
@@ -158,32 +183,15 @@ void setup() {
   load.Init(sample_rate, blocksize);
 
   Serial.begin(250000);
+  Serial.println("Serial communication started");
 
   // Initialize Display
   display.begin(0x3C, true);
   delay(50);
-  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.setCursor(30, 30);
-    display.println("D A I S Y Y");
-    display.display();
-    delay(5);
-  }
-  display.clearDisplay();
-  display.setTextColor(SH110X_WHITE);
-  display.setCursor(30, 30);
-  display.println("D A I S Y Y");
-  display.display();
-  delay(500);
-  display.clearDisplay();
-  display.display();
+  ui.Render();
 
   DAISY.begin(AudioCallback);
-  Serial.println("Started");
+  
 
   rgb_led.init();
 
@@ -193,11 +201,11 @@ void setup() {
   // Set Knob names and display functions
   pot_1.name = "Start";
   pot_2.name = "Length";
-  pot_3.setDisplayMode("Pitch", 12.0f, POT_DISPLAY_MODE_PITCH);
+  pot_3.setDisplayMode("Pitch", 1000.0f, POT_DISPLAY_MODE_PERCENT);
   pot_4.setDisplayMode("Mix", 100.0f, POT_DISPLAY_MODE_PERCENT);
   pot_5.setDisplayMode("Delay", 100.0f, POT_DISPLAY_MODE_PERCENT);
-  pot_6.setDisplayMode("Time", 100.0f, POT_DISPLAY_MODE_PERCENT);
-  // pot_7.setDisplayMode("Structure", 100.0f, POT_DISPLAY_MODE_PERCENT);
+  pot_6.setDisplayMode("Reverb", 100.0f, POT_DISPLAY_MODE_PERCENT);
+  pot_7.setDisplayMode("LFO", 100.0f, POT_DISPLAY_MODE_PERCENT);
 
   // Set Knob Scaling Modes
   pot_3.setBipolar();
@@ -206,9 +214,13 @@ void setup() {
   button_1.init();
   button_2.init();
   button_3.init();
-  button_1.onPressed(activate_rec);
-  button_2.onPress(toggleRecMode);
-  button_3.onPressed(activate_overdub);
+  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);
 }
 
 void loop() {
@@ -222,155 +234,34 @@ void loop() {
   float p7 = pot_7.read();
 
   // Deactivate recording and overdub before reading buttons
-  record_on = false;
-  overdub_on = false;
+  ui.update();
 
   // Read buttons
   button_1.read();
   button_2.read();
   button_3.read();
-
-  // Set loop parameters
-  loop_start = p1;
-  loop_length = p2;
-  looper.SetLoop(loop_start, loop_length);
-
-  // Value should go 1 octave up/down (12 semitones)
-  pitch_val = 12.0f * p3;
-  pitch_shifter.SetTransposition(pitch_val);
+  button_4.read();
+  button_5.read();
+  button_6.read();
+
+  // Set loop start and loop length
+  ui.setLoop(p1, p2);
+  
+  // Value 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
   drywetmix = p4;
   delaymix = p5;
-  delaytime = 100.0f + p6 * 23900.0f;
-  // res.SetStructure(p7);
-
-  // Toggle record
-  looper.SetRecording(record_on, overdub_on);
+  delaytime = 100.0f + p5 * 23900.0f;
+  reverbmix = p6;
+  lfo_amount = p7;
 
-  // Render UI
-  // load.OnBlockStart();
-  renderBuffer();
-  // load.OnBlockEnd();
+  ui.Render();
   rgb_led.setAudioLevelIndicator(int(input_envelope_follower.getValue() * 255));
-
-  // Serial.println(load.GetMaxCpuLoad());
 }
 
 
-void renderBuffer() {
-  double now = millis();
-  if (now - last_render > UI_FPS) {
-    display.clearDisplay();
-    
-    // Render the waveform
-    int wave_height = display.height() * 1.0f;
-    int step = buffer_length / (display.width() * WAVEFORM_OVERSAMPLING);
-    int bottom = display.height()-1;
-    // Render the waveform
-    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
-      if (waveform_cache_dirty) {
-        float sig = 0.0f;
-        for (int s=0; s<WAVEFORM_OVERSAMPLING; s++) {
-          float abs_sig = buffer[step*i];
-          abs_sig = abs(abs_sig) * 100.0f;
-          sig += abs_sig;
-        }
-        sig = sig / float(WAVEFORM_OVERSAMPLING);
-        if (sig != 0.0f) {
-          sig = log10(sig)/3.6f;
-        }
-        waveform_cache[x] = int(sig * wave_height);
-      }
-      // Serial.print(waveform_cache[x]);
-      // Serial.print(",");
-      display.drawFastVLine(x, bottom, -waveform_cache[x], SH110X_WHITE);
-      display.drawFastHLine(0, bottom, display.width(), SH110X_WHITE);
-    }
-    waveform_cache_dirty = false;
-
-    // Draw Line 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
-    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
-    if (x_loop_end >= x_start_loop) {
-      display.drawLine(x_start_loop, 0, x_loop_end, 0, SH110X_WHITE);
-    } else {
-      display.drawLine(x_start_loop, 0, display.width(), 0, SH110X_WHITE);
-      display.drawLine(0, 0, x_loop_end, 0, SH110X_WHITE);
-    }
 
-    // Draw Playhead
-    int x_playhead = int(looper.GetPlayhead() * display.width()) + x_start_loop;
-    display.drawLine(x_playhead, 6, x_playhead, 24, SH110X_WHITE);
-
-    // Draw Recording stuff
-    if (record_on && ! overdub_on) {
-       // 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);
-      // Record sign
-      display.fillRect(0, 0, 13, 13, SH110X_WHITE);
-      display.fillRect(2, 2, 12, 12, SH110X_WHITE);
-      display.fillRect(1, 1, 11, 11, SH110X_BLACK);
-      display.fillCircle(6, 6, 3, SH110X_WHITE);
-    }
 
-    // Draw Overdub stuff
-    if (overdub_on) {
-      // Overdub sign (a "plus")
-      display.fillRect(0, 0, 13, 13, SH110X_WHITE);
-      display.fillRect(2, 2, 12, 12, SH110X_WHITE);
-      display.fillRect(1, 1, 11, 11, SH110X_BLACK);
-      display.drawLine(6, 2, 6, 10, SH110X_WHITE);
-      display.drawLine(2, 6, 10, 6, SH110X_WHITE);
-    }
-
-    // Render potentiometer UIs in case a knob is changed
-    pot_1.renderUi();
-    pot_2.renderUi();
-    pot_3.renderUi();
-    pot_4.renderUi();
-    pot_5.renderUi();
-    pot_6.renderUi();
-    // pot_7.renderUi();
-
-    // After pressing the rec mode button display info
-    if (display_rec_mode) {
-        int x_margin = 10;
-        int y_margin = 13;
-        int x_center = display.width()/2;
-        int y_center = display.height()/2;
-        // Render a rectangle Backdrop for the text
-        display.fillRect(3+x_margin, 3+y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE);
-        display.fillRect(x_margin, y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE);
-        display.fillRect(x_margin+1, y_margin+1, display.width()-(x_margin+1)*2, display.height()-(y_margin+1)*2, SH110X_BLACK);
-      if (rec_mode_limit_length_active) {
-       centeredText("Limit recording", x_center, y_center-4, SH110X_WHITE);
-       centeredText("to loop length", x_center, y_center+4, SH110X_WHITE);
-      } else {
-       centeredText("Record into", x_center, y_center-4, SH110X_WHITE);
-       centeredText("full buffer", x_center, y_center+4, SH110X_WHITE);
-      }
-    }
-    if (now - last_displayed_rec_mode > 500.0) {
-      display_rec_mode = false;
-    }
-
-    // Display all that stuff and store the time of the last render
-    display.display();
-    last_render = now;
-  }
-}
diff --git a/code/daisy-looper/helpers.h b/code/daisy-looper/helpers.h
index ef66325e25d825533c04d8aab64c12846f08ef0e..3a9178fa1bc68a24b7ef84c8b1efbd6e787f5ca4 100644
--- a/code/daisy-looper/helpers.h
+++ b/code/daisy-looper/helpers.h
@@ -30,6 +30,112 @@ int centeredText(const char *buf, int x, int y, int color, int lineheight=8) {
   return w;
 }
 
+int centeredTextMark(const char *buf, int x, int y, int color, int underline_line=0, int lineheight=8) {
+  int16_t x1, y1;
+  uint16_t w, h;
+  char *line_pointer = strchr(buf, '\n');
+  display.setTextColor(color);
+  if (!line_pointer) {
+    display.getTextBounds(buf, 0, 0, &x1, &y1, &w, &h); //calc width of new string
+    int x_start = x - (w / 2);
+    int y_start = y - (h / 2);
+    display.setCursor(x_start, y_start);
+    if (underline_line == 1) {
+        display.drawFastHLine(x_start-2, y_start+lineheight, w+4, color);
+      }
+    display.print(buf);
+  }else {
+    char *tmp = strdup(buf);
+    char* d = strtok(tmp, "\n");
+    int line = 0;
+    while (d != NULL) {
+        display.getTextBounds(d, 0, 0, &x1, &y1, &w, &h); //calc width of new string
+        int x_start = x - (w / 2);
+        int y_start = y - (h / 2)-lineheight/2 + (line*lineheight);
+        display.setCursor(x_start, y_start);
+        display.print(d);
+        d = strtok(NULL, ",");
+        if (underline_line == 1) {
+          display.drawFastHLine(x_start-2, y_start+lineheight, w+4, color);
+        }
+        line++;
+    }
+    free(tmp);
+  }
+  return w;
+}
+
+
+int centeredTextMarkMulti(const char *buf, int x, int y, int color, int underline_line=0, int lineheight=8) {
+  int16_t x1, y1;
+  uint16_t w, h;
+  char *line_pointer = strchr(buf, '\n');
+  display.setTextColor(color);
+  if (!line_pointer) {
+    display.getTextBounds(buf, 0, 0, &x1, &y1, &w, &h); //calc width of new string
+    int x_start = x - (w / 2);
+    int y_start = y - (h / 2);
+    display.setCursor(x_start, y_start);
+    if (underline_line == 1) {
+        display.drawFastHLine(x_start-2, y_start, w+4, color);
+      }
+    display.print(buf);
+  }else {
+    char *tmp = strdup(buf);
+    char* d = strtok(tmp, "\n");
+    int line = 0;
+    while (d != NULL) {
+        display.getTextBounds(d, 0, 0, &x1, &y1, &w, &h); //calc width of new string
+        int x_start = x - (w / 2);
+        int y_start = y - (h / 2)-lineheight/2 + (line*lineheight);
+        display.setCursor(x_start, y_start);
+        display.print(d);
+        d = strtok(NULL, ",");
+        if (line == underline_line) {
+          display.drawFastHLine(x_start-2, y_start+lineheight-3, w+4, color);
+        }
+        line++;
+    }
+    free(tmp);
+  }
+  return w;
+}
+
+int button_multi(const char *buf, int x, int y, int color, int underline_line=0, int lines=0) {
+  int16_t x1, y1;
+  uint16_t w, h;
+  display.setTextColor(color);
+  char *tmp = strdup(buf);
+  int line = 0;
+  char* pch = NULL;
+  pch = strtok(tmp, "\n");
+
+  int radius = 3;
+  int cell_width = 128/3;
+  int left_x = x - (cell_width/2);
+  int margin = 10;
+  int circle_start_x  = left_x + margin;
+  int spacing = (cell_width - margin - margin) / lines;
+
+  while (pch != NULL){
+    // Draw Option-Circles
+    display.drawCircle(circle_start_x + line*spacing, y+7, radius, SH110X_WHITE);
+    // Only display the active text
+    if (line == underline_line) {
+      display.getTextBounds(pch, 0, 0, &x1, &y1, &w, &h); //calc width of new string
+      int x_start = x - (w / 2);
+      int y_start = y - (h / 2)-7;
+      display.setCursor(x_start, y_start);
+      display.print(pch);
+      // On the active option, draw a filled circle
+      display.fillCircle(circle_start_x + line*spacing, y+7, radius, SH110X_WHITE);
+    }
+    line++;
+    pch = strtok(NULL, "\n");
+  }
+  free(tmp);
+  return w;
+}
 
 
 #endif
\ No newline at end of file
diff --git a/code/daisy-looper/looper.h b/code/daisy-looper/looper.h
index d75f8f9137aa777857d17e4b9a3610937f4b129b..9624a92b60ca87b97b56e674345b1dc54cc5e731 100644
--- a/code/daisy-looper/looper.h
+++ b/code/daisy-looper/looper.h
@@ -3,6 +3,20 @@
 
 namespace atoav {
 
+enum RecPitchMode {
+  REC_PITCH_MODE_NORMAL,
+  REC_PITCH_MODE_PITCHED,
+  REC_PITCH_MODE_UNPITCHED,
+  REC_PITCH_MODE_LAST,
+};
+
+enum RecStartMode {
+  REC_START_MODE_BUFFER,
+  REC_START_MODE_LOOP,
+  REC_START_MODE_PLAYHEAD,
+  REC_START_MODE_LAST,
+};
+
 class Looper {
   public:
     void Init(float *buf, size_t length) {
@@ -12,19 +26,34 @@ class Looper {
       memset(_buffer, 0, sizeof(float) * _buffer_length);
     }
 
-    void SetRecording(bool is_rec_on, bool is_overdub_on) {
-      this->_is_overdub_on = is_overdub_on;
-        //Initialize recording head position on start
-        if (_rec_env_pos_inc <= 0 && is_rec_on) {
-            // _rec_head = (_loop_start + _play_head) % _buffer_length; 
-            _rec_head = (_loop_start) % _buffer_length; 
-            _is_empty = false;
+    RecPitchMode rec_pitch_mode = REC_PITCH_MODE_NORMAL;
+    RecStartMode rec_start_mode = REC_START_MODE_BUFFER;
+
+    void SetRecording(bool is_recording, bool is_overdub) {
+      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;
+        switch (rec_start_mode) {
+          case REC_START_MODE_LOOP:
+            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));
+            break;
         }
-        // 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_rec_on ? 1 : -1;
+          
+        _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;
     }
 
     void SetLoop(const float loop_start, const float loop_length) {
@@ -56,20 +85,49 @@ class Looper {
       if (_rec_env_pos > 0) {
         // Calculate fade in/out
         float rec_attenuation = static_cast<float>(_rec_env_pos) / static_cast<float>(kFadeLength);
-        if (this->_is_overdub_on) {
-          _buffer[_rec_head] += in * rec_attenuation;
+        if (this->is_overdub) {
+          _buffer[int(rec_head)] += in * rec_attenuation;
         } else {
-          _buffer[_rec_head] = in * rec_attenuation + _buffer[_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
+        switch (rec_pitch_mode) {
+          case REC_PITCH_MODE_NORMAL: 
+            rec_head += 1.0f; 
+            break;
+          case REC_PITCH_MODE_UNPITCHED: 
+            rec_head += playback_increment; 
+            break;
+          case REC_PITCH_MODE_PITCHED:
+            if (playback_increment != 0.0) {
+              rec_head += 1.0f/playback_increment;
+            }
+            break;
         }
-        _rec_head ++;
 
         // Different recording modes
-        if (!_limit_rec_length) {
-          _rec_head %= _buffer_length;
+        if (!stop_after_recording) {
+          if (!stay_within_loop) {
+            // record into whole buffer
+            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);
+          }
         } else {
-          // Limit rec head to stay inside the loop
-          _rec_head %= (_loop_start + _loop_length);
-          _rec_head = max(_loop_start, _rec_head);
+          if (!stay_within_loop) {
+            if (rec_head > _buffer_length) { SetRecording(false, false); }
+          } else {
+            if (rec_head > _loop_start + _loop_length) { SetRecording(false, false); }
+          }
+        }
+
+        if (rec_head > _buffer_length) {
+          rec_head = 0.0f;
+        } else if (rec_head < 0) {
+          rec_head = _buffer_length;
         }
         
       }
@@ -81,48 +139,112 @@ class Looper {
       // Playback from the buffer
       float attenuation = 1;
       float output = 0;
-      //Calculate fade in/out
-      if (_play_head < kFadeLength) {
-        attenuation = static_cast<float>(_play_head) / static_cast<float>(kFadeLength);
+
+      // Calculate fade in/out
+      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 = (_loop_start + _play_head) % _buffer_length;
+      auto play_pos = int(_loop_start + play_head) % _buffer_length;
       output = _buffer[play_pos] * attenuation;
 
       // Advance playhead
-      _play_head ++;
-      if (_play_head >= _loop_length) {
+      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;
-        _play_head = 0;
+        play_head = 0;
+      } else if (play_head <= 0) {
+        _loop_start = _pending_loop_start;
+        _loop_length = _pending_loop_length;
+        play_head = _loop_length;
       }
       
+      
       return output * attenuation;
     }
 
     float GetPlayhead() {
-      return  float(_play_head) / float(_buffer_length);
+      return  float(play_head) / float(_buffer_length);
     }
 
-    float GetLoopStart() {
-      return  float(_loop_start) / float(_buffer_length);
+    float GetRecHead() {
+      return  float(rec_head) / float(_buffer_length);
     }
 
-    float GetLoopLength() {
-      return  float(_loop_length) / float(_buffer_length);
+    bool toggleRecMode() {
+      stay_within_loop = !stay_within_loop;
+      return stay_within_loop;
     }
 
-    float GetRecHead() {
-      return  float(_rec_head) / float(_buffer_length);
+    void setRecModeFull() {
+      Serial.println("[Looper] Set RecMode to Full");
+      stay_within_loop = false;
+      stop_after_recording = false;
     }
 
-    bool toggleRecMode() {
-      _limit_rec_length = !_limit_rec_length;
-      return _limit_rec_length;
+    void setRecModeLoop() {
+      Serial.println("[Looper] Set RecMode to Loop");
+      stay_within_loop = true;
+      stop_after_recording = false;
+    }
+
+    void setRecModeFullShot() {
+      Serial.println("[Looper] Set RecMode to Loop FullShot");
+      stay_within_loop = false;
+      stop_after_recording = true;
+    }
+
+    void setRecModeLoopShot() {
+      Serial.println("[Looper] Set RecMode to Loop Oneshot");
+      stay_within_loop = true;
+      stop_after_recording = true;
+    }
+
+
+    void setPlaybackSpeed(float increment) {
+      playback_increment = increment;
+    }
+
+    void addToPlayhead(float value) {
+      play_head += value;
+    }
+
+    float* getBuffer() {
+      return _buffer;
+    }
+
+    size_t getBufferLength() {
+      return _buffer_length;
+    }
+
+    bool isRecording() {
+      return is_recording;
+    }
+
+    bool isOverdubbing() {
+      return is_overdub;
+    }
+
+    void setRecPitchMode(RecPitchMode mode) {
+      Serial.print("[Looper] Set RecPitchMode from ");
+      Serial.print(rec_pitch_mode);
+      Serial.print(" to ");
+      Serial.println(mode);
+      rec_pitch_mode = mode;
+    }
+
+    void setRecStartMode(RecStartMode mode) {
+      Serial.print("[Looper] Set RecStartMode from ");
+      Serial.print(rec_start_mode);
+      Serial.print(" to ");
+      Serial.println(mode);
+      rec_start_mode = mode;
     }
 
   private:
@@ -137,19 +259,21 @@ class Looper {
     size_t _loop_start          = 0;
     size_t _pending_loop_start  = 0;
 
-    size_t _play_head = 0;
-    size_t _rec_head  = 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;
-    bool _limit_rec_length = false;
-    bool _is_overdub_on = false;
-};
+    bool stay_within_loop = false;
+    bool is_overdub = false;
+    bool is_recording = false;
+    bool stop_after_recording = false;
 };
 
-
-
+}; // namespace atoav
 
 #endif
\ No newline at end of file
diff --git a/code/daisy-looper/ui.h b/code/daisy-looper/ui.h
new file mode 100644
index 0000000000000000000000000000000000000000..2d27771b6adb5bccd3782dde6b8fde5534100dbc
--- /dev/null
+++ b/code/daisy-looper/ui.h
@@ -0,0 +1,584 @@
+#include <stdint.h>
+#include "WSerial.h"
+#include "usbd_def.h"
+#ifndef Ui_h
+#define Ui_h
+
+#include "Adafruit_SH110X.h"
+#include "Adafruit_GFX.h"
+#include "potentiometers.h"
+#include "buttons.h"
+#include "looper.h"
+
+#define UI_MAX_FPS 60
+#define WAVEFORM_OVERSAMPLING 2
+
+extern Potentiometer pot_1, pot_2, pot_3, pot_4, pot_5, pot_6, pot_7;
+extern Button button_1, button_2, button_3, button_4, button_5, button_6;
+extern Adafruit_SH1106G display;
+extern atoav::Looper looper;
+
+
+
+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_LAST
+};
+
+enum RecMode {
+  REC_MODE_FULL,
+  REC_MODE_LOOP,
+  REC_MODE_FULL_SHOT,
+  REC_MODE_LAST = 6
+};
+
+enum PlayMode {
+  PLAY_MODE_DRUNK,
+  PLAY_MODE_WINDOW,
+  PLAY_MODE_LOOP,
+  PLAY_MODE_GRAIN,
+  PLAY_MODE_ONESHOT,
+  PLAY_MODE_LAST
+};
+
+enum RecordingState {
+  REC_STATE_NOT_RECORDING,
+  REC_STATE_RECORDING,
+  REC_STATE_OVERDUBBING,
+  REC_STATE_LAST
+};
+
+// Different types of buttons
+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_ENUM,         // Toggles between a group of buttons
+  BUTTON_TYPE_LAST
+};
+
+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)
+     : 
+      name(name),
+      button(button),
+      is_home(is_home),
+      type(type),
+      active(default_value)
+    {
+      // Count the number of lines in the name
+      for(int i = 0; name[i] != '\0'; i++) {
+          if(name[i] == '\n')
+              ++lines;
+      }
+    }
+    Button button;
+    bool is_home;
+    int active;
+    ButtonType type;
+    int lines = 0;
+    
+    void next() {
+      active++;
+      if (active > lines) {
+        active = 0;
+      }
+    }
+};
+
+class ButtonGrid {
+  public:
+    ButtonGrid(const GridButton (&grid_buttons)[6]) 
+    : grid_buttons_{grid_buttons[0], grid_buttons[1], grid_buttons[2], grid_buttons[3], grid_buttons[4], grid_buttons[5]} {}
+    GridButton grid_buttons_[6];
+
+    void render(int button_enum) {
+      int width = display.width();
+      int height = display.height();
+      int box_width = int(width/3.0f);
+      int box_height= int(height/2.0f);
+      int i = 0;
+      // 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++) {
+          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;
+            text_color = SH110X_BLACK;
+          }
+
+          // Position variables
+          uint16_t x = box_x * box_width;
+          uint16_t y = box_y * box_height;
+          uint16_t xc = x + box_width/2;
+          uint16_t yc = y + box_height/2;
+
+          // Fill Background
+          display.fillRect(x, y, box_width, box_height, bg_color);
+
+          // Render the different button types
+          if (grid_buttons_[i].type == BUTTON_TYPE_TOGGLE) {
+            centeredTextMarkMulti(name, xc, yc, text_color, grid_buttons_[i].active, 12);
+          } else if (grid_buttons_[i].type == BUTTON_TYPE_MULTITOGGLE) {
+            button_multi(name, xc, yc, text_color, grid_buttons_[i].active, grid_buttons_[i].lines);
+          } else if (grid_buttons_[i].type == BUTTON_TYPE_ENUM) {
+            bool active = i == button_enum;
+            centeredTextMark(name, xc, yc, text_color, active);
+          } else {
+            centeredText(name, xc, yc, text_color);
+          }
+          
+          // Counter for the number of the button
+          i++;
+        }
+      }
+      // Draw divider Line
+      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);
+    }
+
+};
+
+class Ui {
+  public:
+    Ui() : button_grids {
+      ButtonGrid({
+        GridButton("REC\nMENU", button_1, true),
+        GridButton("MOM\nTOGGLE", button_2, false, BUTTON_TYPE_TOGGLE, 0),
+        GridButton("PRE\nPOST\nOUT\nNOISE", button_3, false, BUTTON_TYPE_MULTITOGGLE, 0),
+        GridButton("FULL\nLOOP\nSHOT", button_4, false, BUTTON_TYPE_MULTITOGGLE, 1),
+        GridButton("NORMAL\nPITCHD\nUNPTCH", button_5, false, BUTTON_TYPE_MULTITOGGLE, 0),
+        GridButton("START\nLOOPST\nPLAYHD", button_6, false, BUTTON_TYPE_MULTITOGGLE, 0),
+      }),
+      ButtonGrid({
+        GridButton("LOOP", button_1, false),
+        GridButton("PLAY\nMENU", button_2, true),
+        GridButton("WINDOW", button_3, false),
+        GridButton("DRUNK", button_4, false),
+        GridButton("GRAIN", button_5, false),
+        GridButton("SHOT", button_6, false),
+      }),
+      ButtonGrid({
+        GridButton("MIDI\nTRIG.", button_1, false),
+        GridButton("MIDI\nUNMUTE", button_2, false),
+        GridButton("TRIG.\nMENU", button_3, true),
+        GridButton("MANUAL\nTRIG.", button_4, false),
+        GridButton("MANUAL\nUNMUTE", button_5, false),
+        GridButton("AUTO", button_6, false),
+      }),
+      ButtonGrid({
+        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),
+      }),
+    } {};
+    UiMode ui_mode = UI_MODE_SPLASH;
+    ButtonGrid button_grids[4];
+    // RecMode rec_mode = REC_MODE_FULL;
+    RecMode rec_mode = REC_MODE_LOOP;
+
+    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:
+          break;
+        case UI_MODE_PLAY_MENU:
+          break;
+        case UI_MODE_TRIGGER_MENU:
+          break;
+        case UI_MODE_FX_MENU:
+          break;
+      }
+    }
+
+    void Render() {
+      switch (ui_mode) {
+        case UI_MODE_SPLASH:
+          renderSplash();
+          break;
+        case UI_MODE_DEFAULT:
+          setupDefault();
+          renderDefault();
+          break;
+        case UI_MODE_REC_MENU:
+          setupRecMenu();
+          renderGrid(0, rec_mode);
+          break;
+        case UI_MODE_PLAY_MENU:
+          renderGrid(1);
+          break;
+        case UI_MODE_TRIGGER_MENU:
+          renderGrid(2);
+          break;
+        case UI_MODE_FX_MENU:
+          renderGrid(3);
+          break;
+      }
+    }
+
+    // Render button grids
+    void renderGrid(size_t num, int button_enum=0) {
+      double now = millis();
+      if (now - last_render > UI_MAX_FPS) {
+        display.clearDisplay();
+        button_grids[num].render(button_enum);
+        // Display all that stuff and store the time of the last render
+        display.display();
+        last_render = now;
+      }
+    }
+
+    // Renders a splash screen
+    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();
+
+      // Splash rendering is now done, go to next UI Mode
+      setMode(UI_MODE_DEFAULT);
+    }
+
+    void resetControls() {
+      button_1.reset();
+      button_2.reset();
+      button_3.reset();
+      button_4.reset();
+      button_5.reset();
+      button_6.reset();
+    }
+
+    void setupRecMenu() {
+      // Only run once
+      if (ui_mode == UI_MODE_REC_MENU && last_ui_mode != UI_MODE_REC_MENU) {
+        Serial.println("[UI] Setup Rec Menu");
+        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](){ 
+          
+          Serial.print("[UI] Mom/Toggle option ");
+          if (rec_button_momentary) {
+            Serial.println("momentary -> toggle");
+          }else{
+            Serial.println("toggle -> momentary");
+          }
+          rec_button_momentary = !rec_button_momentary;
+          button_grids[0].grid_buttons_[1].active = !rec_button_momentary;
+        }); 
+
+        // Set Recording Source (Pre/Post/Out/Noise)
+        button_3.onPress([this](){
+          // TODO: Implement Pre/Post/Out Recording
+          button_grids[0].grid_buttons_[2].next();
+        }); // FULL ONESHOT
+
+        // Switch Recording modes
+        button_4.onPress([this](){ 
+          button_grids[0].grid_buttons_[3].next();
+          // Button.active returns number according to mode:
+          rec_mode = (RecMode) button_grids[0].grid_buttons_[3].active;
+          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;
+          }
+        });
+
+        // Set Recording Pitch mode (Normal/Pitched/Unpitched)
+        button_5.onPress([this](){ 
+          button_grids[0].grid_buttons_[4].next();
+          looper.setRecPitchMode((atoav::RecPitchMode) button_grids[0].grid_buttons_[4].active);
+        });
+
+        // Set Recording Start Option (Buffer Start/Loop Start/Playhead)
+        button_6.onPress([this](){
+          button_grids[0].grid_buttons_[5].next();
+          looper.setRecStartMode((atoav::RecStartMode) button_grids[0].grid_buttons_[5].active);
+        });
+        last_ui_mode = ui_mode;
+      }
+    }
+
+    void setupDefault() {
+      // Only run once
+      if (ui_mode == UI_MODE_DEFAULT && last_ui_mode != UI_MODE_DEFAULT) {
+        Serial.println("[UI] Setup Default mode");
+        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
+        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
+        if (rec_button_momentary) {
+          Serial.println("[UI] Set to momentary mode");
+          button_4.onPressed([this](){ this->activateRecording(); });
+          button_5.onPressed([this](){ this->activateOverdub(); });
+          button_4.onReleased([this](){ this->stopRecording(); });
+          button_5.onReleased([this](){ this->stopRecording(); });
+        } else {
+          Serial.println("[UI] Set to toggle mode");
+          button_4.onReleased([this](){ this->toggleRecording(); });
+          button_5.onReleased([this](){ this->toggleOverdub(); });
+        }
+        button_6.onPressed([this](){ this->setMode(UI_MODE_FX_MENU); });
+        last_ui_mode = ui_mode;
+      }
+    }
+
+    void renderDefault() {
+      double now = millis();
+      if (now - last_render > UI_MAX_FPS) {
+        display.clearDisplay();
+        // Render the waveform
+        int wave_height = display.height() * 1.0f;
+        int step = looper.getBufferLength() / (display.width() * WAVEFORM_OVERSAMPLING);
+        int bottom = display.height()-1;
+        // Render the waveform
+        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
+          if (waveform_cache_dirty) {
+            float sig = 0.0f;
+            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;
+            }
+            sig = sig / float(WAVEFORM_OVERSAMPLING);
+            if (sig != 0.0f) {
+              sig = log10(sig)/3.6f;
+            }
+            waveform_cache[x] = int(sig * wave_height);
+          }
+          // Serial.print(waveform_cache[x]);
+          // Serial.print(",");
+          display.drawFastVLine(x, bottom, -waveform_cache[x], SH110X_WHITE);
+          display.drawFastHLine(0, bottom, display.width(), SH110X_WHITE);
+        }
+        waveform_cache_dirty = false;
+
+        // Draw Line 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
+        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
+        if (x_loop_end >= x_start_loop) {
+          display.drawLine(x_start_loop, 0, x_loop_end, 0, SH110X_WHITE);
+        } else {
+          display.drawLine(x_start_loop, 0, display.width(), 0, SH110X_WHITE);
+          display.drawLine(0, 0, x_loop_end, 0, SH110X_WHITE);
+        }
+
+        // Draw Playhead
+        int x_playhead = int(looper.GetPlayhead() * display.width()) + x_start_loop;
+        display.drawLine(x_playhead, 6, x_playhead, 24, SH110X_WHITE);
+
+        // Draw Recording stuff
+        if (recording_state == REC_STATE_RECORDING) {
+          // 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);
+          // Record sign
+          display.fillRect(0, 0, 13, 13, SH110X_WHITE);
+          display.fillRect(2, 2, 12, 12, SH110X_WHITE);
+          display.fillRect(1, 1, 11, 11, SH110X_BLACK);
+          display.fillCircle(6, 6, 3, SH110X_WHITE);
+        }
+
+        // Draw Overdub stuff
+        if (recording_state == REC_STATE_OVERDUBBING) {
+          // Overdub sign (a "plus")
+          display.fillRect(0, 0, 13, 13, SH110X_WHITE);
+          display.fillRect(2, 2, 12, 12, SH110X_WHITE);
+          display.fillRect(1, 1, 11, 11, SH110X_BLACK);
+          display.drawLine(6, 2, 6, 10, SH110X_WHITE);
+          display.drawLine(2, 6, 10, 6, SH110X_WHITE);
+        }
+
+        // Render potentiometer UIs in case a knob is changed
+        pot_1.renderUi();
+        pot_2.renderUi();
+        pot_3.renderUi();
+        pot_4.renderUi();
+        pot_5.renderUi();
+        pot_6.renderUi();
+        pot_7.renderUi();
+
+        display.display();
+        last_render = now;
+      }
+    }
+
+    void activateRecording() {
+      if (recording_state != REC_STATE_RECORDING) {
+        Serial.println("[UI] Activate Recording");
+        looper.SetRecording(true, false);
+        waveform_cache_dirty = true;
+        recording_state = REC_STATE_RECORDING;
+      }
+    }
+
+    void toggleRecording() {
+      Serial.print("[UI] Toggle Recording ");
+      Serial.print(recording_state);
+      switch (recording_state) {
+        case REC_STATE_NOT_RECORDING:
+          activateRecording();
+          break;
+        case REC_STATE_RECORDING:
+          stopRecording();
+          break;
+        case REC_STATE_OVERDUBBING:
+          activateRecording();
+          break;
+      }
+      Serial.print(" -> ");
+      Serial.println(recording_state);
+    }
+
+    bool isRecording() {
+      return looper.isRecording();
+    }
+
+    void activateOverdub() {
+      if (recording_state != REC_STATE_OVERDUBBING) {
+        Serial.println("[UI] Activate Overdub");
+        waveform_cache_dirty = true;
+        recording_state = REC_STATE_OVERDUBBING;
+        looper.SetRecording(true, true);
+      }
+    }
+
+    void stopRecording() {
+      if (recording_state != REC_STATE_NOT_RECORDING) {
+        Serial.println("[UI] Stop Recording");
+        recording_state = REC_STATE_NOT_RECORDING;
+        looper.SetRecording(false, false);
+      }
+    }
+
+    void toggleOverdub() {
+      Serial.print("[UI] Toggle Overdub ");
+      Serial.print(recording_state);
+      switch (recording_state) {
+        case REC_STATE_NOT_RECORDING:
+          activateOverdub();
+          break;
+        case REC_STATE_OVERDUBBING:
+          stopRecording();
+          break;
+        case REC_STATE_RECORDING:
+          activateOverdub();
+          break;
+      }
+      Serial.print(" -> ");
+      Serial.println(recording_state);
+    }
+
+
+    bool isOverdubbing() {
+      return looper.isOverdubbing();
+    }
+
+    void resetRecordingState() {
+      if (recording_state == REC_STATE_RECORDING || recording_state == REC_STATE_OVERDUBBING) {
+        waveform_cache_dirty = true; 
+      }
+    }
+
+    void update() {
+      resetRecordingState();
+    }
+
+    void setLoop(float start, float length) {
+      loop_start = start;
+      loop_length = length;
+      looper.SetLoop(start, length);
+    }
+
+    private:
+      double last_render;
+      uint16_t waveform_cache[128] = {0};
+      bool waveform_cache_dirty = true;
+      RecordingState recording_state = REC_STATE_NOT_RECORDING;
+      UiMode last_ui_mode = UI_MODE_LAST;
+      bool rec_button_momentary = true;
+      float loop_start = 0.0f;
+      float loop_length = 0.0f;
+};
+
+
+
+#endif
\ No newline at end of file