diff --git a/components/bl00mbox/.clang-tidy b/components/bl00mbox/.clang-tidy
index 8797ef9f00e0c9fa8a20af5f8ee4e1931ea5b1e4..96260e90c56d5e0f433f914043042cfa7ca8e769 100644
--- a/components/bl00mbox/.clang-tidy
+++ b/components/bl00mbox/.clang-tidy
@@ -1 +1 @@
-Checks: '-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-clang-diagnostic-deprecated-declarations,-clang-diagnostic-incompatible-pointer-types'
+Checks: '-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-clang-diagnostic-deprecated-declarations,-clang-diagnostic-incompatible-pointer-types,-clang-analyzer-core.NullDereference'
diff --git a/components/bl00mbox/CMakeLists.txt b/components/bl00mbox/CMakeLists.txt
index 1a99b66feaec5b10d458c4161c9c242c56c3f271..487573d07d7c21d2b1a1899ce40bd2bb3dfad316 100644
--- a/components/bl00mbox/CMakeLists.txt
+++ b/components/bl00mbox/CMakeLists.txt
@@ -13,6 +13,7 @@ idf_component_register(
         plugins/sampler.c
         plugins/delay.c
         plugins/flanger.c
+        plugins/multipitch.c
         plugins/sequencer.c
         plugins/noise.c
         plugins/noise_burst.c
diff --git a/components/bl00mbox/bl00mbox_plugin_registry.c b/components/bl00mbox/bl00mbox_plugin_registry.c
index c3f9fa0eecec967b009ffb56bbd2949acde1ea6f..46edee771b4ed457d07a7e2709ad4e14ab7cdbb6 100644
--- a/components/bl00mbox/bl00mbox_plugin_registry.c
+++ b/components/bl00mbox/bl00mbox_plugin_registry.c
@@ -99,6 +99,7 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index)
 #include "noise_burst.h"
 #include "distortion.h"
 #include "mixer.h"
+#include "multipitch.h"
 #include "slew_rate_limiter.h"
 
 void bl00mbox_plugin_registry_init(void){
@@ -116,4 +117,5 @@ void bl00mbox_plugin_registry_init(void){
     plugin_add(&distortion_desc);
     plugin_add(&mixer_desc);
     plugin_add(&slew_rate_limiter_desc);
+    plugin_add(&multipitch_desc);
 }
diff --git a/components/bl00mbox/plugins/distortion.c b/components/bl00mbox/plugins/distortion.c
index 4dfcb404b8be7c4a7656fa0b3240b70ed8bd78c7..7effbea2d26cc96eee55e494a9103079c4b50c6d 100644
--- a/components/bl00mbox/plugins/distortion.c
+++ b/components/bl00mbox/plugins/distortion.c
@@ -2,7 +2,7 @@
 
 radspa_t * distortion_create(uint32_t init_var);
 radspa_descriptor_t distortion_desc = {
-    .name = "distortion",
+    .name = "_distortion",
     .id = 9000,
     .description = "distortion with linear interpolation between int16 values in plugin_table[129]",
     .create_plugin_instance = distortion_create,
diff --git a/components/bl00mbox/plugins/mixer.c b/components/bl00mbox/plugins/mixer.c
index 501662c77a962cf313891cba77e3d38925505d73..397fd142cc54c982e529367bd409ec63865aa0d7 100644
--- a/components/bl00mbox/plugins/mixer.c
+++ b/components/bl00mbox/plugins/mixer.c
@@ -45,7 +45,7 @@ radspa_t * mixer_create(uint32_t init_var){
     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);
+    radspa_signal_set_group(mixer, init_var, 1, 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/multipitch.c b/components/bl00mbox/plugins/multipitch.c
new file mode 100644
index 0000000000000000000000000000000000000000..50ff80ec208050c8355b4df012a679765a6fe7a5
--- /dev/null
+++ b/components/bl00mbox/plugins/multipitch.c
@@ -0,0 +1,72 @@
+#include "multipitch.h"
+
+radspa_descriptor_t multipitch_desc = {
+    .name = "multipitch",
+    .id = 37,
+    .description = "takes a pitch input and provides a number of shifted outputs.\ninit_var: number of outputs, default 0",
+    .create_plugin_instance = multipitch_create,
+    .destroy_plugin_instance = radspa_standard_plugin_destroy
+};
+
+void multipitch_run(radspa_t * multipitch, uint16_t num_samples, uint32_t render_pass_id){
+    bool output_request = false;
+    radspa_signal_t * thru_sig = radspa_signal_get_by_index(multipitch, 0);
+    if(thru_sig->buffer != NULL) output_request = true;
+    radspa_signal_t * input_sig = radspa_signal_get_by_index(multipitch, 1);
+
+    uint8_t num_outputs = (multipitch->len_signals - 2)/2;
+    radspa_signal_t * output_sigs[num_outputs];
+    radspa_signal_t * pitch_sigs[num_outputs];
+    for(uint8_t j = 0; j < num_outputs; j++){
+        output_sigs[j] = radspa_signal_get_by_index(multipitch, 2 + j);
+        pitch_sigs[j] = radspa_signal_get_by_index(multipitch, 3 + j);
+        if(output_sigs[j]->buffer != NULL) output_request = true;
+    }
+    if(!output_request) return;
+
+    int32_t ret = 0;
+    int32_t rets[num_outputs];
+
+    for(uint16_t i = 0; i < num_samples; i++){
+        int32_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
+        ret = input;
+
+        if(thru_sig->buffer != NULL) (thru_sig->buffer)[i] = ret;
+
+        int32_t pitch;
+        for(uint8_t j = 0; j < num_outputs; j++){
+            pitch = pitch_sigs[j]->get_value(pitch_sigs[j], i, num_samples, render_pass_id);
+            rets[j] = pitch + input - RADSPA_SIGNAL_VAL_SCT_A440;
+            if(output_sigs[j]->buffer != NULL) (output_sigs[j]->buffer)[i] = rets[j];
+        }
+    }
+    
+    // clang-tidy only, num_samples is always nonzero
+    if(!num_samples){
+        for(uint8_t j = 0; j < num_outputs; j++){
+            rets[j] = 0;
+        }
+    }
+
+    for(uint8_t j = 0; j < num_outputs; j++){
+        output_sigs[j]->value = rets[j];
+    }
+    thru_sig->value = ret;
+}
+
+radspa_t * multipitch_create(uint32_t init_var){
+    if(init_var > 127) init_var = 127;
+    radspa_t * multipitch = radspa_standard_plugin_create(&multipitch_desc, 2 + 2*init_var, sizeof(int32_t), 0);
+    if(multipitch == NULL) return NULL;
+    multipitch->render = multipitch_run;
+
+    radspa_signal_set(multipitch, 0, "thru", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440);
+    radspa_signal_set(multipitch, 1, "input", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440);
+
+    radspa_signal_set_group(multipitch, init_var, 2, 2, "output", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_SCT,
+            RADSPA_SIGNAL_VAL_SCT_A440);
+    radspa_signal_set_group(multipitch, init_var, 2, 3, "shift", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT,
+            RADSPA_SIGNAL_VAL_SCT_A440);
+
+    return multipitch;
+}
diff --git a/components/bl00mbox/plugins/multipitch.h b/components/bl00mbox/plugins/multipitch.h
new file mode 100644
index 0000000000000000000000000000000000000000..7e830c8024688eb2f3895353860a938b125e1add
--- /dev/null
+++ b/components/bl00mbox/plugins/multipitch.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <radspa.h>
+#include <radspa_helpers.h>
+
+extern radspa_descriptor_t multipitch_desc;
+radspa_t * multipitch_create(uint32_t init_var);
+void multipitch_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+
diff --git a/components/bl00mbox/plugins/sampler.c b/components/bl00mbox/plugins/sampler.c
index 8c7d65711c87edb8d2e89adb662f0b13f6805e02..e5ed7ac51fe442c3bf329cf560bb250cb048c14f 100644
--- a/components/bl00mbox/plugins/sampler.c
+++ b/components/bl00mbox/plugins/sampler.c
@@ -2,7 +2,7 @@
 
 radspa_t * sampler_create(uint32_t init_var);
 radspa_descriptor_t sampler_desc = {
-    .name = "sampler_ram",
+    .name = "_sampler_ram",
     .id = 696969,
     .description = "simple sampler that stores a copy of the sample in ram",
     .create_plugin_instance = sampler_create,
diff --git a/components/bl00mbox/plugins/sequencer.c b/components/bl00mbox/plugins/sequencer.c
index 6bdaefb2aadabd7215b3699571957ea5ca317cbf..c137271430281f5430c86c4c32ee020d54df9766 100644
--- a/components/bl00mbox/plugins/sequencer.c
+++ b/components/bl00mbox/plugins/sequencer.c
@@ -1,9 +1,12 @@
 #include "sequencer.h"
 
 radspa_descriptor_t sequencer_desc = {
-    .name = "sequencer",
+    .name = "_sequencer",
     .id = 56709,
-    .description = "i.o.u.",
+    .description =  "sequencer that can output triggers or general control signals, best enjoyed through the "
+                    "'sequencer' patch.\ninit_var: 1st byte (lsb): number of tracks, 2nd byte: number of steps"
+                    "\ntable encoding (all int16_t): index 0: track type (-32767: trigger track, 32767: direct "
+                    "track). next 'number of steps' indices: track data (repeat for number of tracks)",
     .create_plugin_instance = sequencer_create,
     .destroy_plugin_instance = radspa_standard_plugin_destroy
 };
@@ -22,67 +25,27 @@ static uint64_t target(uint64_t step_len, uint64_t bpm, uint64_t beat_div){
         return (48000ULL * 60 * 4) / (bpm * beat_div);
 }
 
-radspa_t * sequencer_create(uint32_t init_var){
-    //init_var:
-    // lsbyte: number of channels
-    // lsbyte+1: number of pixels in channel (>= bars*beats_div)
-    uint32_t num_tracks = 1;
-    uint32_t num_pixels = 16;
-    if(init_var){
-        num_tracks = init_var & 0xFF;
-        num_pixels = (init_var>>8) & 0xFF;
-    }
-    uint32_t table_size = num_tracks * (num_pixels + 1);
-    uint32_t num_signals = num_tracks + SEQUENCER_NUM_SIGNALS; //one for each channel output
-    radspa_t * sequencer = radspa_standard_plugin_create(&sequencer_desc, num_signals, sizeof(sequencer_data_t), table_size);
-    sequencer->render = sequencer_run;
-
+void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_pass_id){
+    bool output_request = false;
     sequencer_data_t * data = sequencer->plugin_data;
-    data->track_step_len = num_pixels;
-    data->num_tracks = num_tracks;
-    data->bpm_prev = 120;
-    data->beat_div_prev = 16;
-    data->counter_target = target(data->track_step_len, data->bpm_prev, data->beat_div_prev);
-    radspa_signal_set(sequencer, SEQUENCER_STEP, "step", RADSPA_SIGNAL_HINT_OUTPUT, 0);
-    radspa_signal_set(sequencer, SEQUENCER_STEP_LEN, "step_len", RADSPA_SIGNAL_HINT_INPUT, num_pixels);
-    radspa_signal_set(sequencer, SEQUENCER_SYNC_OUT, "sync_out", RADSPA_SIGNAL_HINT_OUTPUT, 0);
-    radspa_signal_set(sequencer, SEQUENCER_SYNC_IN, "sync_in", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
-    radspa_signal_set(sequencer, SEQUENCER_BPM, "bpm", RADSPA_SIGNAL_HINT_INPUT, data->bpm_prev);
-    radspa_signal_set(sequencer, SEQUENCER_BEAT_DIV, "beat_div", RADSPA_SIGNAL_HINT_INPUT, data->beat_div_prev);
-    radspa_signal_set(sequencer, SEQUENCER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_t * track_sigs[data->num_tracks];
 
-    /*
-    for(uint8_t i = 0; i < num_signals; i++){
-        radspa_signal_set(sequencer, SEQUENCER_NUM_SIGNALS + i, "track", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    for(uint8_t j = 0; j < data->num_tracks; j++){
+        track_sigs[j] = radspa_signal_get_by_index(sequencer, SEQUENCER_OUTPUT+j);
+        if(track_sigs[j]->buffer != NULL) output_request = true;
     }
-    */
-
-    data->counter = 0;
-    data->sync_in_prev = 0;
-    data->sync_out = 32767;
-
 
-    return sequencer;
-}
-
-/* ~table encoding~
- * first int16_t: track type:
- * -32767 : trigger track
- * 32767 : direct track
- * in between: slew rate 
- */
-
-void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_pass_id){
     radspa_signal_t * step_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_STEP);
+    if(step_sig->buffer != NULL) output_request = true;
+    radspa_signal_t * step_len_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_STEP_LEN);
     radspa_signal_t * sync_out_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_SYNC_OUT);
-    radspa_signal_t * output_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_OUTPUT);
-    if((output_sig->buffer == NULL) && (sync_out_sig->buffer == NULL) && (step_sig->buffer == NULL)) return;
+    if(sync_out_sig->buffer != NULL) output_request = true;
+    if(!output_request) return;
 
-    radspa_signal_t * step_len_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_STEP_LEN);
     radspa_signal_t * sync_in_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_SYNC_IN);
     radspa_signal_t * bpm_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_BPM);
     radspa_signal_t * beat_div_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_BEAT_DIV);
-    sequencer_data_t * data = sequencer->plugin_data;
+
     int16_t * table = sequencer->plugin_table;
 
     int16_t s1 = radspa_signal_get_value(step_len_sig, 0, num_samples, render_pass_id);
@@ -117,24 +80,68 @@ void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_p
             data->sync_in_prev = sync_in;
             
             if(!data->counter){ //event just happened
-                for(uint8_t track = 0; track < data->num_tracks; track++){
-                    int16_t type = table[track*data->track_step_len];
-                    int16_t stage_val = table[data->step + 1 + data->track_step_len * track];
+                for(uint8_t j = 0; j < data->num_tracks; j++){
+                    int16_t type = table[j * (data->track_step_len + 1)];
+                    int16_t stage_val = table[data->step + 1 + (1 + data->track_step_len) * j];
                     if(type == 32767){
-                        data->track_fill[track] = stage_val;
+                        data->tracks[j].track_fill = stage_val;
                     } else if(type == -32767){
-                        if(stage_val > 0) data->track_fill[track] = radspa_trigger_start(stage_val, &(data->trigger_hist[track]));
-                        if(stage_val < 0) data->track_fill[track] = radspa_trigger_stop(&(data->trigger_hist[track]));
+                        if(stage_val > 0) data->tracks[j].track_fill = radspa_trigger_start(stage_val, &(data->tracks[j].trigger_hist));
+                        if(stage_val < 0) data->tracks[j].track_fill = radspa_trigger_stop(&(data->tracks[j].trigger_hist));
                     }
                 }
             }
           
-            if(output_sig->buffer != NULL) (output_sig->buffer)[i] = data->track_fill[0];
+            for(uint8_t j = 0; j < data->num_tracks; j++){
+                if(track_sigs[j]->buffer != NULL) (track_sigs[j]->buffer)[i] = data->tracks[j].track_fill;
+            }
             if(sync_out_sig->buffer != NULL) (sync_out_sig->buffer)[i] = data->sync_out;
             if(step_sig->buffer != NULL) (step_sig->buffer)[i] = data->step;
         }
     }
+    for(uint8_t j = 0; j < data->num_tracks; j++){
+        track_sigs[j]->value = data->tracks[j].track_fill;
+    }
     sync_out_sig->value = data->sync_out;
-    output_sig->value = data->track_fill[0];
     step_sig->value = data->step;
 }
+
+radspa_t * sequencer_create(uint32_t init_var){
+    uint32_t num_tracks = 4;
+    uint32_t num_pixels = 16;
+    if(init_var){
+        num_tracks = init_var & 0xFF;
+        num_pixels = (init_var>>8) & 0xFF;
+    }
+    if(!num_tracks) return NULL;
+    if(!num_pixels) return NULL;
+
+    uint32_t table_size = num_tracks * (num_pixels + 1);
+    uint32_t num_signals = num_tracks + SEQUENCER_NUM_SIGNALS; //one for each channel output
+    size_t data_size = sizeof(sequencer_data_t) + sizeof(sequencer_track_data_t) * (num_tracks - 1);
+    radspa_t * sequencer = radspa_standard_plugin_create(&sequencer_desc, num_signals, data_size, table_size);
+    if(sequencer == NULL) return NULL;
+
+    sequencer->render = sequencer_run;
+
+    sequencer_data_t * data = sequencer->plugin_data;
+    data->track_step_len = num_pixels;
+    data->num_tracks = num_tracks;
+    data->bpm_prev = 120;
+    data->beat_div_prev = 16;
+    data->counter_target = target(data->track_step_len, data->bpm_prev, data->beat_div_prev);
+    radspa_signal_set(sequencer, SEQUENCER_STEP, "step", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(sequencer, SEQUENCER_STEP_LEN, "step_len", RADSPA_SIGNAL_HINT_INPUT, num_pixels);
+    radspa_signal_set(sequencer, SEQUENCER_SYNC_OUT, "sync_out", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(sequencer, SEQUENCER_SYNC_IN, "sync_in", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
+    radspa_signal_set(sequencer, SEQUENCER_BPM, "bpm", RADSPA_SIGNAL_HINT_INPUT, data->bpm_prev);
+    radspa_signal_set(sequencer, SEQUENCER_BEAT_DIV, "beat_div", RADSPA_SIGNAL_HINT_INPUT, data->beat_div_prev);
+    radspa_signal_set_group(sequencer, data->num_tracks, 1, SEQUENCER_OUTPUT, "track",
+            RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
+
+    data->counter = 0;
+    data->sync_in_prev = 0;
+    data->sync_out = 32767;
+
+    return sequencer;
+}
diff --git a/components/bl00mbox/plugins/sequencer.h b/components/bl00mbox/plugins/sequencer.h
index a6b5748af15ae359ca111fc08feea3a9403792b8..bed48c15a79788bbb53ff5dc9ce8c24df14ba9ee 100644
--- a/components/bl00mbox/plugins/sequencer.h
+++ b/components/bl00mbox/plugins/sequencer.h
@@ -3,6 +3,11 @@
 #include "radspa.h"
 #include "radspa_helpers.h"
 
+typedef struct {
+    int16_t track_fill;
+    int16_t trigger_hist;
+} sequencer_track_data_t;
+
 typedef struct {
     uint8_t num_tracks;
     uint16_t track_step_len;
@@ -14,10 +19,10 @@ typedef struct {
     int16_t sync_out;
     int16_t bpm_prev;
     int16_t beat_div_prev;
-    int16_t track_fill[1];
-    int16_t trigger_hist[1];
+    sequencer_track_data_t tracks[];
 } sequencer_data_t;
 
+
 extern radspa_descriptor_t sequencer_desc;
 radspa_t * sequencer_create(uint32_t init_var);
 void sequencer_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/radspa_helpers.c b/components/bl00mbox/radspa/radspa_helpers.c
index 4d9f3d01d9a1d7be14eabb728cb09d3cc6b78f67..91b6deae494edb75de929075379ccedc2bdab132 100644
--- a/components/bl00mbox/radspa/radspa_helpers.c
+++ b/components/bl00mbox/radspa/radspa_helpers.c
@@ -1,12 +1,32 @@
 //SPDX-License-Identifier: CC0-1.0
 #include "radspa_helpers.h"
 
+#define RADSPA_SIGNAL_CACHING
+
 radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t signal_index){
-    radspa_signal_t * ret = plugin->signals;
-    for(uint16_t i = 0; i < signal_index; i++){
-        ret = ret->next;
-        if(ret == NULL) break;
+    radspa_signal_t * ret = NULL;
+    if(plugin == NULL) return ret; // clang-tidy
+#ifdef RADSPA_SIGNAL_CACHING
+    static radspa_signal_t * cache_s = NULL;
+    static radspa_t * cache_p = NULL;
+    static uint16_t cache_i = 0;
+
+    if((plugin == cache_p) && (signal_index == cache_i + 1) && (cache_s != NULL)){
+        ret = cache_s->next;
+    }
+    if(ret == NULL){
+#endif
+        ret = plugin->signals;
+        for(uint16_t i = 0; i < signal_index; i++){
+            ret = ret->next;
+            if(ret == NULL) break;
+        }
+#ifdef RADSPA_SIGNAL_CACHING
     }
+    cache_s = ret;
+    cache_p = plugin;
+    cache_i = signal_index;
+#endif
     return ret;
 }
 
@@ -18,10 +38,16 @@ 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,
+void radspa_signal_set_description(radspa_t * plugin, uint8_t signal_index, char * description){
+    radspa_signal_t * sig = radspa_signal_get_by_index(plugin, signal_index);
+    if(sig == NULL) return;
+    sig->description = description;
+}
+
+void radspa_signal_set_group(radspa_t * plugin, uint8_t group_len, uint8_t step, 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);
+        radspa_signal_t * sig = radspa_signal_get_by_index(plugin, signal_index + i * step);
         if(sig == NULL) return;
         sig->name = name;
         sig->hints = hints;
@@ -30,6 +56,15 @@ void radspa_signal_set_group(radspa_t * plugin, uint8_t group_len, uint8_t signa
     }
 }
 
+void radspa_signal_set_group_description(radspa_t * plugin, uint8_t group_len, uint8_t step, uint8_t signal_index,
+                                    char * description){
+    for(uint8_t i = 0; i < group_len; i++){
+        radspa_signal_t * sig = radspa_signal_get_by_index(plugin, signal_index + i * step);
+        if(sig == NULL) return;
+        sig->description = description;
+    }
+}
+
 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 11b9709ac5ae220e25891fd192a2129880cdc103..c8e3717629225ae671c5b073f694ed88c4feac9b 100644
--- a/components/bl00mbox/radspa/radspa_helpers.h
+++ b/components/bl00mbox/radspa/radspa_helpers.h
@@ -6,8 +6,11 @@
 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,
+void radspa_signal_set_group(radspa_t * plugin, uint8_t group_len, uint8_t step, uint8_t signal_index, char * name,
                                     uint32_t hints, int16_t value);
+void radspa_signal_set_description(radspa_t * plugin, uint8_t signal_index, char * description);
+void radspa_signal_set_group_description(radspa_t * plugin, uint8_t group_len, uint8_t step, uint8_t signal_index,
+                                    char * description);
 
 
 // get signal struct from a signal index
diff --git a/python_payload/apps/simple_drums/__init__.py b/python_payload/apps/simple_drums/__init__.py
index b17027a918623a190299dab34f1c1e15311c0640..b9ade9a3a060cfa9041b3a73fa25c93cb67fc0b2 100644
--- a/python_payload/apps/simple_drums/__init__.py
+++ b/python_payload/apps/simple_drums/__init__.py
@@ -41,7 +41,7 @@ class SimpleDrums(Application):
         super().__init__(app_ctx)
         # ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
         self.blm = bl00mbox.Channel("simple drums")
-        self.seq = self.blm.new(bl00mbox.patches.step_sequencer)
+        self.seq = self.blm.new(bl00mbox.patches.sequencer)
         self.hat = self.blm.new(bl00mbox.patches.sampler, "hihat.wav")
         # Dot(10, 10, -30, 0, self._track_col(0)).draw(0,ctx)
         self.kick = self.blm.new(bl00mbox.patches.sampler, "kick.wav")
@@ -51,9 +51,9 @@ class SimpleDrums(Application):
         self.kick.signals.output = self.blm.mixer
         self.snare.signals.output = self.blm.mixer
         self.hat.signals.output = self.blm.mixer
-        self.kick.signals.trigger = self.seq.plugins.sequencer0.signals.output
-        self.hat.signals.trigger = self.seq.plugins.sequencer1.signals.output
-        self.snare.signals.trigger = self.seq.plugins.sequencer2.signals.output
+        self.kick.signals.trigger = self.seq.plugins.seq.signals.track0
+        self.hat.signals.trigger = self.seq.plugins.seq.signals.track1
+        self.snare.signals.trigger = self.seq.plugins.seq.signals.track2
         self.seq.signals.bpm.value = 80
 
         self.track_names = ["kick", "hihat", "snare"]
diff --git a/python_payload/bl00mbox/_patches.py b/python_payload/bl00mbox/_patches.py
index bf7fb5895b1171dbe9572d3bd995f20f69397ba2..97ea3bc24886f32090f73c8d761f0b74421dd467 100644
--- a/python_payload/bl00mbox/_patches.py
+++ b/python_payload/bl00mbox/_patches.py
@@ -17,8 +17,8 @@ class _Patch:
 
     def __repr__(self):
         ret = "[patch] " + type(self).__name__
-        ret += "\n  [signals:]\n    " + "\n    ".join(repr(self.signals).split("\n"))
-        ret += "\n  [plugins:]\n    " + "\n    ".join(repr(self.plugins).split("\n"))
+        ret += "\n  [signals:]    " + "\n    ".join(repr(self.signals).split("\n"))
+        ret += "\n  [plugins:]    " + "\n    ".join(repr(self.plugins).split("\n"))
         return ret
 
 
@@ -30,7 +30,12 @@ class _PatchItemList:
         return iter(self._items)
 
     def __repr__(self):
-        return "\n".join([x + ": " + repr(getattr(self, x)) for x in self._items])
+        rets = ""
+        for x in self._items:
+            a = "\n" + repr(getattr(self, x)).split("]")
+            a[0] += ": " + x
+            ret += "]".join(a)
+        return ret
 
     def __setattr__(self, key, value):
         current_value = getattr(self, key, None)
@@ -82,18 +87,20 @@ class tinysynth_fm(tinysynth):
     def __init__(self, chan):
         super().__init__(chan)
         self.plugins.mod_osc = self._channel.new(bl00mbox.plugins.osc_fm)
+        self.plugins.mult = self._channel.new(bl00mbox.plugins.multipitch, 1)
         self.plugins.mod_osc.signals.output = self.plugins.osc.signals.lin_fm
         self.signals.fm_waveform = self.plugins.mod_osc.signals.waveform
-        self.signals.fm_pitch = self.plugins.mod_osc.signals.pitch
+        self.plugins.mod_osc.signals.pitch = self.plugins.mult.signals.output0
+        self.plugins.osc.signals.pitch = self.plugins.mult.signals.thru
+
+        self.signals.fm = self.plugins.mult.signals.shift0
+        self.signals.pitch = self.plugins.mult.signals.input
         self.signals.decay = 1000
         self.signals.attack = 20
         self.signals.waveform = -1
         self.signals.fm_waveform = 0
 
-        self.sync_mod_osc(2.5)
-
-    def sync_mod_osc(self, val):
-        self.signals.fm_pitch.freq = int(val) * self.signals.pitch.freq
+        self.signals.fm = 3173  # weird but eh
 
 
 class sampler(_Patch):
@@ -109,7 +116,7 @@ class sampler(_Patch):
         f = wave.open("/flash/sys/samples/" + filename, "r")
 
         self.len_frames = f.getnframes()
-        self.plugins.sampler = chan._new_plugin(696969, self.len_frames)
+        self.plugins.sampler = chan.new(bl00mbox.plugins._sampler_ram, self.len_frames)
 
         assert f.getsampwidth() == 2
         assert f.getnchannels() in (1, 2)
@@ -138,7 +145,7 @@ class sampler(_Patch):
         return self._filename
 
 
-class step_sequencer(_Patch):
+class sequencer(_Patch):
     def __init__(self, chan, num=4):
         super().__init__(chan)
         if num > 32:
@@ -147,19 +154,16 @@ class step_sequencer(_Patch):
             num = 0
         self.seqs = []
         prev_seq = None
-        for i in range(num):
-            seq = chan._new_plugin(56709)
-            seq.table = [-32767] + ([0] * 16)
-            if prev_seq is None:
-                self.signals.bpm = seq.signals.bpm
-                self.signals.beat_div = seq.signals.beat_div
-                self.signals.step = seq.signals.step
-                self.signals.step_len = seq.signals.step_len
-            else:
-                prev_seq.signals.sync_out = seq.signals.sync_in
-            prev_seq = seq
-            self.seqs += [seq]
-            setattr(self.plugins, "sequencer" + str(i), seq)
+        self.num_pixels = 16
+        self.num_tracks = num
+        init_var = (self.num_pixels * 256) + (self.num_tracks)  # magic
+        self.plugins.seq = chan.new(bl00mbox.plugins._sequencer, init_var)
+        self.signals.bpm = self.plugins.seq.signals.bpm
+        self.signals.beat_div = self.plugins.seq.signals.beat_div
+        self.signals.step = self.plugins.seq.signals.step
+        self.signals.step_len = self.plugins.seq.signals.step_len
+        tracktable = [-32767] + ([0] * self.num_pixels)
+        self.plugins.seq.table = tracktable * self.num_tracks
 
     def __repr__(self):
         ret = "[patch] step sequencer"
@@ -188,19 +192,22 @@ class step_sequencer(_Patch):
         ret += "\n" + "\n".join(super().__repr__().split("\n")[1:])
         return ret
 
+    def _get_table_index(self, track, step):
+        return step + 1 + track * (self.num_pixels + 1)
+
     def trigger_start(self, track, step):
-        a = self.seqs[track].table
-        a[step + 1] = 32767
-        self.seqs[track].table = a
+        a = self.plugins.seq.table
+        a[self._get_table_index(track, step)] = 32767
+        self.plugins.seq.table = a
 
     def trigger_stop(self, track, step):
-        a = self.seqs[track].table
-        a[step + 1] = 0
-        self.seqs[track].table = a
+        a = self.plugins.seq.table
+        a[self._get_table_index(track, step)] = 0
+        self.plugins.seq.table = a
 
     def trigger_state(self, track, step):
-        a = self.seqs[track].table
-        return a[step + 1]
+        a = self.plugins.seq.table
+        return a[self._get_table_index(track, step)]
 
     def trigger_toggle(self, track, step):
         if self.trigger_state(track, step) == 0:
@@ -212,7 +219,7 @@ class step_sequencer(_Patch):
 class fuzz(_Patch):
     def __init__(self, chan):
         super().__init__(chan)
-        self.plugins.dist = chan.new(bl00mbox.plugins.distortion)
+        self.plugins.dist = chan.new(bl00mbox.plugins._distortion)
         self.signals.input = self.plugins.dist.signals.input
         self.signals.output = self.plugins.dist.signals.output
         self._intensity = 2
diff --git a/python_payload/mypystubs/bl00mbox/_patches.pyi b/python_payload/mypystubs/bl00mbox/_patches.pyi
index 50581ef2e28c190f132425a3d21a718ce9284849..8ae4f41df8e1a2d9b4ffd5682a1a3519b6913768 100644
--- a/python_payload/mypystubs/bl00mbox/_patches.pyi
+++ b/python_payload/mypystubs/bl00mbox/_patches.pyi
@@ -25,8 +25,7 @@ class _PatchPluginList:
 class tinysynth(_Patch): ...
 class tinysynth_fm(tinysynth): ...
 
-class step_sequencer(_Patch):
-    seqs: List[bl00mbox.Plugin]
+class sequencer(_Patch):
     bpm: int
 
     def trigger_state(self, track: int, i: int) -> bool: ...