           - (unsigned long)(-float(adsr_values[env].A) * log(1 - (cv_current / (ENV_PEAK * ENV_SATURATION))));
     }
 
-    // handle encoder button press
-    DUINO_Encoder::Button b = Encoder.get_button();
-    if(b == DUINO_Encoder::DoubleClicked)
-    {
-      invert_current_selection();
-      saving = !saving;
-      invert_current_selection();
-    }
-    else if(b == DUINO_Encoder::Clicked)
-    {
-      if(saving)
-      {
-        if(!saved_)
-        {
-          save_params(0, v, 8);
-          Display.fill_rect(123, 2, 3, 3, DUINO_SH1106::Black);
-          Display.display(123, 125, 0, 0);
-        }
-      }
-      else
-      {
-        invert_current_selection();
-        selected++;
-        selected %= 8;
-        invert_current_selection();
-      }
-    }
-
-    // handle encoder spin
-    if(!saving)
-    {
-      v[selected] += Encoder.get_value();
-      if(v[selected] < 0)
-      {
-        v[selected] = 0;
-      }
-      if(v[selected] > V_MAX)
-      {
-        v[selected] = V_MAX;
-      }
-      if(v[selected] != v_last[selected])
-      {
-        // mark save box
-        if(saved_)
-        {
-          saved_ = false;
-          Display.fill_rect(123, 2, 3, 3, DUINO_SH1106::Black);
-          Display.display(123, 125, 0, 0);
-        }
-
-        // update slider
-        uint16_t col = 11 * (selected % 4) + (selected > 3 ? 85 : 1);
-        Display.fill_rect(col, 51 - v_last[selected], 9, 3, DUINO_SH1106::Black);
-        Display.fill_rect(col, 51 - v[selected], 9, 3, DUINO_SH1106::White);
-        Display.display(col, col + 8, 1, 6);
-
-        // update ADSR value
-        uint8_t e = selected > 3 ? 1 : 0;
-        switch(selected % 4)
-        {
-          case 0:
-            adsr_values[e].A = uint16_t(v[selected]) * 24;
-            break;
-          case 1:
-            adsr_values[e].D = uint16_t(v[selected]) * 24;
-            break;
-          case 2:
-            adsr_values[e].S = (float(v[selected]) / float(V_MAX)) * ENV_PEAK;
-            break;
-          case 3:
-            adsr_values[e].R = uint16_t(v[selected]) * 24;
-            break;
-        }
-
-        // update last encoder value
-        v_last[selected] = v[selected];
-      }
-    }
+    widget_loop();
 
     // display selected envelope
     if(selected_env != last_selected_env)

    Display.draw_char(54, 56, '1', DUINO_SH1106::White);
    Display.draw_char(69, 56, '2', DUINO_SH1106::White);
    Display.draw_char(76, 56, 0x10, DUINO_SH1106::White);

    // draw sliders & labels
    for(uint8_t i = 0; i < 8; ++i)
    {
      Display.fill_rect(11 * (i % 4) + (i > 3 ? 85 : 1), 51 - v[i], 9, 3, DUINO_SH1106::White);
      Display.draw_char(11 * (i % 4) + (i > 3 ? 87 : 3), 56, label[i % 4], DUINO_SH1106::White);
    }

    Display.fill_rect(1, 55, 9, 9, DUINO_SH1106::Inverse);
    Display.fill_rect(53, 55, 7, 9, DUINO_SH1106::Inverse);

    Display.display_all();
  }


void gate_isr();
void switch_isr();

void save_click_callback();
void adsr_scroll_callback_0(int delta);
void adsr_scroll_callback_1(int delta);
void adsr_scroll_callback_2(int delta);
void adsr_scroll_callback_3(int delta);
void adsr_scroll_callback_4(int delta);
void adsr_scroll_callback_5(int delta);
void adsr_scroll_callback_6(int delta);
void adsr_scroll_callback_7(int delta);

class DU_ADSR_Function : public DUINO_Function {
 public:
  DU_ADSR_Function() : DUINO_Function(0b10111100) { }
  
  virtual void setup()
  {
    // build widget hierarchy
    container_outer_ = new DUINO_WidgetContainer<2>(DUINO_Widget::DoubleClick, 1);
    widget_save_ = new DUINO_DisplayWidget(121, 0, 7, 7);
    widget_save_->attach_click_callback(save_click_callback);
    container_outer_->attach_child(widget_save_, 0);
    container_adsr_ = new DUINO_WidgetContainer<8>(DUINO_Widget::Click);
    for (uint8_t i = 0; i < 4; ++i)
    {
      widgets_adsr_[i] = new DUINO_DisplayWidget(11 * i + 2, 55, 7, 9);
      widgets_adsr_[i + 4] = new DUINO_DisplayWidget(11 * i + 86, 55, 7, 9);
      container_adsr_->attach_child(widgets_adsr_[i], i);
      container_adsr_->attach_child(widgets_adsr_[i + 4], i + 4);
    }
    container_outer_->attach_child(container_adsr_, 1);
    widgets_adsr_[0]->attach_scroll_callback(adsr_scroll_callback_0);
    widgets_adsr_[1]->attach_scroll_callback(adsr_scroll_callback_1);
    widgets_adsr_[2]->attach_scroll_callback(adsr_scroll_callback_2);
    widgets_adsr_[3]->attach_scroll_callback(adsr_scroll_callback_3);
    widgets_adsr_[4]->attach_scroll_callback(adsr_scroll_callback_4);
    widgets_adsr_[5]->attach_scroll_callback(adsr_scroll_callback_5);
    widgets_adsr_[6]->attach_scroll_callback(adsr_scroll_callback_6);
    widgets_adsr_[7]->attach_scroll_callback(adsr_scroll_callback_7);

    env = 0;
    gate_time = 0;
    release_time = 0;
    last_selected_env = 0;
    last_gate = false;

