diff --git a/components/bl00mbox/CMakeLists.txt b/components/bl00mbox/CMakeLists.txt
index f4fe44122c8535d8fad5b78f94aa5e2c53b5cd59..734fd79ed7087ac0cd2f9e56bc7c6e77f7deb21e 100644
--- a/components/bl00mbox/CMakeLists.txt
+++ b/components/bl00mbox/CMakeLists.txt
@@ -15,6 +15,10 @@ idf_component_register(
         plugins/flanger.c
         plugins/sequencer.c
         plugins/noise.c
+        plugins/distortion.c
+        plugins/lowpass.c
+        plugins/mixer.c
+        plugins/slew_rate_limiter.c
         radspa/radspa_helpers.c
         extern/xoroshiro64star.c
     INCLUDE_DIRS
diff --git a/components/bl00mbox/bl00mbox_plugin_registry.c b/components/bl00mbox/bl00mbox_plugin_registry.c
index e18ca82ce3dba92af5587864d62d57d2a3deb814..278bead36c141f7e8e3e7c01917bf903b20544b8 100644
--- a/components/bl00mbox/bl00mbox_plugin_registry.c
+++ b/components/bl00mbox/bl00mbox_plugin_registry.c
@@ -91,11 +91,14 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index)
 #include "env_adsr.h"
 #include "ampliverter.h"
 #include "delay.h"
-//#include "filter.h"
+#include "lowpass.h"
 #include "sequencer.h"
 #include "sampler.h"
 #include "flanger.h"
 #include "noise.h"
+#include "distortion.h"
+#include "mixer.h"
+#include "slew_rate_limiter.h"
 
 void bl00mbox_plugin_registry_init(void){
     if(bl00mbox_plugin_registry_is_initialized) return;
@@ -103,9 +106,12 @@ void bl00mbox_plugin_registry_init(void){
     plugin_add(&ampliverter_desc);
     plugin_add(&env_adsr_desc);   
     plugin_add(&delay_desc);   
-//    plugin_add(&filter_desc);   
+    plugin_add(&lowpass_desc);   
     plugin_add(&sequencer_desc);
     plugin_add(&sampler_desc);
     plugin_add(&flanger_desc);
     plugin_add(&noise_desc);
+    plugin_add(&distortion_desc);
+    plugin_add(&mixer_desc);
+    plugin_add(&slew_rate_limiter_desc);
 }
diff --git a/components/bl00mbox/bl00mbox_radspa_requirements.c b/components/bl00mbox/bl00mbox_radspa_requirements.c
index dc86121be1530b8fbdea9619d67800ec33ef139b..e5310ff4c1db90d89ebd88b9b79e46bd86c7b154 100644
--- a/components/bl00mbox/bl00mbox_radspa_requirements.c
+++ b/components/bl00mbox/bl00mbox_radspa_requirements.c
@@ -68,7 +68,8 @@ int16_t radspa_clip(int32_t a){
 }
 
 int16_t radspa_add_sat(int32_t a, int32_t b){ return radspa_clip(a+b); }
-int16_t radspa_mult_shift(int32_t a, int32_t b){ return radspa_clip((a*b)>>15); }
+int32_t radspa_mult_shift(int32_t a, int32_t b){ return radspa_clip((a*b)>>15); }
+int32_t radspa_gain(int32_t a, int32_t b){ return radspa_clip((a*b)>>12); }
 
 int16_t radspa_trigger_start(int16_t velocity, int16_t * hist){
     int16_t ret = ((* hist) > 0) ? -velocity : velocity;
diff --git a/components/bl00mbox/bl00mbox_user.c b/components/bl00mbox/bl00mbox_user.c
index cf237406d04d35429ac92844c9daedd5f82055d4..e17eccfea9d66913b48a0d16f6fc0dbb060a3af1 100644
--- a/components/bl00mbox/bl00mbox_user.c
+++ b/components/bl00mbox/bl00mbox_user.c
@@ -654,6 +654,16 @@ char * bl00mbox_channel_bud_get_signal_name(uint8_t channel, uint32_t bud_index,
     return sig->name;
 }
 
+int8_t bl00mbox_channel_bud_get_signal_name_multiplex(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
+    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
+    if(chan == NULL) return false;
+    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
+    if(bud == NULL) return false;
+    radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
+    if(sig == NULL) return false;
+    return sig->name_multiplex;
+}
+
 char * bl00mbox_channel_bud_get_signal_description(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
     bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
     if(chan == NULL) return false;
diff --git a/components/bl00mbox/include/bl00mbox_user.h b/components/bl00mbox/include/bl00mbox_user.h
index 9e5fe3fb039b602846d8e1d1a6867399f39eabf8..23d0e6e171821b57394254c047ec3789c1b06b47 100644
--- a/components/bl00mbox/include/bl00mbox_user.h
+++ b/components/bl00mbox/include/bl00mbox_user.h
@@ -36,6 +36,7 @@ uint32_t bl00mbox_channel_bud_get_plugin_id(uint8_t channel, uint32_t bud_index)
 uint16_t bl00mbox_channel_bud_get_num_signals(uint8_t channel, uint32_t bud_index);
 
 char * bl00mbox_channel_bud_get_signal_name(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
+int8_t bl00mbox_channel_bud_get_signal_name_multiplex(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
 char * bl00mbox_channel_bud_get_signal_description(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
 char * bl00mbox_channel_bud_get_signal_unit(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
 bool bl00mbox_channel_bud_set_signal_value(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index, int16_t value);
diff --git a/components/bl00mbox/plugins/brok3n/filter.c b/components/bl00mbox/plugins/brok3n/filter.c
deleted file mode 100644
index d9cdd4ff830cc365cd72ab3e6620602db4ec7f1f..0000000000000000000000000000000000000000
--- a/components/bl00mbox/plugins/brok3n/filter.c
+++ /dev/null
@@ -1,141 +0,0 @@
-#include "filter.h"
-
-radspa_t * filter_create(uint32_t init_var);
-radspa_descriptor_t filter_desc = {
-    .name = "filter",
-    .id = 69420,
-    .description = "simple filter",
-    .create_plugin_instance = filter_create,
-    .destroy_plugin_instance = radspa_standard_plugin_destroy
-};
-
-#define FILTER_NUM_SIGNALS 5
-#define FILTER_OUTPUT 0
-#define FILTER_INPUT 1
-#define FILTER_FREQ 2
-#define FILTER_Q 3
-#define FILTER_GAIN 4
-
-
-static int16_t apply_filter(filter_data_t * data, int32_t input, int32_t gain){
-    data->pos++;
-    if(data->pos >= 3) data->pos = 0;
-
-	int64_t out = 0;
-    int64_t in_acc = input;
-
-	for(int8_t i=0; i<2; i++){
-        int8_t pos = data->pos - i - 1;
-        if(pos < 0) pos += 3;
-        in_acc += (2-i)*data->in_history[pos];
-
-        int16_t shift = data->out_coeff_shift[i] - data->in_coeff_shift;
-        if(shift >= 0){
-            out -= (data->out_history[pos] * data->out_coeff[i]) >> shift;
-        } else {
-            out -= (data->out_history[pos] * data->out_coeff[i]) << (-shift);
-        }
-    }
-    out += in_acc * data->in_coeff;
-    out = out >> data->in_coeff_shift;
-
-    data->in_history[data->pos] = input;
-    data->out_history[data->pos] = out;
-
-	return radspa_mult_shift(out, gain);
-}
-
-static uint8_t coeff_shift(float coeff) {
-    int32_t ret = 16;
-    while(1){
-        int32_t test_val = (1<<ret) * coeff;
-        if(test_val < 0) test_val = -test_val;
-        if(test_val > (1<<12)){
-            ret -= 3;
-        } else if (test_val < (1<<8)){
-            ret += 3;
-        } else {
-            break;
-        }
-        if(ret > 28) break;
-        if(ret < 4) break;
-    }
-    return ret;
-}
-
-static void set_filter_coeffs(filter_data_t * data, float freq, float q){
-    //molasses, sorry
-    if(freq == 0) return;
-    if(q == 0) return;
-    float K = 2*48000;
-    float omega = freq * 6.28;
-    float A[3];
-    A[0] = K*K;
-    A[1] = K*omega/q;
-    A[2] = omega*omega;
-    float B = omega*omega;
-
-    float sum_a = A[0] + A[1] + A[2];
-
-    float round;
-    int16_t shift;
-
-    round = B / sum_a;
-    shift = coeff_shift(round);
-    data->in_coeff = round * (1<<shift);
-    data->in_coeff_shift = shift;
-
-    round = 2.*(A[2]-A[0]) / sum_a;
-    shift = coeff_shift(round);
-    data->out_coeff[0] = round * (1<<shift);
-    data->out_coeff_shift[0] = shift;
-
-    round = (A[0]-A[1]+A[2]) / sum_a;
-    shift = coeff_shift(round);
-    data->out_coeff[1] = round * (1<<shift);
-    data->out_coeff_shift[1] = shift;
-}
-
-void filter_run(radspa_t * filter, uint16_t num_samples, uint32_t render_pass_id){
-    radspa_signal_t * output_sig = radspa_signal_get_by_index(filter, FILTER_OUTPUT);
-    if(output_sig->buffer == NULL) return;
-    filter_data_t * data = filter->plugin_data;
-    radspa_signal_t * input_sig = radspa_signal_get_by_index(filter, FILTER_INPUT);
-    radspa_signal_t * freq_sig = radspa_signal_get_by_index(filter, FILTER_FREQ);
-    radspa_signal_t * q_sig = radspa_signal_get_by_index(filter, FILTER_Q);
-    radspa_signal_t * gain_sig = radspa_signal_get_by_index(filter, FILTER_GAIN);
-
-    static int16_t ret = 0;
-    
-    for(uint16_t i = 0; i < num_samples; i++){
-        int16_t input = radspa_signal_get_value(input_sig, i, num_samples, render_pass_id);
-        int32_t freq = radspa_signal_get_value(freq_sig, i, num_samples, render_pass_id);
-        int16_t q = radspa_signal_get_value(q_sig, i, num_samples, render_pass_id);
-        int16_t gain = radspa_signal_get_value(gain_sig, i, num_samples, render_pass_id);
-
-        if((freq != data->prev_freq) | (q != data->prev_q)){
-            set_filter_coeffs(data, freq, ((float) q + 1.)/1000.);
-            data->prev_freq = freq;
-            data->prev_q = q;
-        }
-
-        ret = apply_filter(data, input, gain);
-        (output_sig->buffer)[i] = ret;
-    }
-    output_sig->value = ret;
-}
-
-radspa_t * filter_create(uint32_t real_init_var){
-    radspa_t * filter = radspa_standard_plugin_create(&filter_desc, FILTER_NUM_SIGNALS, sizeof(filter_data_t), 0);
-    if(filter == NULL) return NULL;
-    filter->render = filter_run;
-    radspa_signal_set(filter, FILTER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
-    radspa_signal_set(filter, FILTER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
-    radspa_signal_set(filter, FILTER_FREQ, "freq", RADSPA_SIGNAL_HINT_INPUT, 500);
-    radspa_signal_set(filter, FILTER_Q, "reso", RADSPA_SIGNAL_HINT_INPUT, 1000);
-    radspa_signal_set(filter, FILTER_GAIN, "gain", RADSPA_SIGNAL_HINT_INPUT, 32760);
-    filter_data_t * data = filter->plugin_data;
-    data->pos = 0;
-    data->prev_freq = 1<<24;
-    return filter;
-}
diff --git a/components/bl00mbox/plugins/brok3n/filter.h b/components/bl00mbox/plugins/brok3n/filter.h
deleted file mode 100644
index 36e3d1e93c06e5b01ef76a9e76a9360be44aa07f..0000000000000000000000000000000000000000
--- a/components/bl00mbox/plugins/brok3n/filter.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-#include <radspa.h>
-
-typedef struct {
-    int64_t in_history[3];
-    int64_t in_coeff;
-    int16_t in_coeff_shift;
-    int64_t out_history[3];
-    int64_t out_coeff[2];
-    int16_t out_coeff_shift[2];
-
-    int32_t prev_freq;
-    int16_t prev_q;
-    int8_t pos;
-} filter_data_t;
-
-extern radspa_descriptor_t filter_desc;
-radspa_t * filter_create(uint32_t init_var);
-void filter_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
diff --git a/components/bl00mbox/plugins/delay.c b/components/bl00mbox/plugins/delay.c
index 756e703d72df7d4228bc9f21632a1b839de702b3..d87218e4010a30ac382a5a46832eab586ae522c5 100644
--- a/components/bl00mbox/plugins/delay.c
+++ b/components/bl00mbox/plugins/delay.c
@@ -41,7 +41,9 @@ void delay_run(radspa_t * delay, uint16_t num_samples, uint32_t render_pass_id){
         data->write_head_position++;
         while(data->write_head_position >= buffer_size) data->write_head_position -= buffer_size; // maybe faster than %
         if(time != data->time_prev){
-            data->read_head_position = time * (48000/1000) + data->write_head_position;
+            data->read_head_position = data->write_head_position;
+            data->read_head_position = - time * (48000/1000);
+            if(data->read_head_position < 0) data->read_head_position += buffer_size;
             data->time_prev = time;
         } else {
             data->read_head_position++;
@@ -72,11 +74,13 @@ void delay_run(radspa_t * delay, uint16_t num_samples, uint32_t render_pass_id){
 
 radspa_t * delay_create(uint32_t init_var){
     if(init_var == 0) init_var = 500;
+    if(init_var > 10000) init_var = 10000;
     uint32_t buffer_size = init_var*(48000/1000);
     radspa_t * delay = radspa_standard_plugin_create(&delay_desc, DELAY_NUM_SIGNALS, sizeof(delay_data_t), buffer_size);
 
     if(delay == NULL) return NULL;
     delay_data_t * plugin_data = delay->plugin_data;
+    plugin_data->time_prev = UINT32_MAX;
     plugin_data->max_delay = init_var;
     delay->render = delay_run;
     radspa_signal_set(delay, DELAY_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
diff --git a/components/bl00mbox/plugins/delay.h b/components/bl00mbox/plugins/delay.h
index c0882a1085164432b99351c98871834d39e1ad94..9519edd44abe7a00de27111833be11f6dd2b021d 100644
--- a/components/bl00mbox/plugins/delay.h
+++ b/components/bl00mbox/plugins/delay.h
@@ -3,7 +3,7 @@
 #include <radspa_helpers.h>
 
 typedef struct {
-    uint32_t read_head_position;
+    int32_t read_head_position;
     uint32_t write_head_position;
     uint32_t max_delay;
     uint32_t time_prev;
diff --git a/components/bl00mbox/plugins/distortion.c b/components/bl00mbox/plugins/distortion.c
new file mode 100644
index 0000000000000000000000000000000000000000..4dfcb404b8be7c4a7656fa0b3240b70ed8bd78c7
--- /dev/null
+++ b/components/bl00mbox/plugins/distortion.c
@@ -0,0 +1,55 @@
+#include "distortion.h"
+
+radspa_t * distortion_create(uint32_t init_var);
+radspa_descriptor_t distortion_desc = {
+    .name = "distortion",
+    .id = 9000,
+    .description = "distortion with linear interpolation between int16 values in plugin_table[129]",
+    .create_plugin_instance = distortion_create,
+    .destroy_plugin_instance = radspa_standard_plugin_destroy
+};
+
+#define DISTORTION_NUM_SIGNALS 2
+#define DISTORTION_OUTPUT 0
+#define DISTORTION_INPUT 1
+
+void distortion_run(radspa_t * distortion, uint16_t num_samples, uint32_t render_pass_id){
+    radspa_signal_t * output_sig = radspa_signal_get_by_index(distortion, DISTORTION_OUTPUT);
+    if(output_sig->buffer == NULL) return;
+    int16_t * dist = distortion->plugin_table;
+    radspa_signal_t * input_sig = radspa_signal_get_by_index(distortion, DISTORTION_INPUT);
+
+    static int32_t ret = 0;
+    
+    for(uint16_t i = 0; i < num_samples; i++){
+
+        int32_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
+        input += 32768;
+        uint8_t index = input>>9;
+        int32_t blend = input & ((1<<7)-1);
+        ret = dist[index]*((1<<7)-blend) + dist[index+1]*blend;
+        ret = ret >> 7;
+        (output_sig->buffer)[i] = ret;
+    }
+    output_sig->value = ret;
+}
+
+radspa_t * distortion_create(uint32_t init_var){
+    radspa_t * distortion = radspa_standard_plugin_create(&distortion_desc, DISTORTION_NUM_SIGNALS, 0, (1<<7) + 1);
+    if(distortion == NULL) return NULL;
+    distortion->render = distortion_run;
+    radspa_signal_set(distortion, DISTORTION_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(distortion, DISTORTION_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
+
+    // prefill table with mild saturation
+    int16_t * dist = distortion->plugin_table;
+    for(int32_t i = 0; i < ((1<<7)+1) ; i++){
+        if(i < 64){
+            dist[i] = ((i*i)*32767>>12) - 32767;
+        } else {
+            dist[i] = -((128-i)*(128-i)*32767>>12) + 32767;
+        }
+    }
+
+    return distortion;
+}
diff --git a/components/bl00mbox/plugins/distortion.h b/components/bl00mbox/plugins/distortion.h
new file mode 100644
index 0000000000000000000000000000000000000000..87455bad8d1e3a903d0bea099254ec9a6c3029ab
--- /dev/null
+++ b/components/bl00mbox/plugins/distortion.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <radspa.h>
+#include <radspa_helpers.h>
+
+extern radspa_descriptor_t distortion_desc;
+radspa_t * distortion_create(uint32_t init_var);
+void distortion_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+
diff --git a/components/bl00mbox/plugins/flanger.c b/components/bl00mbox/plugins/flanger.c
index 085ff80b62f0537f0ef2decd9ae9f643712cd4a6..1f68d48188018f1bc8be76241d807383641cd13d 100644
--- a/components/bl00mbox/plugins/flanger.c
+++ b/components/bl00mbox/plugins/flanger.c
@@ -77,7 +77,7 @@ void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_
         int16_t dry_vol = (mix>0) ? (32767-mix) : (32767+mix); //always pos polarity
 
         ret = radspa_add_sat(radspa_mult_shift(dry, dry_vol), radspa_mult_shift(radspa_clip(wet), mix));
-        ret = radspa_clip(radspa_mult_shift(ret, level));
+        ret = radspa_clip(radspa_gain(ret, level));
         (output_sig->buffer)[i] = ret;
     }
     output_sig->value = ret;
@@ -93,8 +93,10 @@ radspa_t * flanger_create(uint32_t init_var){
     radspa_signal_set(flanger, FLANGER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
     radspa_signal_set(flanger, FLANGER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
     radspa_signal_set(flanger, FLANGER_MANUAL, "manual", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, 18367);
-    radspa_signal_set(flanger, FLANGER_RESONANCE, "resonance", RADSPA_SIGNAL_HINT_INPUT, 16000);
-    radspa_signal_set(flanger, FLANGER_LEVEL, "level", RADSPA_SIGNAL_HINT_INPUT, 16000);
+    radspa_signal_set(flanger, FLANGER_RESONANCE, "resonance", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN,
+                            RADSPA_SIGNAL_VAL_UNITY_GAIN/2);
+    radspa_signal_set(flanger, FLANGER_LEVEL, "level", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN,
+                            RADSPA_SIGNAL_VAL_UNITY_GAIN);
     radspa_signal_set(flanger, FLANGER_MIX, "mix", RADSPA_SIGNAL_HINT_INPUT, 1<<14);
     return flanger;
 }
diff --git a/components/bl00mbox/plugins/lowpass.c b/components/bl00mbox/plugins/lowpass.c
new file mode 100644
index 0000000000000000000000000000000000000000..f2087ed4880cf4acf394b0cb164a3245a5b9e285
--- /dev/null
+++ b/components/bl00mbox/plugins/lowpass.c
@@ -0,0 +1,140 @@
+#include "lowpass.h"
+
+radspa_t * lowpass_create(uint32_t init_var);
+radspa_descriptor_t lowpass_desc = {
+    .name = "lowpass",
+    .id = 69420,
+    .description = "2nd order lowpass lowpass, runs rly sluggish rn, sowy",
+    .create_plugin_instance = lowpass_create,
+    .destroy_plugin_instance = radspa_standard_plugin_destroy
+};
+
+#define LOWPASS_NUM_SIGNALS 5
+#define LOWPASS_OUTPUT 0
+#define LOWPASS_INPUT 1
+#define LOWPASS_FREQ 2
+#define LOWPASS_Q 3
+#define LOWPASS_GAIN 4
+
+static float apply_lowpass(lowpass_data_t * data, int32_t input){
+    data->pos++;
+    if(data->pos >= 3) data->pos = 0;
+
+	float out = 0;
+    float in_acc = input;
+
+	for(int8_t i = 0; i<2; i++){
+        int8_t pos = data->pos - i - 1;
+        if(pos < 0) pos += 3;
+        in_acc += (2-i)*data->in_history[pos];
+
+        out -= (data->out_history[pos] * data->out_coeff[i]);
+    }
+    out += in_acc * data->in_coeff;
+
+    data->in_history[data->pos] = input;
+    data->out_history[data->pos] = out;
+
+	return out;
+}
+
+static uint8_t coeff_shift(float coeff) {
+    int32_t ret = 16;
+    while(1){
+        int32_t test_val = (1<<ret) * coeff;
+        if(test_val < 0) test_val = -test_val;
+        if(test_val > (1<<12)){
+            ret -= 3;
+        } else if (test_val < (1<<8)){
+            ret += 3;
+        } else {
+            break;
+        }
+        if(ret > 28) break;
+        if(ret < 4) break;
+    }
+    return ret;
+}
+
+static void set_lowpass_coeffs(lowpass_data_t * data, float freq, float q){
+    //molasses, sorry
+    if(freq == 0) return;
+    if(q == 0) return;
+    float K = 2*48000;
+    float omega = freq * 6.28;
+    float A[3];
+    A[0] = K*K;
+    A[1] = K*omega/q;
+    A[2] = omega*omega;
+    float B = omega*omega;
+
+    float sum_a = A[0] + A[1] + A[2];
+
+    float round;
+    //int16_t shift;
+
+    round = B / sum_a;
+    //shift = coeff_shift(round);
+    data->in_coeff = round;// * (1<<shift);
+    //data->in_coeff_shift = shift;
+
+    round = 2.*(A[2]-A[0]) / sum_a;
+    //shift = coeff_shift(round);
+    data->out_coeff[0] = round;// * (1<<shift);
+    //data->out_coeff_shift[0] = shift;
+
+    round = (A[0]-A[1]+A[2]) / sum_a;
+    //shift = coeff_shift(round);
+    data->out_coeff[1] = round;// * (1<<shift);
+    //data->out_coeff_shift[1] = shift;
+}
+
+void lowpass_run(radspa_t * lowpass, uint16_t num_samples, uint32_t render_pass_id){
+    radspa_signal_t * output_sig = radspa_signal_get_by_index(lowpass, LOWPASS_OUTPUT);
+    if(output_sig->buffer == NULL) return;
+    lowpass_data_t * data = lowpass->plugin_data;
+    radspa_signal_t * input_sig = radspa_signal_get_by_index(lowpass, LOWPASS_INPUT);
+    radspa_signal_t * freq_sig = radspa_signal_get_by_index(lowpass, LOWPASS_FREQ);
+    radspa_signal_t * q_sig = radspa_signal_get_by_index(lowpass, LOWPASS_Q);
+    radspa_signal_t * gain_sig = radspa_signal_get_by_index(lowpass, LOWPASS_GAIN);
+
+    static int16_t ret = 0;
+    
+    for(uint16_t i = 0; i < num_samples; i++){
+        int16_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
+        int32_t freq = freq_sig->get_value(freq_sig, i, num_samples, render_pass_id);
+        int16_t q = q_sig->get_value(q_sig, i, num_samples, render_pass_id);
+        int16_t gain = gain_sig->get_value(gain_sig, i, num_samples, render_pass_id);
+
+        if((freq != data->prev_freq) | (q != data->prev_q)){
+            set_lowpass_coeffs(data, freq, ((float) q + 1.)/1000.);
+            data->prev_freq = freq;
+            data->prev_q = q;
+        }
+    
+        float out = apply_lowpass(data, input) + 0.5;
+        int16_t ret = radspa_clip(radspa_gain((int32_t) out, gain));
+        (output_sig->buffer)[i] = ret;
+    }
+    output_sig->value = ret;
+}
+
+radspa_t * lowpass_create(uint32_t real_init_var){
+    radspa_t * lowpass = radspa_standard_plugin_create(&lowpass_desc, LOWPASS_NUM_SIGNALS, sizeof(lowpass_data_t), 0);
+    if(lowpass == NULL) return NULL;
+    lowpass->render = lowpass_run;
+    radspa_signal_set(lowpass, LOWPASS_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(lowpass, LOWPASS_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
+    radspa_signal_set(lowpass, LOWPASS_FREQ, "freq", RADSPA_SIGNAL_HINT_INPUT, 500);
+    radspa_signal_set(lowpass, LOWPASS_Q, "reso", RADSPA_SIGNAL_HINT_INPUT, 1000);
+    radspa_signal_set(lowpass, LOWPASS_GAIN, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN,
+                                RADSPA_SIGNAL_VAL_UNITY_GAIN);
+    lowpass_data_t * data = lowpass->plugin_data;
+    data->pos = 0;
+    data->prev_freq = 1<<24;
+    for(uint8_t i = 0; i < 3;i++){
+        data->in_history[i] = 0.;
+        data->out_history[i] = 0.;
+    }
+    return lowpass;
+}
diff --git a/components/bl00mbox/plugins/lowpass.h b/components/bl00mbox/plugins/lowpass.h
new file mode 100644
index 0000000000000000000000000000000000000000..d0b138eebabc6edc3efbf9e89d54013a184b1667
--- /dev/null
+++ b/components/bl00mbox/plugins/lowpass.h
@@ -0,0 +1,20 @@
+#pragma once
+#include <radspa.h>
+#include <radspa_helpers.h>
+
+typedef struct {
+    float in_history[3];
+    float in_coeff;
+    //int16_t in_coeff_shift;
+    float out_history[3];
+    float out_coeff[2];
+    //int16_t out_coeff_shift[2];
+
+    int32_t prev_freq;
+    int16_t prev_q;
+    int8_t pos;
+} lowpass_data_t;
+
+extern radspa_descriptor_t lowpass_desc;
+radspa_t * lowpass_create(uint32_t init_var);
+void lowpass_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
diff --git a/components/bl00mbox/plugins/mixer.c b/components/bl00mbox/plugins/mixer.c
new file mode 100644
index 0000000000000000000000000000000000000000..501662c77a962cf313891cba77e3d38925505d73
--- /dev/null
+++ b/components/bl00mbox/plugins/mixer.c
@@ -0,0 +1,52 @@
+#include "mixer.h"
+
+radspa_descriptor_t mixer_desc = {
+    .name = "mixer",
+    .id = 21,
+    .description = "sums input and applies output gain\n  init_var: number of inputs, 1-127, default 4",
+    .create_plugin_instance = mixer_create,
+    .destroy_plugin_instance = radspa_standard_plugin_destroy
+};
+
+void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
+    radspa_signal_t * output_sig = radspa_signal_get_by_index(mixer, 0);
+    if(output_sig->buffer == NULL) return;
+    radspa_signal_t * gain_sig = radspa_signal_get_by_index(mixer, 1);
+
+    uint8_t num_inputs = mixer->len_signals - 2;
+    radspa_signal_t * input_sigs[num_inputs];
+    for(uint8_t i = 0; i < num_inputs; i++){
+        input_sigs[i] = radspa_signal_get_by_index(mixer, 2 + i);
+    }
+
+    int32_t * dc_acc = mixer->plugin_data;
+
+    static int32_t ret = 0;
+    for(uint16_t i = 0; i < num_samples; i++){
+        int16_t gain = gain_sig->get_value(gain_sig, i, num_samples, render_pass_id);
+        ret = 0;
+        for(uint8_t j = 0; j < num_inputs; j++){
+            ret += input_sigs[j]->get_value(input_sigs[j], i, num_samples, render_pass_id);
+        }
+        // remove dc
+        (* dc_acc) = (ret + (* dc_acc)*1023) >> 10;
+        ret -= (* dc_acc);
+        (output_sig->buffer)[i] = radspa_clip(radspa_gain(ret, gain));
+    }
+    output_sig->value = ret;
+}
+
+radspa_t * mixer_create(uint32_t init_var){
+    if(init_var == 0) init_var = 4;
+    if(init_var > 127) init_var = 127;
+    radspa_t * mixer = radspa_standard_plugin_create(&mixer_desc, 2 + init_var, sizeof(int32_t), 0);
+    if(mixer == NULL) return NULL;
+    mixer->render = mixer_run;
+    radspa_signal_set(mixer, 0, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    int16_t gain = RADSPA_SIGNAL_VAL_UNITY_GAIN/init_var;
+    radspa_signal_set(mixer, 1, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, gain);
+    radspa_signal_set_group(mixer, init_var, 2, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
+    int32_t * dc_acc = mixer->plugin_data;
+    (* dc_acc) = 0;
+    return mixer;
+}
diff --git a/components/bl00mbox/plugins/mixer.h b/components/bl00mbox/plugins/mixer.h
new file mode 100644
index 0000000000000000000000000000000000000000..79d6236c2b496fca4c0976c0cec8ec4e568d70f4
--- /dev/null
+++ b/components/bl00mbox/plugins/mixer.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <radspa.h>
+#include <radspa_helpers.h>
+
+extern radspa_descriptor_t mixer_desc;
+radspa_t * mixer_create(uint32_t init_var);
+void mixer_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+
diff --git a/components/bl00mbox/plugins/noise.c b/components/bl00mbox/plugins/noise.c
index 3c0cb2bed7d02d166037defb0a16aeac1c13767f..364456f5178c88d3f928cf9824f76aee20766553 100644
--- a/components/bl00mbox/plugins/noise.c
+++ b/components/bl00mbox/plugins/noise.c
@@ -31,6 +31,3 @@ radspa_t * noise_create(uint32_t init_var){
     radspa_signal_set(noise, NOISE_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
     return noise;
 }
-
-#undef NOISE_NUM_SIGNALS
-#undef NOISE_OUTPUT
diff --git a/components/bl00mbox/plugins/osc_fm.c b/components/bl00mbox/plugins/osc_fm.c
index ed525eec33665b76c1051576a784915efadfadcc..750a8ee3bb575792fce34fbcea2abc8e423bb724 100644
--- a/components/bl00mbox/plugins/osc_fm.c
+++ b/components/bl00mbox/plugins/osc_fm.c
@@ -66,7 +66,7 @@ static inline int16_t triangle(int16_t saw){
     return tmp;
 }
 
-inline int16_t waveshaper(int16_t saw, int16_t shape){
+static inline int16_t waveshaper(int16_t saw, int16_t shape){
     int32_t tmp = saw;
     uint8_t sh = ((uint16_t) shape) >> 14;
     sh = (sh + 2)%4;
diff --git a/components/bl00mbox/plugins/slew_rate_limiter.c b/components/bl00mbox/plugins/slew_rate_limiter.c
new file mode 100644
index 0000000000000000000000000000000000000000..f1907b7c74dcd6995d6011fcc927f9354acdfaee
--- /dev/null
+++ b/components/bl00mbox/plugins/slew_rate_limiter.c
@@ -0,0 +1,51 @@
+#include "slew_rate_limiter.h"
+
+static inline int16_t waveshaper(int16_t saw, int16_t shape);
+
+radspa_descriptor_t slew_rate_limiter_desc = {
+    .name = "slew_rate_limiter",
+    .id = 23,
+    .description = "very cheap nonlinear filter",
+    .create_plugin_instance = slew_rate_limiter_create,
+    .destroy_plugin_instance = radspa_standard_plugin_destroy
+};
+
+#define SLEW_RATE_LIMITER_NUM_SIGNALS 3
+#define SLEW_RATE_LIMITER_OUTPUT 0
+#define SLEW_RATE_LIMITER_INPUT 1
+#define SLEW_RATE_LIMITER_SLEW_RATE 2
+
+radspa_t * slew_rate_limiter_create(uint32_t init_var){
+    radspa_t * slew_rate_limiter = radspa_standard_plugin_create(&slew_rate_limiter_desc, SLEW_RATE_LIMITER_NUM_SIGNALS, sizeof(slew_rate_limiter_data_t), 0);
+    slew_rate_limiter->render = slew_rate_limiter_run;
+    radspa_signal_set(slew_rate_limiter, SLEW_RATE_LIMITER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(slew_rate_limiter, SLEW_RATE_LIMITER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
+    radspa_signal_set(slew_rate_limiter, SLEW_RATE_LIMITER_SLEW_RATE, "slew_rate", RADSPA_SIGNAL_HINT_INPUT, 1000);
+    return slew_rate_limiter;
+}
+
+void slew_rate_limiter_run(radspa_t * slew_rate_limiter, uint16_t num_samples, uint32_t render_pass_id){
+    radspa_signal_t * output_sig = radspa_signal_get_by_index(slew_rate_limiter, SLEW_RATE_LIMITER_OUTPUT);
+    if(output_sig->buffer == NULL) return;
+    radspa_signal_t * input_sig = radspa_signal_get_by_index(slew_rate_limiter, SLEW_RATE_LIMITER_INPUT);
+    radspa_signal_t * slew_rate_sig = radspa_signal_get_by_index(slew_rate_limiter, SLEW_RATE_LIMITER_SLEW_RATE);
+
+    slew_rate_limiter_data_t * data = slew_rate_limiter->plugin_data;
+
+    int32_t ret = 0;
+    for(uint16_t i = 0; i < num_samples; i++){
+        int32_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
+        int32_t slew_rate = (uint16_t) slew_rate_sig->get_value(slew_rate_sig, i, num_samples, render_pass_id);
+        ret = data->prev;
+        if(input - ret > slew_rate){
+            ret += slew_rate;
+        } else if(ret - input > slew_rate){
+            ret -= slew_rate;
+        } else {
+            ret = input;
+        }
+        (output_sig->buffer)[i] = ret;
+    }
+    output_sig->value = ret;
+}
+
diff --git a/components/bl00mbox/plugins/slew_rate_limiter.h b/components/bl00mbox/plugins/slew_rate_limiter.h
new file mode 100644
index 0000000000000000000000000000000000000000..4c92240b201abab055a92d0493dfa36f3ee54988
--- /dev/null
+++ b/components/bl00mbox/plugins/slew_rate_limiter.h
@@ -0,0 +1,12 @@
+#pragma once
+#include "radspa.h"
+#include "radspa_helpers.h"
+
+typedef struct {
+    int32_t prev;
+} slew_rate_limiter_data_t;
+
+extern radspa_descriptor_t slew_rate_limiter_desc;
+radspa_t * slew_rate_limiter_create(uint32_t init_var);
+void slew_rate_limiter_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+
diff --git a/components/bl00mbox/radspa/radspa.h b/components/bl00mbox/radspa/radspa.h
index 5ad964d53b376cc65ed7437a615169965503b1fc..6e018c4a1a9747a75b3a8e4f22ef9ecc3bb63e5b 100644
--- a/components/bl00mbox/radspa/radspa.h
+++ b/components/bl00mbox/radspa/radspa.h
@@ -41,11 +41,11 @@
 #define RADSPA_SIGNAL_HINT_INPUT (1<<0)
 #define RADSPA_SIGNAL_HINT_OUTPUT (1<<1)
 #define RADSPA_SIGNAL_HINT_TRIGGER (1<<2)
-#define RADSPA_SIGNAL_HINT_VOL (1<<3)
+#define RADSPA_SIGNAL_HINT_GAIN (1<<3)
 #define RADSPA_SIGNAL_HINT_SCT (1<<5)
 
 #define RADSPA_SIGNAL_VAL_SCT_A440 (INT16_MAX - 6*2400)
-#define RADSPA_SIGNAL_VAL_UNITY_GAIN (1<<11)
+#define RADSPA_SIGNAL_VAL_UNITY_GAIN (1<<12)
 
 struct _radspa_descriptor_t;
 struct _radspa_signal_t;
@@ -115,7 +115,9 @@ extern int16_t radspa_clip(int32_t a);
 // saturating int16 addition
 extern int16_t radspa_add_sat(int32_t a, int32_t b);
 // (a*b)>>15
-extern int16_t radspa_mult_shift(int32_t a, int32_t b);
+extern int32_t radspa_mult_shift(int32_t a, int32_t b);
+// (a*b)>>12
+extern int32_t radspa_gain(int32_t a, int32_t b);
 
 extern int16_t radspa_trigger_start(int16_t velocity, int16_t * hist);
 extern int16_t radspa_trigger_stop(int16_t * hist);
diff --git a/components/bl00mbox/radspa/radspa_helpers.c b/components/bl00mbox/radspa/radspa_helpers.c
index a557e51b831cdbf94362a2bffdc3cbcbed8fbe0e..4d9f3d01d9a1d7be14eabb728cb09d3cc6b78f67 100644
--- a/components/bl00mbox/radspa/radspa_helpers.c
+++ b/components/bl00mbox/radspa/radspa_helpers.c
@@ -18,6 +18,18 @@ void radspa_signal_set(radspa_t * plugin, uint8_t signal_index, char * name, uin
     sig->value = value;
 }
 
+void radspa_signal_set_group(radspa_t * plugin, uint8_t group_len, uint8_t signal_index, char * name,
+                                    uint32_t hints, int16_t value){
+    for(uint8_t i = 0; i < group_len; i++){
+        radspa_signal_t * sig = radspa_signal_get_by_index(plugin, signal_index + i);
+        if(sig == NULL) return;
+        sig->name = name;
+        sig->hints = hints;
+        sig->value = value;
+        sig->name_multiplex = i;
+    }
+}
+
 int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_t value){
     radspa_signal_t * sig = calloc(1,sizeof(radspa_signal_t));
     if(sig == NULL) return -1; // allocation failed
diff --git a/components/bl00mbox/radspa/radspa_helpers.h b/components/bl00mbox/radspa/radspa_helpers.h
index e1b9fa0eb852a38869384d6ee1161e7bb23a9557..11b9709ac5ae220e25891fd192a2129880cdc103 100644
--- a/components/bl00mbox/radspa/radspa_helpers.h
+++ b/components/bl00mbox/radspa/radspa_helpers.h
@@ -6,10 +6,15 @@
 int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_t value);
 // as above, but sets parameters of an already existing signal with at list position signal_index
 void radspa_signal_set(radspa_t * plugin, uint8_t signal_index, char * name, uint32_t hints, int16_t value);
+void radspa_signal_set_group(radspa_t * plugin, uint8_t group_len, uint8_t signal_index, char * name,
+                                    uint32_t hints, int16_t value);
+
+
 // get signal struct from a signal index
 radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t signal_index);
 
-radspa_t * radspa_standard_plugin_create(radspa_descriptor_t * desc, uint8_t num_signals, size_t plugin_data_size, uint32_t plugin_table_size);
+radspa_t * radspa_standard_plugin_create(radspa_descriptor_t * desc, uint8_t num_signals, size_t plugin_data_size,
+                                                uint32_t plugin_table_size);
 void radspa_standard_plugin_destroy(radspa_t * plugin);
 
 // frees all signal structs. typically used to destroy a plugin instance.
diff --git a/components/micropython/usermodule/mp_sys_bl00mbox.c b/components/micropython/usermodule/mp_sys_bl00mbox.c
index 40668da111bcbdadc6b72b8ffe31261ffbbea9a5..ecd10b25f880c00e2c83a990cec461b4aed094b8 100644
--- a/components/micropython/usermodule/mp_sys_bl00mbox.c
+++ b/components/micropython/usermodule/mp_sys_bl00mbox.c
@@ -7,6 +7,7 @@
 #include "bl00mbox.h"
 #include "bl00mbox_plugin_registry.h"
 #include "bl00mbox_user.h"
+#include "radspa.h"
 
 // ========================
 //    PLUGIN OPERATIONS
@@ -233,6 +234,16 @@ STATIC mp_obj_t mp_channel_bud_get_signal_name(mp_obj_t chan, mp_obj_t bud,
 STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_name_obj,
                                  mp_channel_bud_get_signal_name);
 
+STATIC mp_obj_t mp_channel_bud_get_signal_name_multiplex(mp_obj_t chan,
+                                                         mp_obj_t bud,
+                                                         mp_obj_t signal) {
+    int8_t ret = bl00mbox_channel_bud_get_signal_name_multiplex(
+        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
+    return mp_obj_new_int(ret);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_name_multiplex_obj,
+                                 mp_channel_bud_get_signal_name_multiplex);
+
 STATIC mp_obj_t mp_channel_bud_get_signal_description(mp_obj_t chan,
                                                       mp_obj_t bud,
                                                       mp_obj_t signal) {
@@ -488,6 +499,8 @@ STATIC const mp_map_elem_t bl00mbox_globals_table[] = {
     // SIGNAL OPERATIONS
     { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_name),
       MP_ROM_PTR(&mp_channel_bud_get_signal_name_obj) },
+    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_name_multiplex),
+      MP_ROM_PTR(&mp_channel_bud_get_signal_name_multiplex_obj) },
     { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_description),
       MP_ROM_PTR(&mp_channel_bud_get_signal_description_obj) },
     { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_unit),
@@ -531,6 +544,12 @@ STATIC const mp_map_elem_t bl00mbox_globals_table[] = {
 
     // CONSTANTS
     { MP_ROM_QSTR(MP_QSTR_NUM_CHANNELS), MP_ROM_INT(BL00MBOX_CHANNELS) },
+    { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_SCT),
+      MP_ROM_INT(RADSPA_SIGNAL_HINT_SCT) },
+    { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_GAIN),
+      MP_ROM_INT(RADSPA_SIGNAL_HINT_GAIN) },
+    { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_TRIGGER),
+      MP_ROM_INT(RADSPA_SIGNAL_HINT_TRIGGER) },
 };
 
 STATIC MP_DEFINE_CONST_DICT(mp_module_bl00mbox_globals, bl00mbox_globals_table);
diff --git a/python_payload/apps/shoegaze/__init__.py b/python_payload/apps/shoegaze/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7726f43a72f81a70bb36158b3745a5455e0fa738
--- /dev/null
+++ b/python_payload/apps/shoegaze/__init__.py
@@ -0,0 +1,208 @@
+from st3m.application import Application, ApplicationContext
+from st3m.goose import Dict, Any, List
+from st3m.input import InputState
+from ctx import Context
+
+import json
+import math
+import captouch
+import bl00mbox
+
+import leds
+import hardware
+
+chords = [
+    [-5, -5, 2, 7, 10],
+    [-4, -4, 0, 5, 8],
+    [-10, -2, 5, 8, 12],
+    [-12, 0, 3, 10, 15],
+    [-9, 3, 7, 14, 15],
+]
+
+from st3m.application import Application, ApplicationContext
+
+
+class ShoegazeApp(Application):
+    def __init__(self, app_ctx: ApplicationContext) -> None:
+        super().__init__(app_ctx)
+
+        self.blm = bl00mbox.Channel()
+
+        self.delay_on = True
+        self.fuzz_on = True
+        self.chord_index = 0
+        self.chord: List[int] = []
+        self.main_lp = self.blm.new(bl00mbox.plugins.lowpass)
+        self.main_fuzz = self.blm.new(bl00mbox.patches.fuzz)
+        self.main_mixer = self.blm.new(bl00mbox.plugins.mixer, 2)
+        self.git_strings = [
+            self.blm.new(bl00mbox.patches.karplus_strong) for i in range(4)
+        ]
+        self.bass_string = self.blm.new(bl00mbox.patches.karplus_strong)
+        self.git_mixer = self.blm.new(bl00mbox.plugins.mixer, 4)
+        self.git_fuzz = self.blm.new(bl00mbox.patches.fuzz)
+        self.git_delay = self.blm.new(bl00mbox.plugins.delay)
+        self.git_lp = self.blm.new(bl00mbox.plugins.lowpass)
+        self.bass_lp = self.blm.new(bl00mbox.plugins.lowpass)
+
+        self.git_mixer.signals.input0 = self.git_strings[0].signals.output
+        self.git_mixer.signals.input1 = self.git_strings[1].signals.output
+        self.git_mixer.signals.input2 = self.git_strings[2].signals.output
+        self.git_mixer.signals.input3 = self.git_strings[3].signals.output
+        self.git_mixer.signals.output = self.git_lp.signals.input
+        self.git_fuzz.buds.dist.signals.input = self.git_lp.signals.output
+
+        self.bass_lp.signals.input = self.bass_string.signals.output
+        self.main_mixer.signals.input1 = self.bass_lp.signals.output
+
+        self.main_fuzz.buds.dist.signals.input = self.main_mixer.signals.output
+        self.main_fuzz.buds.dist.signals.output = self.main_lp.signals.input
+        self.main_lp.signals.output = self.blm.mixer
+
+        self.git_delay.signals.time = 200
+        self.git_delay.signals.dry_vol = 32767
+        self.git_delay.signals.level = 16767
+        self.git_delay.signals.feedback = 27000
+
+        self.git_fuzz.intensity = 8
+        self.git_fuzz.gate = 1500
+        self.git_fuzz.volume = 12000
+        self.git_lp.signals.freq = 700
+        self.git_lp.signals.reso = 2500
+
+        self.bass_lp.signals.freq = 400
+        self.bass_lp.signals.reso = 3000
+        self.main_fuzz.intensity = 8
+        self.main_fuzz.gate = 1500
+        self.main_fuzz.volume = 32000
+
+        self.main_mixer.signals.gain = 2000
+        self.main_lp.signals.reso = 2000
+
+        self.cp_prev = captouch.read()
+
+        self._set_chord(3)
+        self.prev_captouch = [0] * 10
+        self._tilt_bias = 0.0
+        self._detune_prev = 0.0
+        self._git_string_tuning = [0] * 4
+        self._button_prev = 0
+        self._update_connections()
+
+    def _update_connections(self) -> None:
+        if self.fuzz_on:
+            self.bass_lp.signals.gain = 32767
+            self.git_lp.signals.gain = 32767
+            self.main_lp.signals.freq = 2500
+            self.main_lp.signals.gain = 2000
+            self.git_mixer.signals.gain = 4000
+            self.main_lp.signals.input = self.main_fuzz.buds.dist.signals.output
+            if self.delay_on:
+                self.git_delay.signals.input = self.git_fuzz.buds.dist.signals.output
+                self.main_mixer.signals.input0 = self.git_delay.signals.output
+            else:
+                self.main_mixer.signals.input0 = self.git_fuzz.buds.dist.signals.output
+        else:
+            self.bass_lp.signals.gain = 2000
+            self.git_lp.signals.gain = 2000
+            self.main_lp.signals.freq = 6000
+            self.main_lp.signals.gain = 4000
+            self.git_mixer.signals.gain = 500
+            self.main_lp.signals.input = self.main_mixer.signals.output
+            if self.delay_on:
+                self.git_delay.signals.input = self.git_lp.signals.output
+                self.main_mixer.signals.input0 = self.git_delay.signals.output
+            else:
+                self.main_mixer.signals.input0 = self.git_lp.signals.output
+
+    def fuzz_toggle(self) -> None:
+        self.fuzz_on = not self.fuzz_on
+        self._update_connections()
+
+    def delay_toggle(self) -> None:
+        self.delay_on = not self.delay_on
+        self._update_connections()
+
+    def _set_chord(self, i: int) -> None:
+        hue = int(72 * (i + 0.5)) % 360
+        if i != self.chord_index:
+            self.chord_index = i
+            for j in range(40):
+                leds.set_hsv(j, hue, 1, 0.2)
+            self.chord = chords[i]
+            leds.update()
+
+    def draw(self, ctx: Context) -> None:
+        ctx.text_align = ctx.CENTER
+        ctx.font = ctx.get_font_name(5)
+        ctx.font_size = 30
+
+        ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
+
+        ctx.move_to(60, 60)
+        if self.delay_on:
+            ctx.rgb(0, 255, 0)
+            ctx.text("delay ON!")
+        else:
+            ctx.rgb(255, 0, 0)
+            ctx.text("delay off")
+
+        ctx.move_to(50, -50)
+        detune = "detune: " + str(int(self._detune_prev * 100))
+        ctx.rgb(0, 255, 0)
+        ctx.text(detune).rotate(-10)
+
+        ctx.move_to(-50, 50)
+        if self.fuzz_on:
+            ctx.rgb(0, 255, 0)
+            ctx.text("fuzz ON!")
+        else:
+            ctx.rgb(255, 0, 0)
+            ctx.text("fuzz off")
+        ctx.restore()
+
+    def think(self, ins: InputState, delta_ms: int) -> None:
+        super().think(ins, delta_ms)
+
+        y = ins.imu.acc[0]
+        x = ins.imu.acc[1]
+        tilt = (y**2) + (x**2)
+        tilt = tilt / 50
+        self._tilt_bias = 0.99 * self._tilt_bias + 0.01 * tilt
+        detune = (tilt - self._tilt_bias) * 0.5 + self._detune_prev * 0.5
+        self._detune_prev = detune
+
+        cts = captouch.read()
+        button = ins.left_button
+        if button != self._button_prev:
+            if button == 1:
+                self.delay_toggle()
+            if button == -1:
+                pass
+                # self.fuzz_toggle()
+            self._button_prev = button
+
+        for i in range(1, 10, 2):
+            if cts.petals[i].pressed and (not self.cp_prev.petals[i].pressed):
+                k = int((i - 1) / 2)
+                self._set_chord(k)
+
+        for i in range(2, 10, 2):
+            k = int(i / 2) - 1
+            if cts.petals[i].pressed and (not self.cp_prev.petals[i].pressed):
+                self._git_string_tuning[k] = self.chord[k] - 12
+                self.git_strings[k].signals.pitch.tone = self._git_string_tuning[k]
+                self.git_strings[k].decay = 3000
+                self.git_strings[k].signals.trigger.start()
+
+            self.git_strings[k].signals.pitch.tone = self._git_string_tuning[k] + detune
+
+        if cts.petals[0].pressed and (not self.cp_prev.petals[0].pressed):
+            self.bass_string.signals.pitch.tone = self.chord[0] - 24
+            self.bass_string.decay = 1000
+            self.bass_string.signals.trigger.start()
+
+        self.cp_prev = cts
+
+    def __del__(self) -> None:
+        self.blm.clear()
diff --git a/python_payload/apps/shoegaze/flow3r.toml b/python_payload/apps/shoegaze/flow3r.toml
new file mode 100644
index 0000000000000000000000000000000000000000..067e5746f57e754538b51f72f87603df224bfbc4
--- /dev/null
+++ b/python_payload/apps/shoegaze/flow3r.toml
@@ -0,0 +1,11 @@
+[app]
+name = "shoegaze"
+menu = "Music"
+
+[entry]
+class = "ShoegazeApp"
+
+[metadata]
+author = "Flow3r Badge Authors"
+license = "LGPL-3.0-only"
+url = "https://git.flow3r.garden/flow3r/flow3r-firmware"
diff --git a/python_payload/bl00mbox/__init__.py b/python_payload/bl00mbox/__init__.py
index 92acbc2a6557dd7215a1e118ff9908e5e7b83399..6f4058f79d0c57320640a8c483e797c273f29cc1 100644
--- a/python_payload/bl00mbox/__init__.py
+++ b/python_payload/bl00mbox/__init__.py
@@ -1,519 +1,6 @@
 # SPDX-License-Identifier: CC0-1.0
 
-import sys_bl00mbox
-
-# note: consider the 'sys_bl00mbox' api super unstable for now pls :3
-import math
-
+from bl00mbox._user import *
 import bl00mbox._patches as patches
 import bl00mbox._helpers as helpers
-from bl00mbox._patches import _Patch as PatchType
 from bl00mbox._plugins import plugins
-from bl00mbox._plugins import _Plugin as PluginType
-
-
-class Bl00mboxError(Exception):
-    pass
-
-
-def _makeSignal(bud, signal_num):
-    hints = sys_bl00mbox.channel_bud_get_signal_hints(
-        bud.channel_num, bud.bud_num, signal_num
-    )
-    if hints & 2:
-        signal = SignalOutput(bud, signal_num)
-        signal._hints = "output"
-    elif hints & 1:
-        if hints & 4:
-            signal = SignalInputTrigger(bud, signal_num)
-            signal._hints = "input/trigger"
-        elif hints & 32:
-            signal = SignalInputPitch(bud, signal_num)
-            signal._hints = "input/pitch"
-        else:
-            signal = SignalInput(bud, signal_num)
-            signal._hints = "input"
-    return signal
-
-
-class ChannelMixer:
-    def __init__(self, channel):
-        self._channel = channel
-        pass
-
-    def __repr__(self):
-        ret = "[channel mixer]"
-        ret += " (" + str(len(self.connections)) + " connections)"
-        for con in self.connections:
-            ret += "\n  " + con.name
-            ret += " in [bud " + str(con._bud.bud_num) + "] " + con._bud.name
-        return ret
-
-    def _unplug_all(self):
-        # TODO
-        pass
-
-    @property
-    def connections(self):
-        ret = []
-        for i in range(sys_bl00mbox.channel_mixer_num(self._channel.channel_num)):
-            b = sys_bl00mbox.channel_get_bud_by_mixer_list_pos(
-                self._channel.channel_num, i
-            )
-            s = sys_bl00mbox.channel_get_signal_by_mixer_list_pos(
-                self._channel.channel_num, i
-            )
-            sig = Signal(Bud(self._channel, 0, bud_num=b), s)
-            ret += [sig]
-        return ret
-
-
-class Signal:
-    def __init__(self, bud, signal_num):
-        self._bud = bud
-        self._signal_num = signal_num
-        self._name = sys_bl00mbox.channel_bud_get_signal_name(
-            bud.channel_num, bud.bud_num, signal_num
-        )
-        self._description = sys_bl00mbox.channel_bud_get_signal_description(
-            bud.channel_num, bud.bud_num, signal_num
-        )
-        self._unit = sys_bl00mbox.channel_bud_get_signal_unit(
-            bud.channel_num, bud.bud_num, signal_num
-        )
-        self._hints = ""
-
-    def __repr__(self):
-        self._bud._check_existence()
-
-        ret = self.name
-        if len(self.unit):
-            ret += " [" + self.unit + "]"
-        ret += " [" + self.hints + "]: "
-        conret = []
-        direction = " <?> "
-        if isinstance(self, SignalInput):
-            direction = " <= "
-            if len(self.connections):
-                ret += str(self.connections[0].value)
-            else:
-                ret += str(self.value)
-        elif isinstance(self, SignalOutput):
-            direction = " => "
-            ret += str(self.value)
-
-        for con in self.connections:
-            if isinstance(con, Signal):
-                conret += [
-                    direction
-                    + con.name
-                    + " in [bud "
-                    + str(con._bud.bud_num)
-                    + "] "
-                    + con._bud.name
-                ]
-            if isinstance(con, ChannelMixer):
-                conret += [" ==> [channel mixer]"]
-        nl = "\n"
-        if len(conret) > 1:
-            ret += "\n"
-            nl += "  "
-        ret += nl.join(conret)
-        return ret
-
-    @property
-    def name(self):
-        return self._name
-
-    @property
-    def description(self):
-        return self._description
-
-    @property
-    def unit(self):
-        return self._unit
-
-    @property
-    def hints(self):
-        return self._hints
-
-    @property
-    def connections(self):
-        return []
-
-    @property
-    def value(self):
-        self._bud._check_existence()
-        return sys_bl00mbox.channel_bud_get_signal_value(
-            self._bud.channel_num, self._bud.bud_num, self._signal_num
-        )
-
-
-class SignalOutput(Signal):
-    @Signal.value.setter
-    def value(self, val):
-        if val == None:
-            sys_bl00mbox.channel_disconnect_signal_tx(
-                self._bud.channel_num, self._bud.bud_num, self._signal_num
-            )
-        elif isinstance(val, SignalInput):
-            val.value = self
-        elif isinstance(val, ChannelMixer):
-            if val._channel.channel_num == self._bud.channel_num:
-                sys_bl00mbox.channel_connect_signal_to_output_mixer(
-                    self._bud.channel_num, self._bud.bud_num, self._signal_num
-                )
-
-    @property
-    def connections(self):
-        cons = []
-        chan = self._bud.channel_num
-        bud = self._bud.bud_num
-        sig = self._signal_num
-        for i in range(sys_bl00mbox.channel_subscriber_num(chan, bud, sig)):
-            b = sys_bl00mbox.channel_get_bud_by_subscriber_list_pos(chan, bud, sig, i)
-            s = sys_bl00mbox.channel_get_signal_by_subscriber_list_pos(
-                chan, bud, sig, i
-            )
-            if (s >= 0) and (b > 0):
-                cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)]
-            elif s == -1:
-                cons += [ChannelMixer(Channel(chan))]
-        return cons
-
-
-class SignalInput(Signal):
-    @Signal.value.setter
-    def value(self, val):
-        self._bud._check_existence()
-        if isinstance(val, SignalOutput):
-            if len(self.connections):
-                if not sys_bl00mbox.channel_disconnect_signal_rx(
-                    self._bud.channel_num, self._bud.bud_num, self._signal_num
-                ):
-                    return
-            sys_bl00mbox.channel_connect_signal(
-                self._bud.channel_num,
-                self._bud.bud_num,
-                self._signal_num,
-                val._bud.bud_num,
-                val._signal_num,
-            )
-        elif isinstance(val, SignalInput):
-            # TODO
-            pass
-        elif (type(val) == int) or (type(val) == float):
-            if len(self.connections):
-                if not sys_bl00mbox.channel_disconnect_signal_rx(
-                    self._bud.channel_num, self._bud.bud_num, self._signal_num
-                ):
-                    return
-            sys_bl00mbox.channel_bud_set_signal_value(
-                self._bud.channel_num, self._bud.bud_num, self._signal_num, int(val)
-            )
-
-    @property
-    def connections(self):
-        cons = []
-        chan = self._bud.channel_num
-        bud = self._bud.bud_num
-        sig = self._signal_num
-        b = sys_bl00mbox.channel_get_source_bud(chan, bud, sig)
-        s = sys_bl00mbox.channel_get_source_signal(chan, bud, sig)
-        if (s >= 0) and (b > 0):
-            cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)]
-        return cons
-
-
-class SignalInputTrigger(SignalInput):
-    def start(self, velocity=32767):
-        if self.value > 0:
-            self.value = -velocity
-        else:
-            self.value = velocity
-
-    def stop(self):
-        self.value = 0
-
-
-class SignalInputPitch(SignalInput):
-    def __init__(self, bud, signal_num):
-        SignalInput.__init__(self, bud, signal_num)
-
-    @property
-    def tone(self):
-        return (self.value - (32767 - 2400 * 6)) / 200
-
-    @tone.setter
-    def tone(self, val):
-        if (type(val) == int) or (type(val) == float):
-            self.value = (32767 - 2400 * 6) + 200 * val
-        if type(val) == str:
-            self.value = helpers.note_name_to_sct(val)
-
-    @property
-    def freq(self):
-        tone = (self.value - (32767 - 2400 * 6)) / 200
-        return 440 * (2 ** (tone / 12))
-
-    @freq.setter
-    def freq(self, val):
-        tone = 12 * math.log(val / 440, 2)
-        self.value = (32767 - 2400 * 6) + 200 * tone
-
-    def __repr__(self):
-        ret = SignalInput.__repr__(self)
-        ret += " / " + str(self.tone) + " semitones / " + str(self.freq) + "Hz"
-        return ret
-
-
-class SignalList:
-    def __init__(self, bud):
-        self._list = []
-        for signal_num in range(
-            sys_bl00mbox.channel_bud_get_num_signals(bud.channel_num, bud.bud_num)
-        ):
-            hints = sys_bl00mbox.channel_bud_get_signal_hints(
-                bud.channel_num, bud.bud_num, signal_num
-            )
-            if hints & 2:
-                signal = SignalOutput(bud, signal_num)
-                signal._hints = "output"
-            elif hints & 1:
-                if hints & 4:
-                    signal = SignalInputTrigger(bud, signal_num)
-                    signal._hints = "input/trigger"
-                elif hints & 32:
-                    signal = SignalInputPitch(bud, signal_num)
-                    signal._hints = "input/pitch"
-                else:
-                    signal = SignalInput(bud, signal_num)
-                    signal._hints = "input"
-            self._list += [signal]
-            setattr(self, signal.name.split(" ")[0], signal)
-
-    def __setattr__(self, key, value):
-        current_value = getattr(self, key, None)
-        if isinstance(current_value, Signal):
-            current_value.value = value
-            return
-        super().__setattr__(key, value)
-
-
-class Bud:
-    def __init__(self, channel, plugin_id, init_var=0, bud_num=None):
-        self._channel_num = channel.channel_num
-        if bud_num == None:
-            self._plugin_id = plugin_id
-            self._bud_num = sys_bl00mbox.channel_new_bud(
-                self.channel_num, self.plugin_id, init_var
-            )
-            if self._bud_num == None:
-                raise Bl00mboxError("bud init failed")
-        else:
-            self._bud_num = bud_num
-            self._check_existence()
-            self._name = sys_bl00mbox.channel_bud_get_plugin_id(
-                self.channel_num, self.bud_num
-            )
-        self._name = sys_bl00mbox.channel_bud_get_name(self.channel_num, self.bud_num)
-        self._signals = SignalList(self)
-
-    def __repr__(self):
-        self._check_existence()
-        ret = "[bud " + str(self.bud_num) + "] " + self.name
-        for sig in self.signals._list:
-            ret += "\n  " + "\n  ".join(repr(sig).split("\n"))
-        return ret
-
-    def __del__(self):
-        self._check_existence()
-        sys_bl00mbox.channel_delete_bud(self.channel_num, self.bud_num)
-
-    def _check_existence(self):
-        if not sys_bl00mbox.channel_bud_exists(self.channel_num, self.bud_num):
-            raise Bl00mboxError("bud has been deleted")
-
-    @property
-    def channel_num(self):
-        return self._channel_num
-
-    @property
-    def plugin_id(self):
-        return self._plugin_id
-
-    @property
-    def name(self):
-        return self._name
-
-    @property
-    def bud_num(self):
-        return self._bud_num
-
-    @property
-    def signals(self):
-        return self._signals
-
-    @property
-    def table(self):
-        ret = []
-        for x in range(
-            sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num)
-        ):
-            ret += [
-                sys_bl00mbox.channel_bud_get_table_value(
-                    self.channel_num, self.bud_num, x
-                )
-            ]
-        return ret
-
-    @table.setter
-    def table(self, stuff):
-        max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num)
-        if len(stuff) > max_len:
-            stuff = stuff[:max_len]
-        for x, y in enumerate(stuff):
-            sys_bl00mbox.channel_bud_set_table_value(
-                self.channel_num, self.bud_num, x, y
-            )
-
-
-class ChannelOverview:
-    def __init__(self, nonempty=False):
-        self._nonempty = nonempty
-
-    def __repr__(self):
-        nonempty = self._nonempty
-        ret = (
-            "[channel list]\n  foreground: [channel "
-            + str(sys_bl00mbox.channel_get_foreground())
-            + "]"
-        )
-        for i in range(sys_bl00mbox.NUM_CHANNELS):
-            c = Channel(i)
-            if nonempty:
-                if not len(c.buds):
-                    continue
-            ret += "\n" + repr(c)
-        return ret
-
-
-class Channel:
-    show_all = ChannelOverview()
-    show_nonempty = ChannelOverview(nonempty=True)
-
-    def __init__(self, num=None):
-        if num == None:
-            self._channel_num = sys_bl00mbox.channel_get_free()
-        elif (int(num) < sys_bl00mbox.NUM_CHANNELS) and (int(num >= 0)):
-            self._channel_num = int(num)
-
-    def __repr__(self):
-        ret = "[channel " + str(self.channel_num) + "]"
-        if self.foreground:
-            ret += " (foreground)"
-        if self.background_mute_override:
-            ret += " (background mute override)"
-        ret += "\n  volume: " + str(self.volume)
-        b = sys_bl00mbox.channel_buds_num(self.channel_num)
-        ret += "\n  buds: " + str(b)
-        if len(self.buds) != b:
-            ret += " (desync" + str(len(self.buds)) + ")"
-        ret += "\n  " + "\n  ".join(repr(self.mixer).split("\n"))
-        return ret
-
-    def clear(self):
-        sys_bl00mbox.channel_clear(self.channel_num)
-
-    def new_bud(self, thing, init_var=None):
-        bud_init_var = 0
-        if (type(init_var) == int) or (type(init_var) == float):
-            bud_init_var = int(init_var)
-        bud = None
-        if isinstance(thing, PluginType):
-            bud = Bud(self, thing.plugin_id, bud_init_var)
-        if type(thing) == int:
-            bud = Bud(self, thing, bud_init_var)
-        return bud
-
-    def new_patch(self, patch, init_var=None):
-        if init_var == None:
-            return patch(self)
-        else:
-            return patch(self, init_var)
-
-    @staticmethod
-    def all():
-        ret = (
-            "[channel list]\n  foreground: [channel "
-            + str(sys_bl00mbox.channel_get_foreground())
-            + "]"
-        )
-        for i in range(sys_bl00mbox.NUM_CHANNELS):
-            c = Channel(i)
-            ret += "\n" + repr(c)
-        return ret
-
-    def new(self, thing, init_var=None):
-        if type(thing) == type:
-            if issubclass(thing, PatchType):
-                return self.new_patch(thing, init_var)
-        if isinstance(thing, PluginType) or (type(thing) == int):
-            return self.new_bud(thing, init_var)
-
-    @property
-    def buds(self):
-        buds = []
-        for i in range(sys_bl00mbox.channel_buds_num(self.channel_num)):
-            b = sys_bl00mbox.channel_get_bud_by_list_pos(self.channel_num, i)
-            bud = Bud(self, 0, bud_num=b)
-            buds += [bud]
-        return buds
-
-    @property
-    def channel_num(self):
-        return self._channel_num
-
-    @property
-    def connections(self):
-        return sys_bl00mbox.channel_conns_num(self.channel_num)
-
-    @property
-    def volume(self):
-        return sys_bl00mbox.channel_get_volume(self.channel_num)
-
-    @volume.setter
-    def volume(self, value):
-        sys_bl00mbox.channel_set_volume(self.channel_num, value)
-
-    @property
-    def mixer(self):
-        return ChannelMixer(self)
-
-    @mixer.setter
-    def mixer(self, val):
-        if isinstance(val, SignalOutput):
-            val.value = self.mixer
-        elif val is None:
-            ChannelMixer(self)._unplug_all
-        else:
-            raise Bl00mboxError("can't connect this")
-
-    @property
-    def background_mute_override(self):
-        return sys_bl00mbox.channel_get_background_mute_override(self.channel_num)
-
-    @background_mute_override.setter
-    def background_mute_override(self, val):
-        sys_bl00mbox.channel_set_background_mute_override(self.channel_num, val)
-
-    @property
-    def foreground(self):
-        return sys_bl00mbox.channel_get_foreground() == self.channel_num
-
-    @foreground.setter
-    def foreground(self, val):
-        if val:
-            sys_bl00mbox.channel_set_foreground(self.channel_num)
-        elif sys_bl00mbox.channel_get_foreground() == self.channel_num:
-            sys_bl00mbox.channel_set_foreground(0)
diff --git a/python_payload/bl00mbox/_patches.py b/python_payload/bl00mbox/_patches.py
index 02956f51f813fad50c6b1bd8155516bcef93b624..7fd2bb6ca3e0811fe2bddb297382c046a27c6b7e 100644
--- a/python_payload/bl00mbox/_patches.py
+++ b/python_payload/bl00mbox/_patches.py
@@ -1,6 +1,8 @@
 # SPDX-License-Identifier: CC0-1.0
 import math
 
+import bl00mbox
+
 try:
     import cpython.wave as wave
 except ImportError:
@@ -12,6 +14,14 @@ class _Patch:
         return True
 
 
+class _PatchSignalList:
+    pass
+
+
+class _PatchBudList:
+    pass
+
+
 class tinysynth(_Patch):
     def __init__(self, chan):
         self.channel = chan
@@ -208,3 +218,105 @@ class step_sequencer(_Patch):
     @property
     def step(self):
         return self.seqs[0].signals.step
+
+
+class fuzz(_Patch):
+    def __init__(self, chan):
+        self.buds = _PatchBudList()
+        self.signals = _PatchSignalList()
+        self.buds.dist = chan.new(bl00mbox.plugins.distortion)
+        self.signals.input = self.buds.dist.signals.input
+        self.signals.output = self.buds.dist.signals.output
+        self._intensity = 2
+        self._volume = 32767
+        self._gate = 0
+
+    @property
+    def intensity(self):
+        return self._intensity
+
+    @intensity.setter
+    def intensity(self, val):
+        self._intensity = val
+        self._update_table()
+
+    @property
+    def volume(self):
+        return self._volume
+
+    @volume.setter
+    def volume(self, val):
+        self._volume = val
+        self._update_table()
+
+    @property
+    def gate(self):
+        return self._gate
+
+    @gate.setter
+    def gate(self, val):
+        self._gate = val
+        self._update_table()
+
+    def _update_table(self):
+        table = list(range(129))
+        for num in table:
+            if num < 64:
+                ret = num / 64  # scale to [0..1[ range
+                ret = ret**self._intensity
+                if ret > 1:
+                    ret = 1
+                table[num] = int(self._volume * (ret - 1))
+            else:
+                ret = (128 - num) / 64  # scale to [0..1] range
+                ret = ret**self._intensity
+                table[num] = int(self._volume * (1 - ret))
+        gate = self.gate >> 9
+        for i in range(64 - gate, 64 + gate):
+            table[i] = 0
+        self.buds.dist.table = table
+
+
+class karplus_strong(_Patch):
+    def __init__(self, chan):
+        self.buds = _PatchBudList()
+        self.signals = _PatchSignalList()
+        self.buds.noise = chan.new_bud(bl00mbox.plugins.noise)
+        self.buds.flanger = chan.new_bud(bl00mbox.plugins.flanger)
+        self.buds.amp = chan.new_bud(bl00mbox.plugins.ampliverter)
+        self.buds.env = chan.new_bud(bl00mbox.plugins.env_adsr)
+        # self.buds.slew = chan.new_bud(bl00mbox.plugins.slew_rate_limiter)
+
+        self.buds.flanger.signals.resonance = 32500
+        self.buds.flanger.signals.manual.tone = "A2"
+
+        self.buds.env.signals.sustain = 0
+        self.buds.env.signals.decay = 20
+        self.buds.env.signals.attack = 5
+
+        self.buds.amp.signals.output = self.buds.flanger.signals.input
+        self.buds.amp.signals.input = self.buds.noise.signals.output
+        # self.buds.amp.signals.input = self.buds.slew.signals.output
+        # self.buds.slew.signals.input = self.buds.noise.signals.output
+        # self.buds.slew.signals.slew_rate = 10000
+        self.buds.amp.signals.gain = self.buds.env.signals.output
+
+        self.signals.trigger = self.buds.env.signals.trigger
+        self.signals.pitch = self.buds.flanger.signals.manual
+        self.signals.output = self.buds.flanger.signals.output
+        # self.signals.slew_rate = self.buds.slew.signals.slew_rate
+        self.signals.level = self.buds.flanger.signals.level
+        self.decay = 1000
+
+    @property
+    def decay(self):
+        return self._decay
+
+    @decay.setter
+    def decay(self, val):
+        tone = self.buds.flanger.signals.manual.tone
+        loss = (50 * (2 ** (-tone / 12))) // (val / 1000)
+        if loss < 2:
+            loss = 2
+        self.buds.flanger.signals.resonance = 32767 - loss
+        self._decay = val
diff --git a/python_payload/bl00mbox/_user.py b/python_payload/bl00mbox/_user.py
new file mode 100644
index 0000000000000000000000000000000000000000..34593ad8f38a36d3d37afe005db372bfe3c39bdf
--- /dev/null
+++ b/python_payload/bl00mbox/_user.py
@@ -0,0 +1,522 @@
+# SPDX-License-Identifier: CC0-1.0
+
+import sys_bl00mbox
+
+# note: consider the 'sys_bl00mbox' api super unstable for now pls :3
+
+import math
+import bl00mbox
+from bl00mbox import _helpers as helpers
+
+
+class Bl00mboxError(Exception):
+    pass
+
+
+def _makeSignal(bud, signal_num):
+    hints = sys_bl00mbox.channel_bud_get_signal_hints(
+        bud.channel_num, bud.bud_num, signal_num
+    )
+    if hints & 2:
+        signal = SignalOutput(bud, signal_num)
+        signal._hints = "output"
+    elif hints & 1:
+        if hints & 4:
+            signal = SignalInputTrigger(bud, signal_num)
+            signal._hints = "input/trigger"
+        elif hints & 32:
+            signal = SignalInputPitch(bud, signal_num)
+            signal._hints = "input/pitch"
+        else:
+            signal = SignalInput(bud, signal_num)
+            signal._hints = "input"
+    return signal
+
+
+class ChannelMixer:
+    def __init__(self, channel):
+        self._channel = channel
+        pass
+
+    def __repr__(self):
+        ret = "[channel mixer]"
+        ret += " (" + str(len(self.connections)) + " connections)"
+        for con in self.connections:
+            ret += "\n  " + con.name
+            ret += " in [bud " + str(con._bud.bud_num) + "] " + con._bud.name
+        return ret
+
+    def _unplug_all(self):
+        # TODO
+        pass
+
+    @property
+    def connections(self):
+        ret = []
+        for i in range(sys_bl00mbox.channel_mixer_num(self._channel.channel_num)):
+            b = sys_bl00mbox.channel_get_bud_by_mixer_list_pos(
+                self._channel.channel_num, i
+            )
+            s = sys_bl00mbox.channel_get_signal_by_mixer_list_pos(
+                self._channel.channel_num, i
+            )
+            sig = Signal(Bud(self._channel, 0, bud_num=b), s)
+            ret += [sig]
+        return ret
+
+
+class Signal:
+    def __init__(self, bud, signal_num):
+        self._bud = bud
+        self._signal_num = signal_num
+        self._name = sys_bl00mbox.channel_bud_get_signal_name(
+            bud.channel_num, bud.bud_num, signal_num
+        )
+        mpx = sys_bl00mbox.channel_bud_get_signal_name_multiplex(
+            bud.channel_num, bud.bud_num, signal_num
+        )
+        if mpx != (-1):
+            self._name += str(mpx)
+        self._description = sys_bl00mbox.channel_bud_get_signal_description(
+            bud.channel_num, bud.bud_num, signal_num
+        )
+        self._unit = sys_bl00mbox.channel_bud_get_signal_unit(
+            bud.channel_num, bud.bud_num, signal_num
+        )
+        self._hints = ""
+
+    def __repr__(self):
+        self._bud._check_existence()
+
+        ret = self.name
+        if len(self.unit):
+            ret += " [" + self.unit + "]"
+        ret += " [" + self.hints + "]: "
+        conret = []
+        direction = " <?> "
+        if isinstance(self, SignalInput):
+            direction = " <= "
+            if len(self.connections):
+                ret += str(self.connections[0].value)
+            else:
+                ret += str(self.value)
+        elif isinstance(self, SignalOutput):
+            direction = " => "
+            ret += str(self.value)
+
+        for con in self.connections:
+            if isinstance(con, Signal):
+                conret += [
+                    direction
+                    + con.name
+                    + " in [bud "
+                    + str(con._bud.bud_num)
+                    + "] "
+                    + con._bud.name
+                ]
+            if isinstance(con, ChannelMixer):
+                conret += [" ==> [channel mixer]"]
+        nl = "\n"
+        if len(conret) > 1:
+            ret += "\n"
+            nl += "  "
+        ret += nl.join(conret)
+        return ret
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def description(self):
+        return self._description
+
+    @property
+    def unit(self):
+        return self._unit
+
+    @property
+    def hints(self):
+        return self._hints
+
+    @property
+    def connections(self):
+        return []
+
+    @property
+    def value(self):
+        self._bud._check_existence()
+        return sys_bl00mbox.channel_bud_get_signal_value(
+            self._bud.channel_num, self._bud.bud_num, self._signal_num
+        )
+
+
+class SignalOutput(Signal):
+    @Signal.value.setter
+    def value(self, val):
+        if val == None:
+            sys_bl00mbox.channel_disconnect_signal_tx(
+                self._bud.channel_num, self._bud.bud_num, self._signal_num
+            )
+        elif isinstance(val, SignalInput):
+            val.value = self
+        elif isinstance(val, ChannelMixer):
+            if val._channel.channel_num == self._bud.channel_num:
+                sys_bl00mbox.channel_connect_signal_to_output_mixer(
+                    self._bud.channel_num, self._bud.bud_num, self._signal_num
+                )
+
+    @property
+    def connections(self):
+        cons = []
+        chan = self._bud.channel_num
+        bud = self._bud.bud_num
+        sig = self._signal_num
+        for i in range(sys_bl00mbox.channel_subscriber_num(chan, bud, sig)):
+            b = sys_bl00mbox.channel_get_bud_by_subscriber_list_pos(chan, bud, sig, i)
+            s = sys_bl00mbox.channel_get_signal_by_subscriber_list_pos(
+                chan, bud, sig, i
+            )
+            if (s >= 0) and (b > 0):
+                cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)]
+            elif s == -1:
+                cons += [ChannelMixer(Channel(chan))]
+        return cons
+
+
+class SignalInput(Signal):
+    @Signal.value.setter
+    def value(self, val):
+        self._bud._check_existence()
+        if isinstance(val, SignalOutput):
+            if len(self.connections):
+                if not sys_bl00mbox.channel_disconnect_signal_rx(
+                    self._bud.channel_num, self._bud.bud_num, self._signal_num
+                ):
+                    return
+            sys_bl00mbox.channel_connect_signal(
+                self._bud.channel_num,
+                self._bud.bud_num,
+                self._signal_num,
+                val._bud.bud_num,
+                val._signal_num,
+            )
+        elif isinstance(val, SignalInput):
+            # TODO
+            pass
+        elif (type(val) == int) or (type(val) == float):
+            if len(self.connections):
+                if not sys_bl00mbox.channel_disconnect_signal_rx(
+                    self._bud.channel_num, self._bud.bud_num, self._signal_num
+                ):
+                    return
+            sys_bl00mbox.channel_bud_set_signal_value(
+                self._bud.channel_num, self._bud.bud_num, self._signal_num, int(val)
+            )
+
+    @property
+    def connections(self):
+        cons = []
+        chan = self._bud.channel_num
+        bud = self._bud.bud_num
+        sig = self._signal_num
+        b = sys_bl00mbox.channel_get_source_bud(chan, bud, sig)
+        s = sys_bl00mbox.channel_get_source_signal(chan, bud, sig)
+        if (s >= 0) and (b > 0):
+            cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)]
+        return cons
+
+
+class SignalInputTrigger(SignalInput):
+    def start(self, velocity=32767):
+        if self.value > 0:
+            self.value = -velocity
+        else:
+            self.value = velocity
+
+    def stop(self):
+        self.value = 0
+
+
+class SignalInputPitch(SignalInput):
+    def __init__(self, bud, signal_num):
+        SignalInput.__init__(self, bud, signal_num)
+
+    @property
+    def tone(self):
+        return (self.value - (32767 - 2400 * 6)) / 200
+
+    @tone.setter
+    def tone(self, val):
+        if (type(val) == int) or (type(val) == float):
+            self.value = (32767 - 2400 * 6) + 200 * val
+        if type(val) == str:
+            self.value = helpers.note_name_to_sct(val)
+
+    @property
+    def freq(self):
+        tone = (self.value - (32767 - 2400 * 6)) / 200
+        return 440 * (2 ** (tone / 12))
+
+    @freq.setter
+    def freq(self, val):
+        tone = 12 * math.log(val / 440, 2)
+        self.value = (32767 - 2400 * 6) + 200 * tone
+
+    def __repr__(self):
+        ret = SignalInput.__repr__(self)
+        ret += " / " + str(self.tone) + " semitones / " + str(self.freq) + "Hz"
+        return ret
+
+
+class SignalList:
+    def __init__(self, bud):
+        self._list = []
+        for signal_num in range(
+            sys_bl00mbox.channel_bud_get_num_signals(bud.channel_num, bud.bud_num)
+        ):
+            hints = sys_bl00mbox.channel_bud_get_signal_hints(
+                bud.channel_num, bud.bud_num, signal_num
+            )
+            if hints & 2:
+                signal = SignalOutput(bud, signal_num)
+                signal._hints = "output"
+            elif hints & 1:
+                if hints & 4:
+                    signal = SignalInputTrigger(bud, signal_num)
+                    signal._hints = "input/trigger"
+                elif hints & 32:
+                    signal = SignalInputPitch(bud, signal_num)
+                    signal._hints = "input/pitch"
+                else:
+                    signal = SignalInput(bud, signal_num)
+                    signal._hints = "input"
+            self._list += [signal]
+            setattr(self, signal.name.split(" ")[0], signal)
+
+    def __setattr__(self, key, value):
+        current_value = getattr(self, key, None)
+        if isinstance(current_value, Signal):
+            current_value.value = value
+            return
+        super().__setattr__(key, value)
+
+
+class Bud:
+    def __init__(self, channel, plugin_id, init_var=0, bud_num=None):
+        self._channel_num = channel.channel_num
+        if bud_num == None:
+            self._plugin_id = plugin_id
+            self._bud_num = sys_bl00mbox.channel_new_bud(
+                self.channel_num, self.plugin_id, init_var
+            )
+            if self._bud_num == None:
+                raise Bl00mboxError("bud init failed")
+        else:
+            self._bud_num = bud_num
+            self._check_existence()
+            self._name = sys_bl00mbox.channel_bud_get_plugin_id(
+                self.channel_num, self.bud_num
+            )
+        self._name = sys_bl00mbox.channel_bud_get_name(self.channel_num, self.bud_num)
+        self._signals = SignalList(self)
+
+    def __repr__(self):
+        self._check_existence()
+        ret = "[bud " + str(self.bud_num) + "] " + self.name
+        for sig in self.signals._list:
+            ret += "\n  " + "\n  ".join(repr(sig).split("\n"))
+        return ret
+
+    def __del__(self):
+        self._check_existence()
+        sys_bl00mbox.channel_delete_bud(self.channel_num, self.bud_num)
+
+    def _check_existence(self):
+        if not sys_bl00mbox.channel_bud_exists(self.channel_num, self.bud_num):
+            raise Bl00mboxError("bud has been deleted")
+
+    @property
+    def channel_num(self):
+        return self._channel_num
+
+    @property
+    def plugin_id(self):
+        return self._plugin_id
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def bud_num(self):
+        return self._bud_num
+
+    @property
+    def signals(self):
+        return self._signals
+
+    @property
+    def table(self):
+        ret = []
+        for x in range(
+            sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num)
+        ):
+            ret += [
+                sys_bl00mbox.channel_bud_get_table_value(
+                    self.channel_num, self.bud_num, x
+                )
+            ]
+        return ret
+
+    @table.setter
+    def table(self, stuff):
+        max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num)
+        if len(stuff) > max_len:
+            stuff = stuff[:max_len]
+        for x, y in enumerate(stuff):
+            sys_bl00mbox.channel_bud_set_table_value(
+                self.channel_num, self.bud_num, x, y
+            )
+
+
+class ChannelOverview:
+    def __init__(self, nonempty=False):
+        self._nonempty = nonempty
+
+    def __repr__(self):
+        nonempty = self._nonempty
+        ret = (
+            "[channel list]\n  foreground: [channel "
+            + str(sys_bl00mbox.channel_get_foreground())
+            + "]"
+        )
+        for i in range(sys_bl00mbox.NUM_CHANNELS):
+            c = Channel(i)
+            if nonempty:
+                if not len(c.buds):
+                    continue
+            ret += "\n" + repr(c)
+        return ret
+
+
+class Channel:
+    show_all = ChannelOverview()
+    show_nonempty = ChannelOverview(nonempty=True)
+
+    def __init__(self, num=None):
+        if num == None:
+            self._channel_num = sys_bl00mbox.channel_get_free()
+        elif (int(num) < sys_bl00mbox.NUM_CHANNELS) and (int(num >= 0)):
+            self._channel_num = int(num)
+
+    def __repr__(self):
+        ret = "[channel " + str(self.channel_num) + "]"
+        if self.foreground:
+            ret += " (foreground)"
+        if self.background_mute_override:
+            ret += " (background mute override)"
+        ret += "\n  volume: " + str(self.volume)
+        b = sys_bl00mbox.channel_buds_num(self.channel_num)
+        ret += "\n  buds: " + str(b)
+        if len(self.buds) != b:
+            ret += " (desync" + str(len(self.buds)) + ")"
+        ret += "\n  " + "\n  ".join(repr(self.mixer).split("\n"))
+        return ret
+
+    def clear(self):
+        sys_bl00mbox.channel_clear(self.channel_num)
+
+    def new_bud(self, thing, init_var=None):
+        bud_init_var = 0
+        if (type(init_var) == int) or (type(init_var) == float):
+            bud_init_var = int(init_var)
+        bud = None
+        if isinstance(thing, bl00mbox._plugins._Plugin):
+            bud = Bud(self, thing.plugin_id, bud_init_var)
+        if type(thing) == int:
+            bud = Bud(self, thing, bud_init_var)
+        return bud
+
+    def new_patch(self, patch, init_var=None):
+        if init_var == None:
+            return patch(self)
+        else:
+            return patch(self, init_var)
+
+    @staticmethod
+    def all():
+        ret = (
+            "[channel list]\n  foreground: [channel "
+            + str(sys_bl00mbox.channel_get_foreground())
+            + "]"
+        )
+        for i in range(sys_bl00mbox.NUM_CHANNELS):
+            c = Channel(i)
+            ret += "\n" + repr(c)
+        return ret
+
+    def new(self, thing, init_var=None):
+        if type(thing) == type:
+            if issubclass(thing, bl00mbox.patches._Patch):
+                return self.new_patch(thing, init_var)
+        if isinstance(thing, bl00mbox._plugins._Plugin) or (type(thing) == int):
+            return self.new_bud(thing, init_var)
+
+    @property
+    def buds(self):
+        buds = []
+
+        for i in range(sys_bl00mbox.channel_buds_num(self.channel_num)):
+            b = sys_bl00mbox.channel_get_bud_by_list_pos(self.channel_num, i)
+            bud = Bud(self, 0, bud_num=b)
+            buds += [bud]
+        return buds
+
+    @property
+    def channel_num(self):
+        return self._channel_num
+
+    @property
+    def connections(self):
+        return sys_bl00mbox.channel_conns_num(self.channel_num)
+
+    @property
+    def volume(self):
+        return sys_bl00mbox.channel_get_volume(self.channel_num)
+
+    @volume.setter
+    def volume(self, value):
+        sys_bl00mbox.channel_set_volume(self.channel_num, value)
+
+    @property
+    def mixer(self):
+        return ChannelMixer(self)
+
+    @mixer.setter
+    def mixer(self, val):
+        if isinstance(val, SignalOutput):
+            val.value = self.mixer
+        elif val is None:
+            ChannelMixer(self)._unplug_all
+        else:
+            raise Bl00mboxError("can't connect this")
+
+    @property
+    def background_mute_override(self):
+        return sys_bl00mbox.channel_get_background_mute_override(self.channel_num)
+
+    @background_mute_override.setter
+    def background_mute_override(self, val):
+        sys_bl00mbox.channel_set_background_mute_override(self.channel_num, val)
+
+    @property
+    def foreground(self):
+        return sys_bl00mbox.channel_get_foreground() == self.channel_num
+
+    @foreground.setter
+    def foreground(self, val):
+        if val:
+            sys_bl00mbox.channel_set_foreground(self.channel_num)
+        elif sys_bl00mbox.channel_get_foreground() == self.channel_num:
+            sys_bl00mbox.channel_set_foreground(0)
diff --git a/python_payload/mypystubs/bl00mbox/__init__.pyi b/python_payload/mypystubs/bl00mbox/__init__.pyi
index b1369856a863068b53c2bb9df422a108f767f5c5..241a70dc60d32c251f53c335e63ab5aa768a2c7d 100644
--- a/python_payload/mypystubs/bl00mbox/__init__.pyi
+++ b/python_payload/mypystubs/bl00mbox/__init__.pyi
@@ -1,23 +1,14 @@
-from bl00mbox import patches
-
-from typing import Optional, TypeVar, Any, Type
-
-patches = patches
-
-class Signal:
-    value: int
-
-class SignalList:
-    def __setattr__(self, name: str, value: Signal) -> None: ...
-    def __getattr__(self, name: str) -> Signal: ...
-
-class Bud:
-    signals: SignalList
-
-T = TypeVar("T")
-P = TypeVar("P", bound=patches._Patch)
-
-class Channel:
-    background_mute_override: bool
-    def new(self, thing: Type[T], init_var: Optional[Any] = None) -> T: ...
-    def new_patch(self, patch: Type[T], init_var: Optional[Any] = None) -> T: ...
+from bl00mbox._user import *
+import bl00mbox._patches as patches
+import bl00mbox._helpers as helpers
+from bl00mbox._plugins import plugins
+
+__all__ = [
+    "Signal",
+    "SignalList",
+    "Bud",
+    "Channel",
+    "patches",
+    "helpers",
+    "plugins",
+]
diff --git a/python_payload/mypystubs/bl00mbox/_helpers.pyi b/python_payload/mypystubs/bl00mbox/_helpers.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..6226fca1b33388b7befe15e912a5ed509e9ba37c
--- /dev/null
+++ b/python_payload/mypystubs/bl00mbox/_helpers.pyi
@@ -0,0 +1,15 @@
+import bl00mbox
+from typing import Optional, Callable
+
+def terminal_scope(
+    signal: bl00mbox.Signal,
+    signal_min: int,
+    signal_max: int,
+    delay_ms: int,
+    width: int,
+    fun: Optional[Callable[[], None]],
+    fun_ms: Optional[int],
+) -> None: ...
+def sct_to_note_name(sct: int) -> str: ...
+def note_name_to_sct(name: str) -> int: ...
+def sct_to_freq(sct: int) -> int: ...
diff --git a/python_payload/mypystubs/bl00mbox/patches.pyi b/python_payload/mypystubs/bl00mbox/_patches.pyi
similarity index 67%
rename from python_payload/mypystubs/bl00mbox/patches.pyi
rename to python_payload/mypystubs/bl00mbox/_patches.pyi
index 4094d0c47f7319a5145af1f589d7bfdb939d190d..69c1d88e4d3e3c2796a2a42cccc92e8b5f49a0b0 100644
--- a/python_payload/mypystubs/bl00mbox/patches.pyi
+++ b/python_payload/mypystubs/bl00mbox/_patches.pyi
@@ -4,6 +4,12 @@ from typing import List
 
 class _Patch: ...
 
+class _PatchSignalList:
+    def __getattr__(self, name: str) -> bl00mbox.Signal: ...
+
+class _PatchBudList:
+    def __getattr__(self, name: str) -> bl00mbox.Bud: ...
+
 class tinysynth(_Patch):
     def decay(self, v: float) -> None: ...
     def waveform(self, v: int) -> None: ...
@@ -28,3 +34,15 @@ class step_sequencer(_Patch):
 
 class sampler(_Patch):
     sampler: bl00mbox.Bud
+
+class fuzz(_Patch):
+    buds: _PatchBudList
+    signals: _PatchSignalList
+    volume: int
+    intensity: float
+    gate: int
+
+class karplus_strong(_Patch):
+    buds: _PatchBudList
+    signals: _PatchSignalList
+    decay: int
diff --git a/python_payload/mypystubs/bl00mbox/_plugins.pyi b/python_payload/mypystubs/bl00mbox/_plugins.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..b0fec0fba98a623ee8549abfa959e84c41d77701
--- /dev/null
+++ b/python_payload/mypystubs/bl00mbox/_plugins.pyi
@@ -0,0 +1,10 @@
+class _Plugin:
+    index: int
+    plugin_id: int
+    name: str
+    description: str
+
+class _Plugins:
+    def __getattr__(self, name: str) -> _Plugin: ...
+
+plugins = _Plugins()
diff --git a/python_payload/mypystubs/bl00mbox/_user.pyi b/python_payload/mypystubs/bl00mbox/_user.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..ed45a64a36f702819b8e259da612d23acdad17be
--- /dev/null
+++ b/python_payload/mypystubs/bl00mbox/_user.pyi
@@ -0,0 +1,69 @@
+from typing import Optional, TypeVar, Any, Type, List, overload
+import bl00mbox
+
+class Signal:
+    name: str
+    description: str
+    unit: str
+    hints: str
+    connections: List["Signal | ChannelMixer"]
+
+    # mypy doesn't know how to handle asymmetric setters
+    # https://github.com/python/mypy/issues/3004
+    value: Any
+    # value: None | int | float | "Signal" | "ChannelMixer"
+
+    def __init__(self, bud: "Bud", signal_num: int): ...
+
+    # SignalInputTrigger functions
+    def start(self, velocity: int = 32767) -> None: ...
+    def stop(self) -> None: ...
+
+    # SignalInputPitch shorthands
+    freq: int
+    tone: Any
+    # tone: int | float | str
+
+class SignalList:
+    def __setattr__(self, name: str, value: Signal | int | "ChannelMixer") -> None: ...
+    def __getattr__(self, name: str) -> Signal: ...
+
+class Bud:
+    signals: SignalList
+
+    def __init__(
+        self,
+        channel: "Channel",
+        plugin_id: int,
+        init_var: Optional[int] = 0,
+        bud_num: Optional[int] = None,
+    ): ...
+
+T = TypeVar("T")
+P = TypeVar("P", bound=bl00mbox.patches._Patch)
+
+class Channel:
+    background_mute_override: bool
+    mixer: "ChannelMixer"
+
+    def clear(self) -> None: ...
+    def new_patch(self, patch: Type[T], init_var: Optional[Any] = None) -> T: ...
+    def new_bud(
+        self,
+        thing: bl00mbox._plugins._Plugin | int,
+        init_var: Optional[int | float] = None,
+    ) -> Bud: ...
+    @overload
+    def new(self, thing: Type[P], init_var: Optional[Any] = None) -> P: ...
+    @overload
+    def new(
+        self,
+        thing: bl00mbox._plugins._Plugin | int,
+        init_var: Optional[int | float] = None,
+    ) -> Bud: ...
+
+class ChannelMixer:
+    _channel: Channel
+    connections: List[Signal]
+
+    def _unplug_all(self) -> None: ...