diff --git a/components/bl00mbox/CMakeLists.txt b/components/bl00mbox/CMakeLists.txt
index f64b7c81f5808f391981e6679fb1d002dfb5ff1f..2fb97bf4af8929c851f77accf3fbd7cbcdf25553 100644
--- a/components/bl00mbox/CMakeLists.txt
+++ b/components/bl00mbox/CMakeLists.txt
@@ -3,9 +3,10 @@
 idf_component_register(
     SRCS
         bl00mbox.c
-        bl00mbox_audio.c
-        bl00mbox_user.c
+        bl00mbox_channels.c
+        bl00mbox_plugins_signals.c
         bl00mbox_os.c
+        bl00mbox_patches.c
         bl00mbox_plugin_registry.c
         bl00mbox_radspa_requirements.c
         bl00mbox_containers.c
diff --git a/components/bl00mbox/bl00mbox_audio.c b/components/bl00mbox/bl00mbox_channels.c
similarity index 76%
rename from components/bl00mbox/bl00mbox_audio.c
rename to components/bl00mbox/bl00mbox_channels.c
index 564a4fe6241dc863091cd3e2703acdb4d9b40da3..6a84d0e77b41598a9f34ec58a8d323f7db62520d 100644
--- a/components/bl00mbox/bl00mbox_audio.c
+++ b/components/bl00mbox/bl00mbox_channels.c
@@ -5,9 +5,7 @@
 #include "bl00mbox_channel_plugin.h"
 #include <assert.h>
 
-static uint16_t full_buffer_len;
-
-static uint32_t render_pass_id;
+static uint32_t bl00mbox_render_pass_id = 0;
 
 int16_t * bl00mbox_line_in_interlaced = NULL;
 
@@ -135,9 +133,7 @@ bl00mbox_channel_t * bl00mbox_channel_create(){
 failed:
     if(chan){
         if(chan->channel_plugin){
-            // supress errors
-            chan->channel_plugin->parent_self_ref = &chan->channel_plugin;
-            bl00mbox_plugin_destroy(chan->channel_plugin);
+            bl00mbox_plugin_destroy_unlisted(chan->channel_plugin);
         }
         if(chan->render_lock) bl00mbox_delete_lock(&chan->render_lock);
         free(chan);
@@ -148,13 +144,11 @@ failed:
 
 void bl00mbox_channel_clear(bl00mbox_channel_t * chan){
     // note: this does NOT destroy the channel_plugin as it is unlisted
-    bl00mbox_set_iter_t iter;
-    bl00mbox_set_iter_start(&iter, &chan->plugins);
-    bl00mbox_plugin_t * plugin;
-    while((plugin = bl00mbox_set_iter_next(&iter))){
-        bl00mbox_plugin_destroy(plugin);
-    }
+    bl00mbox_clear_patch_container(&chan->patches);
     chan->plugin_id = 0;
+    if(chan->patches.plugin_t.len) bl00mbox_log_error("plugin_t nonempty");
+    if(chan->patches.patch_t.len) bl00mbox_log_error("patch_t nonempty");
+    if(chan->num_plugins) bl00mbox_log_error("num_plugins nonzero");
 }
 
 void bl00mbox_channel_destroy(bl00mbox_channel_t * chan){
@@ -174,27 +168,13 @@ void bl00mbox_channel_destroy(bl00mbox_channel_t * chan){
     bl00mbox_set_remove(all_chans, chan);
     bl00mbox_give_lock(&user_lock);
 
-    // remove from parent
-    if(* (chan->parent_self_ref) != chan){
-        bl00mbox_log_error("channel: parent_self_ref improper: channel %p, ref %p",
-                            chan, * (chan->parent_self_ref));
-    }
-    * (chan->parent_self_ref) = NULL;
-
-    // remove from weak parent
-    if(chan->weak_parent_self_ref){
-        if(* (chan->weak_parent_self_ref) != chan){
-            bl00mbox_log_error("channel: weak_parent_self_ref improper: channel %p, ref %p",
-                                chan, * (chan->weak_parent_self_ref));
-        }
-        * (chan->weak_parent_self_ref) = NULL;
-    }
+    bl00mbox_deref(&chan->ref, chan, "channel");
+    bl00mbox_deref(&chan->weak_ref, chan, "channel");
 
 #ifdef BL00MBOX_DEBUG
     if(chan->connections.len) bl00mbox_log_error("connections nonempty");
     if(chan->roots.len) bl00mbox_log_error("roots nonempty");
     if(chan->always_render.len) bl00mbox_log_error("always render nonempty");
-    if(chan->plugins.len) bl00mbox_log_error("plugins nonempty");
     bl00mbox_wait();
 #endif
 
@@ -209,23 +189,11 @@ void bl00mbox_channel_destroy(bl00mbox_channel_t * chan){
     if(chan->channel_plugin) bl00mbox_plugin_destroy(chan->channel_plugin);
     free(chan->render_plugins);
     free(chan->render_buffers);
-    free(chan->name);
     free(chan);
 }
 
-void bl00mbox_audio_plugin_render(bl00mbox_plugin_t * plugin){
-    if(plugin->render_pass_id == render_pass_id) return;
-#ifdef BL00MBOX_LOOPS_ENABLE
-    if(plugin->is_being_rendered) return;
-#endif
-    plugin->is_being_rendered = true;
-    plugin->rugin->render(plugin->rugin, full_buffer_len, render_pass_id);
-    plugin->render_pass_id = render_pass_id;
-    plugin->is_being_rendered = false;
-}
-
-static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t * out, bool adding){
-    chan->render_pass_id = render_pass_id;
+static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int32_t * out, bool adding){
+    chan->render_pass_id = bl00mbox_render_pass_id;
 
     int32_t vol = radspa_mult_shift(chan->volume, chan->sys_gain);
     if(!vol) return false; // don't render if muted
@@ -233,25 +201,26 @@ static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t *
     // render these even if nothing is plugged into mixer
     if(chan->render_plugins){
         for(size_t i = 0; i < chan->render_plugins->len; i++){
-            bl00mbox_audio_plugin_render(chan->render_plugins->elems[i]);
+            bl00mbox_plugin_t * plugin = chan->render_plugins->elems[i];
+            radspa_render_plugin(plugin->rugin, bl00mbox_render_pass_id);
         }
     }
 
-    bl00mbox_channel_plugin_update_values(chan->channel_plugin->rugin, render_pass_id);
+    bl00mbox_channel_plugin_update_values(chan->channel_plugin->rugin, bl00mbox_render_pass_id);
 
     if(!(chan->render_buffers && chan->render_buffers->len)) return false;
 
 
-    int32_t acc[full_buffer_len];
+    int32_t acc[BL00MBOX_BUFFER_LEN];
 
     // first one non-adding
     int16_t * buffer = chan->render_buffers->elems[0];
     if(buffer[1] == -32768){
-        for(size_t i = 0; i < full_buffer_len; i++){
+        for(size_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
             acc[i] = buffer[0];
         }
     } else {
-        for(size_t i = 0; i < full_buffer_len; i++){
+        for(size_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
             acc[i] = buffer[i];
         }
     }
@@ -261,18 +230,18 @@ static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t *
         buffer = chan->render_buffers->elems[i];
         if(buffer[1] == -32768){
             if(buffer[0]){
-                for(size_t i = 0; i < full_buffer_len; i++){
+                for(size_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
                     acc[i] += buffer[0];
                 }
             }
         } else {
-            for(size_t i = 0; i < full_buffer_len; i++){
+            for(size_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
                 acc[i] += buffer[i];
             }
         }
     }
 
-    for(uint16_t i = 0; i < full_buffer_len; i++){
+    for(uint16_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
         // flip around for rounding towards zero/mulsh boost
         int invert = chan->dc < 0 ? -1 : 1;
         chan->dc = chan->dc * invert;
@@ -284,16 +253,16 @@ static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t *
         acc[i] -= (chan->dc >> 12);
     }
     if(adding){
-        for(uint16_t i = 0; i < full_buffer_len; i++){
-            out[i] = radspa_add_sat(radspa_gain(acc[i], vol), out[i]);
+        for(uint16_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
+            out[i] += radspa_gain(acc[i], vol);
         }
     } else {
-        for(uint16_t i = 0; i < full_buffer_len; i++){
+        for(uint16_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
             out[i] = radspa_gain(acc[i], vol);
         }
     }
     if(chan->compute_rms){
-        for(uint16_t i = 0; i < full_buffer_len; i++){
+        for(uint16_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
             int32_t sq = acc[i];
             sq = (sq * sq) - chan->mean_square;
             // always round down with negative sq so that decay always works.
@@ -305,24 +274,31 @@ static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t *
     return true;
 }
 
-static bool bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t * out, bool adding){
-    if(render_pass_id == chan->render_pass_id) return false;
-    bl00mbox_take_lock(&chan->render_lock);
-    bool ret = _bl00mbox_audio_channel_render(chan, out, adding);
-    bl00mbox_give_lock(&chan->render_lock);
-    // null it out if nothing was rendered
-    chan->mean_square *= ret;
+static bool bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int32_t * out, bool adding){
+    if(bl00mbox_render_pass_id == chan->render_pass_id) return false;
+    bool ret;
+    if(chan->compute_time){
+        uint32_t time = bl00mbox_get_time();
+        bl00mbox_take_lock(&chan->render_lock);
+        ret = _bl00mbox_audio_channel_render(chan, out, adding);
+        bl00mbox_give_lock(&chan->render_lock);
+        // esp-timer: max 1 sec
+        time = (bl00mbox_get_time() - time) & 0xFFFFF;
+        chan->time = ((chan->time * 63)>>6) + time;
+    } else {
+        bl00mbox_take_lock(&chan->render_lock);
+        ret = _bl00mbox_audio_channel_render(chan, out, adding);
+        bl00mbox_give_lock(&chan->render_lock);
+    }
     return ret;
 }
 
-void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len){
-    full_buffer_len = len/2;
+bool _bl00mbox_audio_render(int16_t * rx, int32_t *acc){
     bl00mbox_line_in_interlaced = rx;
-    int16_t acc[full_buffer_len];
     bool acc_init = false;
 
     bl00mbox_take_lock(&active_chans_lock);
-    render_pass_id++;
+    bl00mbox_render_pass_id++;
     if(active_chans){
         for(size_t i = 0; i < active_chans->len; i++){
             acc_init = bl00mbox_audio_channel_render(active_chans->elems[i], acc, acc_init) || acc_init;
@@ -330,14 +306,36 @@ void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len){
     }
     bl00mbox_give_lock(&active_chans_lock);
 
+    return acc_init;
+}
+
+bool bl00mbox_audio_render(int16_t * rx, int16_t * tx){
+    int32_t acc[BL00MBOX_BUFFER_LEN];
+    bool acc_init = _bl00mbox_audio_render(rx, acc);
+
     if(acc_init){
-        for(uint16_t i = 0; i < full_buffer_len; i++){
-            tx[2*i] = acc[i];
-            tx[2*i+1] = acc[i];
+        for(uint16_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
+            int16_t val = radspa_clip(acc[i]);
+            tx[2*i] = val;
+            tx[2*i+1] = val;
         }
-    } else {
-        memset(tx, 0, len * sizeof(int16_t));
     }
+
+    return acc_init;
+}
+
+bool bl00mbox_audio_render_adding(int16_t * rx, int16_t * tx){
+    int32_t acc[BL00MBOX_BUFFER_LEN];
+    bool acc_init = _bl00mbox_audio_render(rx, acc);
+
+    if(acc_init){
+        for(uint16_t i = 0; i < BL00MBOX_BUFFER_LEN; i++){
+            tx[2*i] += radspa_add_sat(tx[2*i], acc[i]);
+            tx[2*i+1] += radspa_add_sat(tx[2*i+1], acc[i]);
+        }
+    }
+
+    return acc_init;
 }
 
 void bl00mbox_audio_init(){
diff --git a/components/bl00mbox/bl00mbox_containers.c b/components/bl00mbox/bl00mbox_containers.c
index 097d57d32ba4dd6dc9dd8b6b00d780e472a244de..df6f818527776e3eb380dc032c21b74c6ff2b0bf 100644
--- a/components/bl00mbox/bl00mbox_containers.c
+++ b/components/bl00mbox/bl00mbox_containers.c
@@ -84,3 +84,10 @@ bl00mbox_array_t * bl00mbox_set_to_array(bl00mbox_set_t * set){
     }
     return ret;
 }
+
+void bl00mbox_deref(bl00mbox_ref_t * ref, void * self, char * name){
+    if(* (ref->self_ref) != self){
+        bl00mbox_log_error("%s: parent_self_ref improper", name);
+    }
+    * (ref->self_ref) = NULL;
+}
diff --git a/components/bl00mbox/bl00mbox_patches.c b/components/bl00mbox/bl00mbox_patches.c
new file mode 100644
index 0000000000000000000000000000000000000000..ac3d1228fd41e7977d04dc260588fae2d184a7c5
--- /dev/null
+++ b/components/bl00mbox/bl00mbox_patches.c
@@ -0,0 +1,69 @@
+#include "bl00mbox_audio.h"
+#include "bl00mbox_user.h"
+
+void bl00mbox_clear_patch_container(bl00mbox_patch_container_t * container){
+    bl00mbox_set_iter_t iter;
+    bl00mbox_set_iter_start(&iter, &container->plugin_t);
+    bl00mbox_plugin_t * plugin;
+    while((plugin = bl00mbox_set_iter_next(&iter))){
+        bl00mbox_plugin_destroy(plugin);
+    }
+    bl00mbox_set_iter_start(&iter, &container->patch_t);
+    bl00mbox_patch_t * patch;
+    while((patch = bl00mbox_set_iter_next(&iter))){
+        bl00mbox_patch_destroy(patch);
+    }
+}
+
+bl00mbox_array_t * bl00mbox_parents_from_patch_container(bl00mbox_patch_container_t * container){
+    size_t len = container->plugin_t.len + container->patch_t.len;
+
+    bl00mbox_array_t * parents = malloc(sizeof(bl00mbox_array_t) + len * sizeof(void *));
+    if(!parents){
+        bl00mbox_log_error("out of memory");
+        return NULL;
+    }
+    
+    size_t index = 0;
+    bl00mbox_set_iter_t iter;
+    bl00mbox_set_iter_start(&iter, &container->plugin_t);
+    bl00mbox_plugin_t * plugin;
+    while((plugin = bl00mbox_set_iter_next(&iter))){
+        parents->elems[index] = plugin->parent;
+        index++;
+    }
+    bl00mbox_set_iter_start(&iter, &container->patch_t);
+    bl00mbox_patch_t * patch;
+    while((patch = bl00mbox_set_iter_next(&iter))){
+        parents->elems[index] = patch->parent;
+        index++;
+    }
+    parents->len = index;
+    return parents;
+}
+
+static inline bl00mbox_set_t * patch_get_set(bl00mbox_patch_t * patch){
+    bl00mbox_patch_container_t * c = patch->container ? &patch->container->patches : &patch->channel->patches;
+    return &c->patch_t;
+}
+
+bl00mbox_patch_t * bl00mbox_patch_create(bl00mbox_channel_t * chan, bl00mbox_patch_t * container){
+    bl00mbox_patch_t * patch = calloc(1, sizeof(bl00mbox_patch_t));
+    if(!patch) return NULL;
+    patch->channel = chan;
+    patch->container = container;
+
+    if(!bl00mbox_set_add_skip_unique_check(patch_get_set(patch), patch)){
+        free(patch);
+        return NULL;
+    }
+    return patch;
+}
+void bl00mbox_patch_destroy(bl00mbox_patch_t * patch){
+    bl00mbox_clear_patch_container(&patch->patches);
+
+    bl00mbox_set_remove(patch_get_set(patch), patch);
+
+    bl00mbox_deref(&patch->ref, patch, "patch");
+    free(patch);
+}
diff --git a/components/bl00mbox/bl00mbox_user.c b/components/bl00mbox/bl00mbox_plugins_signals.c
similarity index 91%
rename from components/bl00mbox/bl00mbox_user.c
rename to components/bl00mbox/bl00mbox_plugins_signals.c
index b836d1830fb84699dfcc5ceb4101b1694ea18ce8..6de8502c3491e1b61199f0ae35c6b4d7647b38d7 100644
--- a/components/bl00mbox/bl00mbox_user.c
+++ b/components/bl00mbox/bl00mbox_plugins_signals.c
@@ -11,8 +11,11 @@ radspa_signal_t * bl00mbox_signal_get_by_index(radspa_t * plugin, uint16_t signa
     return &(plugin->signals[signal_index]);
 }
 
+static inline bl00mbox_connection_t * conn_from_rignal(radspa_signal_t * rignal){
+    return (bl00mbox_connection_t *) rignal->buffer;
+}
 static inline bl00mbox_connection_t * conn_from_signal(bl00mbox_signal_t * signal){
-    return (bl00mbox_connection_t *) signal->rignal->buffer;
+    return conn_from_rignal(signal->rignal);
 }
 
 static bool signal_is_input(bl00mbox_signal_t * signal){
@@ -45,18 +48,18 @@ bl00mbox_connection_t * bl00mbox_connection_from_signal(bl00mbox_signal_t * sign
 }
 
 uint16_t bl00mbox_channel_plugins_num(bl00mbox_channel_t * chan){
-    return chan->plugins.len;
+    return chan->num_plugins;
 }
 
 bl00mbox_array_t * bl00mbox_channel_collect_plugins(bl00mbox_channel_t * chan){
-    unsigned int len = chan->plugins.len;
+    unsigned int len = chan->patches.plugin_t.len;
     bl00mbox_array_t * ret;
     ret = malloc(sizeof(bl00mbox_array_t) + len * sizeof(void *));
     if(!ret) return NULL;
     ret->len = len;
 
     bl00mbox_set_iter_t iter;
-    bl00mbox_set_iter_start(&iter, &chan->plugins);
+    bl00mbox_set_iter_start(&iter, &chan->patches.plugin_t);
     bl00mbox_plugin_t * plugin;
     size_t i = 0;
     while((plugin = bl00mbox_set_iter_next(&iter))) ret->elems[i++] = plugin;
@@ -140,7 +143,7 @@ static void update_roots(bl00mbox_channel_t * chan){
     bl00mbox_set_iter_start(&iter, roots);
     bl00mbox_connection_t * conn;
     while((conn = bl00mbox_set_iter_next(&iter))){
-        render_buffers->elems[index] = conn->buffer;
+        render_buffers->elems[index] = &conn->buffer.data;
         render_plugins->elems[index] = conn->source.plugin;
         index++;
     }
@@ -152,7 +155,7 @@ static void update_roots(bl00mbox_channel_t * chan){
         render_buffers->elems[index] = output_rignal->buffer;
         render_buffers->len++;
 
-        conn = output_rignal->buffer;
+        conn = conn_from_rignal(output_rignal);
         bl00mbox_plugin_t * plugin = conn->source.plugin;
         bool is_duplicate = false;
         for(size_t rindex = 0; rindex < index; rindex++){
@@ -213,6 +216,14 @@ defer:
     free(render_plugins_prev);
     free(render_buffers_prev);
 }
+static inline bl00mbox_set_t * plugin_get_set(bl00mbox_plugin_t * plugin){
+#ifdef BL00MBOX_DEBUG
+    char * txt = plugin->container ? " " : " not ";
+    bl00mbox_log_info("plugin is%sin container", txt);
+#endif
+    bl00mbox_patch_container_t * c = plugin->container ? &plugin->container->patches : &plugin->channel->patches;
+    return &c->plugin_t;
+}
 
 bl00mbox_plugin_t * bl00mbox_plugin_create_unlisted(bl00mbox_channel_t * chan, radspa_descriptor_t * desc, uint32_t init_var){
     // doesn't instantiate, don't free
@@ -223,38 +234,34 @@ bl00mbox_plugin_t * bl00mbox_plugin_create_unlisted(bl00mbox_channel_t * chan, r
 
     plugin->init_var = init_var;
     plugin->rugin = rugin;
+    plugin->render = rugin->render;
     plugin->channel = chan;
     plugin->id = chan->plugin_id++;
     return plugin;
 }
 
-bl00mbox_plugin_t * bl00mbox_plugin_create(bl00mbox_channel_t * chan, uint32_t id, uint32_t init_var){
+bl00mbox_plugin_t * bl00mbox_plugin_create(bl00mbox_channel_t * chan, bl00mbox_patch_t * container, uint32_t id, uint32_t init_var){
     // doesn't instantiate, don't free
     radspa_descriptor_t * desc = bl00mbox_plugin_registry_get_descriptor_from_id(id);
     if(desc == NULL) return NULL;
 
     bl00mbox_plugin_t * plugin = bl00mbox_plugin_create_unlisted(chan, desc, init_var);
 
-    if(!bl00mbox_set_add_skip_unique_check(&chan->plugins, plugin)){
+    plugin->container = container;
+
+    if(!bl00mbox_set_add_skip_unique_check(plugin_get_set(plugin), plugin)){
         plugin->rugin->descriptor->destroy_plugin_instance(plugin->rugin);
         free(plugin);
         return NULL;
     }
-
+    chan->num_plugins += 1;
     bl00mbox_channel_event(chan);
     return plugin;
 }
 
-void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin){
+void bl00mbox_plugin_destroy_unlisted(bl00mbox_plugin_t * plugin){
     bl00mbox_channel_t * chan = plugin->channel;
 
-    // remove from parent
-    if(* (plugin->parent_self_ref) != plugin){
-        bl00mbox_log_error("plugin: parent_self_ref improper: plugin %s, %p, ref %p",
-            plugin->rugin->descriptor->name, plugin, * (plugin->parent_self_ref));
-    }
-    * (plugin->parent_self_ref) = NULL;
-
     // disconnect all signals
     int num_signals = plugin->rugin->len_signals;
     for(int i = 0; i < num_signals; i++){
@@ -268,7 +275,7 @@ void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin){
 
     // pop from sets
     bl00mbox_plugin_set_always_render(plugin, false);
-    bl00mbox_set_remove(&chan->plugins, plugin);
+    bl00mbox_set_remove(plugin_get_set(plugin), plugin);
 
     if(plugin == chan->channel_plugin) chan->channel_plugin = NULL;
 
@@ -276,11 +283,19 @@ void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin){
     free(plugin);
 }
 
+void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin){
+    bl00mbox_channel_t * chan = plugin->channel;
+    chan->num_plugins -= 1;
+    bl00mbox_deref(&plugin->ref, plugin, "plugin");
+    bl00mbox_plugin_destroy_unlisted(plugin);
+}
+
 
 static bl00mbox_connection_t * create_connection(bl00mbox_signal_t * source){
     if(!signal_is_output(source)) return NULL;
     bl00mbox_connection_t * ret = calloc(1, sizeof(bl00mbox_connection_t));
     if(ret == NULL) return NULL;
+    ret->buffer.plugin = source->plugin->rugin;
     ret->subscribers.key = signal_equals;
     memcpy(&ret->source, source, sizeof(bl00mbox_signal_t));
 
@@ -484,7 +499,6 @@ void bl00mbox_plugin_set_always_render(bl00mbox_plugin_t * plugin, bool value){
 }
 
 bl00mbox_error_t bl00mbox_signal_set_value(bl00mbox_signal_t * signal, int value){
-    //while(signal->plugin->is_being_rendered) {};
     if(!signal_is_input(signal)) return BL00MBOX_ERROR_INVALID_CONNECTION;
     if(conn_from_signal(signal)) bl00mbox_signal_disconnect(signal);
     signal->rignal->value = value < -32767 ? -32767 : (value > 32767 ? 32767 : value);
@@ -492,6 +506,5 @@ bl00mbox_error_t bl00mbox_signal_set_value(bl00mbox_signal_t * signal, int value
 }
 
 int16_t bl00mbox_signal_get_value(bl00mbox_signal_t * signal){
-    //while(signal->plugin->is_being_rendered) {};
-    return signal->rignal->buffer ? signal->rignal->buffer[0] : signal->rignal->value;
+    return signal->rignal->buffer ? signal->rignal->buffer->data[0] : signal->rignal->value;
 }
diff --git a/components/bl00mbox/bl00mbox_radspa_requirements.c b/components/bl00mbox/bl00mbox_radspa_requirements.c
index 509d58854c53ff680c891e4046af4daa78261e58..2f7d293419b91b6ee4afe44497f8b3fc627929d2 100644
--- a/components/bl00mbox/bl00mbox_radspa_requirements.c
+++ b/components/bl00mbox/bl00mbox_radspa_requirements.c
@@ -1,11 +1,19 @@
 //SPDX-License-Identifier: CC0-1.0
+#include "bl00mbox_audio.h"
 #include "bl00mbox_radspa_requirements.h"
 
-bool radspa_host_request_buffer_render(int16_t * buf){
-    bl00mbox_plugin_t * plugin = ((bl00mbox_connection_t *) buf)->source.plugin;
-    bl00mbox_audio_plugin_render(plugin);
-    return 1;
-}
+// value: 2400*math.log(48000/RADSPA_SAMPLE_RATE)
+#if RADSPA_SAMPLE_RATE == 48000
+#define RADSPA_SCT_TO_REL_FREQ_OFFSET 0
+#endif
+// we haven't tested anything besides 48k yet, there's plenty hardcoded stuff,
+// might result in memory corruption
+// note: this is right in the middle so we're like a quarter cent off :/
+#if RADSPA_SAMPLE_RATE == 44100
+#define RADSPA_SCT_TO_REL_FREQ_OFFSET 293
+#endif
+
+// ideally we should recalc these per sample rate but we forgor how it works
 
 // py: bigtable = [int(22/200*(2**(14-5+8+x*4096/2400/64))) for x in range(64)]
 // for 48kHz main sample rate
@@ -39,8 +47,7 @@ uint32_t radspa_sct_to_rel_freq(int16_t sct, int16_t undersample_pow){
     /// when sampled at (48>>undersample_pow)kHz
 
     // compiler explorer says this is 33 instructions with O2. might be alright?
-    uint32_t a = sct;
-    a = sct + 28*2400 - 32767 - 330;
+    uint32_t a = sct + 28*2400 - 32767 - 330 + RADSPA_SCT_TO_REL_FREQ_OFFSET;
     // at O2 u get free division for each modulo. still slow, 10 instructions or so.
     int16_t octa = a / 2400;
     a = a % 2400;
diff --git a/components/bl00mbox/config/bl00mbox_config.h b/components/bl00mbox/config/bl00mbox_config.h
index 37a2ddeefbcb10e7f4d8d4ec3a8b14f50fa433af..5743fbf056205370ad1c2a7dc0fa8d8e939a43a4 100644
--- a/components/bl00mbox/config/bl00mbox_config.h
+++ b/components/bl00mbox/config/bl00mbox_config.h
@@ -4,9 +4,8 @@
 //#define BL00MBOX_DEBUG
 
 #ifdef BL00MBOX_FLOW3R
-//#include "flow3r_bsp.h"
-//#define BL00MBOX_MAX_BUFFER_LEN FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE
-#define BL00MBOX_MAX_BUFFER_LEN 64
+#define BL00MBOX_SAMPLE_RATE 48000
+#define BL00MBOX_BUFFER_LEN 64
 #define BL00MBOX_DEFAULT_CHANNEL_VOLUME 8192
 #define BL00MBOX_AUTO_FOREGROUNDING
 #define BL00MBOX_LOOPS_ENABLE
diff --git a/components/bl00mbox/config/radspa_config.h b/components/bl00mbox/config/radspa_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d230803b53c286a6a107291356f88880b1a22d9
--- /dev/null
+++ b/components/bl00mbox/config/radspa_config.h
@@ -0,0 +1,4 @@
+#pragma once
+#include "bl00mbox_config.h"
+#define RADSPA_BUFFER_LEN BL00MBOX_BUFFER_LEN
+#define RADSPA_SAMPLE_RATE BL00MBOX_SAMPLE_RATE
diff --git a/components/bl00mbox/include/bl00mbox.h b/components/bl00mbox/include/bl00mbox.h
index 8a57cf28469decf67d45b0f52868372d6b514f78..34dc6a0437cc6a7777f80aa97f1c21b941c6b9f5 100644
--- a/components/bl00mbox/include/bl00mbox.h
+++ b/components/bl00mbox/include/bl00mbox.h
@@ -5,8 +5,6 @@
 
 #define SAMPLE_RATE 48000
 
-uint16_t bl00mbox_sources_count();
-uint16_t bl00mbox_source_add(void* render_data, void* render_function);
-void bl00mbox_source_remove(uint16_t index);
-bool bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len);
+bool bl00mbox_audio_render(int16_t * rx, int16_t * tx);
+bool bl00mbox_audio_render_adding(int16_t * rx, int16_t * tx);
 void bl00mbox_init(void);
diff --git a/components/bl00mbox/include/bl00mbox_audio.h b/components/bl00mbox/include/bl00mbox_audio.h
index 41552c44a118af95e792f300e793bc09322d915a..17850e83837fcdaaad967a869869c4d7db8ea240 100644
--- a/components/bl00mbox/include/bl00mbox_audio.h
+++ b/components/bl00mbox/include/bl00mbox_audio.h
@@ -10,27 +10,34 @@
 #include "radspa.h"
 #include "radspa_helpers.h"
 
+extern int16_t * bl00mbox_line_in_interlaced;
+
 struct _bl00mbox_plugin_t;
 struct _bl00mbox_connection_source_t;
 struct _bl00mbox_channel_root_t;
 struct _bl00mbox_channel_t;
+struct _bl00mbox_patch_t;
 
-extern int16_t * bl00mbox_line_in_interlaced;
+typedef struct _bl00mbox_patch_container_t {
+    bl00mbox_set_t plugin_t;
+    bl00mbox_set_t patch_t;
+} bl00mbox_patch_container_t;
 
 // pointer is unique identifier, no memcpy of this!
 typedef struct _bl00mbox_plugin_t{
     radspa_t * rugin; // radspa plugin
+    radspa_render_t render;
     char * name;
     uint32_t id; // unique number in channel to for UI purposes
 
-    uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary
     uint32_t init_var; // init var that was used for plugin creation
-    volatile bool is_being_rendered; // true if rendering the plugin is in progress, else false.
     bool always_render;
     struct _bl00mbox_channel_t * channel; // channel that owns the plugin
 
+    struct _bl00mbox_patch_t * container;
+
     void * parent;
-    struct _bl00mbox_plugin_t ** parent_self_ref;
+    bl00mbox_ref_t ref;
 } bl00mbox_plugin_t;
 
 // pointer is NOT unique identifier, memcpy allowed
@@ -41,7 +48,7 @@ typedef struct {
 } bl00mbox_signal_t;
 
 typedef struct _bl00mbox_connection_t{ //child of bl00mbox_ll_t
-    int16_t buffer[BL00MBOX_MAX_BUFFER_LEN]; // MUST stay on top of struct bc type casting! TODO: offsetof()
+    radspa_buffer_t buffer; // MUST stay on top of struct bc type casting! TODO: offsetof()
     bl00mbox_signal_t source;
     bl00mbox_set_t subscribers; // content: bl00mbox_signal_t;
     bool connected_to_mixer; // legacy thing, don't wanna sentinel subsribers
@@ -49,9 +56,10 @@ typedef struct _bl00mbox_connection_t{ //child of bl00mbox_ll_t
 
 // pointer is unique identifier, no memcpy of this!
 typedef struct _bl00mbox_channel_t{
-    char * name;
+    int32_t dev;
 
     int32_t volume;
+
     bool background_mute_override;
 
     // secondary gain that the channel user is supposed to leave untouched so that the OS
@@ -66,19 +74,27 @@ typedef struct _bl00mbox_channel_t{
     // you have to only do the log operation and can skip the root as you can express roots
     // as divisions in the log domain which is much cheaper.
     uint32_t mean_square;
+
     // average output used for DC blocking.
     int32_t dc;
 
+    // used for profiling
+    bool compute_time;
+    uint32_t time;
+
     uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary
 
     uint32_t plugin_id; // used to give each plugin in channel a unique identifier
 
+    uint32_t num_plugins;
+
     // we render all of these and add them up. it's a legacy thing from when we had a output mixer
     // but that kinda mixes poorly (heh) with our general API.
     bl00mbox_set_t roots; // content: bl00mbox_connection_t
     bl00mbox_set_t connections; // content: bl00mbox_connection_t
     bl00mbox_set_t always_render; // content: bl00mbox_plugin_t
-    bl00mbox_set_t plugins; // content: bl00mbox_plugin_t
+
+    struct _bl00mbox_patch_container_t patches;
 
     bl00mbox_plugin_t * channel_plugin;
 
@@ -88,14 +104,13 @@ typedef struct _bl00mbox_channel_t{
     bl00mbox_array_t * render_buffers;
 
     void * parent;
-    struct _bl00mbox_channel_t ** parent_self_ref;
-    struct _bl00mbox_channel_t ** weak_parent_self_ref;
+    bl00mbox_ref_t ref;
+    bl00mbox_ref_t weak_ref;
 } bl00mbox_channel_t;
 
 void bl00mbox_audio_init();
 
 bl00mbox_channel_t * bl00mbox_channel_create();
-void bl00mbox_audio_plugin_render(bl00mbox_plugin_t * plugin);
 bool bl00mbox_channel_get_foreground(bl00mbox_channel_t * chan);
 void bl00mbox_channel_set_foreground(bl00mbox_channel_t * chan, bool enable);
 void bl00mbox_channel_set_background_mute_override(bl00mbox_channel_t * chan, bool enable);
@@ -103,3 +118,23 @@ void bl00mbox_channel_clear(bl00mbox_channel_t * chan);
 void bl00mbox_channel_destroy(bl00mbox_channel_t * chan);
 void bl00mbox_channel_event(bl00mbox_channel_t * channel);
 bl00mbox_array_t * bl00mbox_collect_channels(bool active);
+
+// pointer is unique identifier, no memcpy of this!
+typedef struct _bl00mbox_patch_t {
+    uint32_t id; // unique number in channel to for UI purposes
+
+    struct _bl00mbox_channel_t * channel; // channel that owns the patch
+
+    struct _bl00mbox_patch_container_t patches;
+
+    struct _bl00mbox_patch_t * container;
+
+    void * parent;
+    bl00mbox_ref_t ref;
+} bl00mbox_patch_t;
+
+bl00mbox_patch_t * bl00mbox_patch_create(bl00mbox_channel_t * chan, bl00mbox_patch_t * container);
+void bl00mbox_patch_destroy(bl00mbox_patch_t * patch);
+void bl00mbox_clear_patch_container(bl00mbox_patch_container_t * container);
+bl00mbox_array_t * bl00mbox_parents_from_patch_container(bl00mbox_patch_container_t * container);
+
diff --git a/components/bl00mbox/include/bl00mbox_containers.h b/components/bl00mbox/include/bl00mbox_containers.h
index 71dfb6ea3925abaf6628333b78fa1edc577d82b6..bd4ef52110716f332b12a0486f98e350db2ce993 100644
--- a/components/bl00mbox/include/bl00mbox_containers.h
+++ b/components/bl00mbox/include/bl00mbox_containers.h
@@ -22,7 +22,7 @@ typedef struct {
 
 typedef struct {
     bl00mbox_ll_t * next;
-}bl00mbox_set_iter_t;
+} bl00mbox_set_iter_t;
 
 bool bl00mbox_set_contains(bl00mbox_set_t * set, void * content);
 bool bl00mbox_set_add_skip_unique_check(bl00mbox_set_t * set, void * content);
@@ -34,3 +34,9 @@ void bl00mbox_set_iter_start(bl00mbox_set_iter_t * iter, bl00mbox_set_t * set);
 void * bl00mbox_set_iter_next(bl00mbox_set_iter_t * iter);
 
 bl00mbox_array_t * bl00mbox_set_to_array(bl00mbox_set_t * set);
+
+typedef struct {
+    void ** self_ref;
+} bl00mbox_ref_t;
+
+void bl00mbox_deref(bl00mbox_ref_t * ref, void * self, char * name);
diff --git a/components/bl00mbox/include/bl00mbox_os.h b/components/bl00mbox/include/bl00mbox_os.h
index 59964962a9c7b7493b46b0eb95a5783964eacaca..6fdf4f688b4368f0248f267ec02a81694a9985d6 100644
--- a/components/bl00mbox/include/bl00mbox_os.h
+++ b/components/bl00mbox/include/bl00mbox_os.h
@@ -26,6 +26,8 @@ void bl00mbox_wait();
 
 #ifdef BL00MBOX_ESPIDF
 #include "esp_log.h"
+#define bl00mbox_get_time() esp_timer_get_time()
+#define BL00MBOX_REF_TIME ((1000000. * 64 * BL00MBOX_BUFFER_LEN) / BL00MBOX_SAMPLE_RATE)
 #define bl00mbox_log_error(txt, ...) ESP_LOGE("bl00mbox", txt, ##__VA_ARGS__);
 #ifdef BL00MBOX_DEBUG
 #define bl00mbox_log_info(txt, ...) ESP_LOGE("bl00mbox", txt, ##__VA_ARGS__);
diff --git a/components/bl00mbox/include/bl00mbox_patches.h b/components/bl00mbox/include/bl00mbox_patches.h
new file mode 100644
index 0000000000000000000000000000000000000000..f1e5fef2dadf51957a1edf3e8952c35fd89e9516
--- /dev/null
+++ b/components/bl00mbox/include/bl00mbox_patches.h
@@ -0,0 +1,29 @@
+//SPDX-License-Identifier: CC0-1.0
+#pragma once
+#include "bl00mbox_containers.h"
+#include "bl00mbox_audio.h"
+
+extern int16_t * bl00mbox_line_in_interlaced;
+
+typedef struct {
+    bl00mbox_set_t plugin_t;
+    bl00mbox_set_t patch_t;
+} bl00mbox_patch_container_t;
+
+// pointer is unique identifier, no memcpy of this!
+typedef struct _bl00mbox_patch_t{
+    uint32_t id; // unique number in channel to for UI purposes
+
+    struct _bl00mbox_channel_t * channel; // channel that owns the patch
+
+    bl00mbox_patch_container_t * patches;
+
+    struct _bl00mbox_patch_t * container;
+
+    void * parent;
+    bl00mbox_ref_t ref;
+} bl00mbox_patch_t;
+
+bl00mbox_patch_t * bl00mbox_patch_create(bl00mbox_channel_t * chan, bl00mbox_patch_t * container);
+void bl00mbox_patch_destroy(bl00mbox_patch_t * patch);
+void bl00mbox_clear_patch_container(bl00mbox_patch_container_t * container);
diff --git a/components/bl00mbox/include/bl00mbox_user.h b/components/bl00mbox/include/bl00mbox_user.h
index 254720aef1cc63ed43888df8262336cf44bd5736..d3f6961e22f67bd888e8b6ba3bf25a4800255b30 100644
--- a/components/bl00mbox/include/bl00mbox_user.h
+++ b/components/bl00mbox/include/bl00mbox_user.h
@@ -19,11 +19,13 @@
 uint16_t bl00mbox_channel_plugins_num(bl00mbox_channel_t * chan);
 uint16_t bl00mbox_channel_conns_num(bl00mbox_channel_t * chan);
 uint16_t bl00mbox_channel_mixer_num(bl00mbox_channel_t * chan);
+
 bl00mbox_array_t * bl00mbox_channel_collect_plugins(bl00mbox_channel_t * chan);
 bl00mbox_array_t * bl00mbox_channel_collect_connections_mx(bl00mbox_channel_t * chan);
 
 bl00mbox_plugin_t * bl00mbox_plugin_create_unlisted(bl00mbox_channel_t * chan, radspa_descriptor_t * desc, uint32_t init_var);
-bl00mbox_plugin_t * bl00mbox_plugin_create(bl00mbox_channel_t * chan, uint32_t id, uint32_t init_var);
+bl00mbox_plugin_t * bl00mbox_plugin_create(bl00mbox_channel_t * chan, bl00mbox_patch_t * container, uint32_t id, uint32_t init_var);
+void bl00mbox_plugin_destroy_unlisted(bl00mbox_plugin_t * plugin);
 void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin);
 void bl00mbox_plugin_set_always_render(bl00mbox_plugin_t * plugin, bool value);
 
diff --git a/components/bl00mbox/micropython/bl00mbox/__init__.py b/components/bl00mbox/micropython/bl00mbox/__init__.py
index 6f4058f79d0c57320640a8c483e797c273f29cc1..ffa92e39700b548168b591ebc53bbc804af3a95f 100644
--- a/components/bl00mbox/micropython/bl00mbox/__init__.py
+++ b/components/bl00mbox/micropython/bl00mbox/__init__.py
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: CC0-1.0
 
 from bl00mbox._user import *
-import bl00mbox._patches as patches
 import bl00mbox._helpers as helpers
 from bl00mbox._plugins import plugins
+import bl00mbox._patches as patches
diff --git a/components/bl00mbox/micropython/bl00mbox/_helpers.py b/components/bl00mbox/micropython/bl00mbox/_helpers.py
index 9ae8b1f621596dfa98ec132dbfeb95dfd1a9bbf5..0bba3323de9d8c385977bec0c2b66720861db285 100644
--- a/components/bl00mbox/micropython/bl00mbox/_helpers.py
+++ b/components/bl00mbox/micropython/bl00mbox/_helpers.py
@@ -1,6 +1,13 @@
 # SPDX-License-Identifier: CC0-1.0
 import time
 
+indent = "  "
+indent_nl = "\n" + indent
+
+def indentify(text):
+    if isinstance(text, str):
+        text = text.split("\n")
+    return indent + indent_nl.join(text)
 
 def terminal_scope(
     signal,
@@ -72,3 +79,18 @@ def note_name_to_sct(name):
 
 def sct_to_freq(sct):
     return 440 * 2 ** ((sct - 18367) / 2400)
+
+class _CoreKeyFallthrough:
+    _core_keys = {}
+
+    def __setattr__(self, key, value):
+        if key in self._core_keys:
+            setattr(self._core, key, value)
+        else:
+            super().__setattr__(key, value)
+
+    def __getattr__(self, key):
+        if key in self._core_keys:
+            return getattr(self._core, key)
+        else:
+            raise AttributeError(f"'{type(self)}' object has no attribute {key}")
diff --git a/components/bl00mbox/micropython/bl00mbox/_patches.py b/components/bl00mbox/micropython/bl00mbox/_patches.py
index b63552ea22db6b6c1f07caf1cef7e4c8b17cb5b1..7ed9f2c42bd881cd0f210f28f58f0b93bb5a31d7 100644
--- a/components/bl00mbox/micropython/bl00mbox/_patches.py
+++ b/components/bl00mbox/micropython/bl00mbox/_patches.py
@@ -3,48 +3,44 @@ import math
 import os
 import bl00mbox
 import cpython.wave as wave
+import sys_bl00mbox
+from bl00mbox import _plugins as plugins
+from bl00mbox._helpers import indentify as dent
 
-
-class _Patch:
+class _Patch(plugins.PluginSpawner):
+    _core_keys = ("name", "container", "delete")
+    _patch_name = None
     def __init__(self, chan):
-        self.signals = _PatchSignalList()
-        self._channel = chan
-        # old style
-        self.plugins = _PatchPluginList()
-        # new style
-        self._plugins = []
-
-    def new(self, *args, **kwargs):
-        plugin = self._channel.new(*args, **kwargs)
-        self._plugins.append(plugin)
-        return plugin
+        # due to backwards compatibility reasons we're doing some crimes in chan here
+        kw = chan._hacky_kwargs
+        if self._patch_name is None:
+            self._patch_name = self.__class__.__name__
+        self._core = sys_bl00mbox.PatchCore(kw.core, self, kw.container, self._patch_name, kw.name)
+        self._spawn_core = kw.core
+        self._dev = chan.dev
+        if not self._dev:
+            self.signals = _PatchSignalList()
+            self.plugins = _PatchPluginList()
+            self._channel = chan
+        else:
+            self.signals = bl00mbox.SignalContainer()
+        self._weak_channel = chan._weak_channel
+        self._container = self._core
 
     def __repr__(self):
-        ret = "[patch] " + type(self).__name__
-        ret += "\n  [signals:]    " + "\n    ".join(repr(self.signals).split("\n"))
-        # old style
-        plugin_repr = repr(self.plugins)
-        # new style
-        for plugin in self._plugins:
-            plugin_repr += "\n" + repr(plugin)
-        ret += "\n  [plugins:]    " + "\n    ".join(plugin_repr.split("\n"))
+        return self.show()
+
+    def show(self, *, oneliner = False, patches = False, **kwargs):
+        ret = f"[{self._patch_name}]"
+        if self.name:
+            ret += " " + self.name
+        if patches:
+            ret += "\n" + dent(dent([p.show(oneliner = oneliner, patches = True) for p in self._core.get_patches()]))
+        if oneliner:
+            return ret
+        ret += "\n" + dent("[signals]\n" + dent(repr(self.signals)))
         return ret
 
-    def delete(self):
-        # new style
-        plugins = self._plugins[:]
-        # old style
-        for plugin_name in self.plugins.__dict__:
-            if not plugin_name.startswith("_"):
-                plugin_list = self.plugins.__dict__[plugin_name]
-                if (type(plugin_list)) != list:
-                    plugin_list = [plugin_list]
-                plugins += plugin_list
-
-        for plugin in set(plugins):
-            plugin.delete()
-
-
 class _PatchItemList:
     def __init__(self):
         self._items = []
@@ -54,6 +50,9 @@ class _PatchItemList:
         return iter(self._items)
 
     def __repr__(self):
+        return self.show()
+
+    def show(self, **kwargs):
         ret = ""
         for x in self._items:
             a = ("\n" + repr(getattr(self, x))).split("]")
@@ -401,10 +400,10 @@ class fuzz(_Patch):
 class karplus_strong(_Patch):
     def __init__(self, chan):
         super().__init__(chan)
-        self.plugins.noise = chan._new_plugin(bl00mbox.plugins.noise_burst)
+        self.plugins.noise = chan.new(bl00mbox.plugins.noise_burst)
         self.plugins.noise.signals.length = 25
 
-        self.plugins.flanger = chan._new_plugin(bl00mbox.plugins.flanger)
+        self.plugins.flanger = chan.new(bl00mbox.plugins.flanger)
 
         self.plugins.flanger.signals.resonance = 0
         self.plugins.flanger.signals.decay = 1000
diff --git a/components/bl00mbox/micropython/bl00mbox/_plugins.py b/components/bl00mbox/micropython/bl00mbox/_plugins.py
index a1aacf6c80842550fbf0b2ef6e96a64d1e1f3e26..f41053b350aa6737482893e5e49a7f8bd886288f 100644
--- a/components/bl00mbox/micropython/bl00mbox/_plugins.py
+++ b/components/bl00mbox/micropython/bl00mbox/_plugins.py
@@ -6,6 +6,43 @@ import uctypes
 import os
 import cpython.wave as wave
 
+from bl00mbox import _helpers as helpers
+
+class _Container:
+    pass
+
+class PluginSpawner(helpers._CoreKeyFallthrough):
+    def new(self, thing, *args, name = None, **kwargs):
+        if type(thing) == type and issubclass(thing, bl00mbox.patches._Patch):
+            # patches don't pass *args/**kwargs in the field atm
+            # they should tho
+            # but for now we have to do this lil workaround
+            hacky_kwargs = _Container()
+            hacky_kwargs.name = name
+            hacky_kwargs.container = self._container
+            hacky_kwargs.core = self._spawn_core
+            self._weak_channel._hacky_kwargs = hacky_kwargs
+            return thing(self._weak_channel, *args, **kwargs)
+        elif isinstance(thing, bl00mbox._plugins._PluginDescriptor):
+            return bl00mbox._plugins._make_plugin(
+                self._spawn_core, self._container, thing.plugin_id, *args, name = name, **kwargs
+            )
+        else:
+            raise TypeError("invalid plugin/patch")
+
+def _make_plugin(channel_core, container, plugin_id, *args, **kwargs):
+    if plugin_id in _plugin_subclasses:
+        return _plugin_subclasses[plugin_id](channel_core, container, plugin_id, *args, **kwargs)
+    else:
+        init_var = 0
+        _init_var = kwargs.get("init_var", None)
+        if _init_var is None:
+            if len(args) == 1:
+                init_var = int(args[0])
+        else:
+            init_var = _init_var
+        name = kwargs.get("name", None)
+        return _Plugin(channel_core, container, plugin_id, init_var, name)
 
 class _PluginDescriptor:
     def __init__(self, index):
@@ -25,11 +62,7 @@ class _PluginDescriptor:
         )
 
 
-class _PluginDescriptors:
-    pass
-
-
-plugins = _PluginDescriptors()
+plugins = _Container()
 
 
 def _fill():
@@ -50,36 +83,53 @@ def _fill():
 _fill()
 
 
-class _Plugin:
+class _Plugin(helpers._CoreKeyFallthrough):
     _core_keys = (
         "always_render",
         "delete",
         "init_var",
         "table_len",
         "name",
+        "channel",
         "plugin_id",
     )
 
-    def __setattr__(self, key, value):
-        if key in self._core_keys:
-            setattr(self._core, key, value)
-        else:
-            super().__setattr__(key, value)
+    class Render:
+        def __init__(self, core):
+            self._core = core
+            #self.cpu = self.CPU(core)
 
-    def __getattr__(self, key):
-        if key in self._core_keys:
-            return getattr(self._core, key)
-        else:
-            raise AttributeError(f"'{type(self)}' object has no attribute {key}")
+        @property
+        def always(self):
+            return self._core.always_render
 
-    def __init__(self, channel, core=None, plugin_id=None, init_var=0):
-        if core:
-            self._core = core
-        elif plugin_id is not None:
-            self._core = sys_bl00mbox.PluginCore(channel._core, plugin_id, init_var)
+        class CPU:
+            def __init__(self, core):
+                self._core = core
+
+            @property
+            def compute(self):
+                return self._core.compute_render_time
+
+            @compute.setter
+            def compute(self, val):
+                self._core.compute_render_time = bool(val)
+
+            @property
+            def percent(self):
+                return self._core.render_time
+    @property
+    def render(self):
+        if not hasattr(self, "_render"):
+            self._render = self.Render(self._core)
+        return self._render
+
+    def __init__(self, channel_core, container, plugin_id, init_var=0, name = None):
+        self._core = sys_bl00mbox.PluginCore(channel_core, self, container, plugin_id, init_var, name)
+        if self.channel.dev:
+            self.signals = bl00mbox.SignalContainer._from_plugin(self)
         else:
-            raise ValueError("must supply core or plugin id")
-        self._signals = bl00mbox.SignalList(self)
+            self.signals = bl00mbox.SignalList(self)
 
     def _repr_no_signals(self):
         id_num = self._core.id
@@ -90,19 +140,24 @@ class _Plugin:
             return f"[{self._core.name}]"
 
     def __repr__(self):
+        return self.show()
+
+    def show(self, *, oneliner = False, **kwargs):
         ret = self._repr_no_signals()
-        for sig in self.signals._list:
-            ret += "\n  " + "\n  ".join(sig._no_desc().split("\n"))
-        return ret
+        if oneliner:
+            return ret
+        if isinstance(self.signals, bl00mbox.SignalList):
+            sig_ret = []
+            for sig in self.signals._list:
+                sig_ret.append(sig._no_desc())
+        else:
+            sig_ret = self.signals.show()
+        return ret + "\n" + helpers.indentify(sig_ret)
 
     def _check_existence(self):
         # will fail if plugin was deleted
         _ = self._core.plugin_id
 
-    @property
-    def signals(self):
-        return self._signals
-
     @property
     def table(self):
         _table = self.table_int16_array
@@ -154,33 +209,11 @@ class _Plugin:
 _plugin_subclasses = {}
 
 
-def _make_plugin(channel, plugin_id, *args, **kwargs):
-    if plugin_id in _plugin_subclasses:
-        return _plugin_subclasses[plugin_id](channel, None, plugin_id, *args, **kwargs)
-    else:
-        init_var = 0
-        _init_var = kwargs.get("init_var", None)
-        if _init_var is None:
-            if len(args) == 1:
-                init_var = int(args[0])
-        else:
-            init_var = _init_var
-        return _Plugin(channel, None, plugin_id, init_var)
-
-
-def _get_plugin(core):
-    plugin_id = core.plugin_id
-    if plugin_id in _plugin_subclasses:
-        return _plugin_subclasses[plugin_id](None, core, plugin_id, core.init_var)
-    else:
-        return _Plugin(None, core, plugin_id, core.init_var)
-
 
 def _plugin_set_subclass(plugin_id):
     def decorator(cls):
         _plugin_subclasses[plugin_id] = cls
         return cls
-
     return decorator
 
 
@@ -195,19 +228,16 @@ class _Sampler(_Plugin):
     _STATUS = 10
     _BUFFER = 11
 
-    def __init__(self, channel, core, plugin_id, init_var=1000):
+    def __init__(self, channel_core, container, plugin_id, init_var=1000, name = None):
         self._filename = ""
-        if core is not None:
-            super().__init__(channel, core, None)
-            self._memory_len = self.init_var
-        elif type(init_var) is str:
+        if type(init_var) is str:
             with wave.open(init_var, "r") as f:
                 self._memory_len = f.getnframes()
-                super().__init__(channel, None, plugin_id, init_var=self._memory_len)
+                super().__init__(channel_core, container, plugin_id, init_var=self._memory_len, name = name)
                 self.load(init_var)
         else:
             self._memory_len = int(48 * init_var)
-            super().__init__(channel, None, plugin_id, init_var=self._memory_len)
+            super().__init__(channel_core, container, plugin_id, init_var=self._memory_len, name = name)
 
     def __repr__(self):
         ret = super().__repr__()
@@ -266,7 +296,7 @@ class _Sampler(_Plugin):
                 assert f.getnchannels() in (1, 2)
                 assert f.getcomptype() == "NONE"
             except AssertionError:
-                raise Bl00mboxError("incompatible file format")
+                raise TypeError("incompatible file format")
 
             frames = f.getnframes()
             if frames > self._memory_len:
@@ -498,14 +528,11 @@ class _Distortion(_Plugin):
 
 @_plugin_set_subclass(172)
 class _PolySqueeze(_Plugin):
-    def __init__(self, channel, core, plugin_id, num_outputs=3, num_inputs=10):
-        if core is None:
-            outs = max(min(num_outputs, 16), 1)
-            ins = max(min(num_inputs, 32), num_outputs)
-            init_var = outs + (ins * 256)
-            super().__init__(channel, core, plugin_id, init_var=init_var)
-        else:
-            super().__init__(channel, core, None)
+    def __init__(self, channel_core, container, plugin_id, num_outputs=3, num_inputs=10, name = None):
+        outs = max(min(num_outputs, 16), 1)
+        ins = max(min(num_inputs, 32), num_outputs)
+        init_var = outs + (ins * 256)
+        super().__init__(channel_core, container, plugin_id, init_var=init_var, name = name)
 
 
 @_plugin_set_subclass(0)
@@ -632,20 +659,15 @@ class _Osc(_Plugin):
 
 @_plugin_set_subclass(56709)
 class _Sequencer(_Plugin):
-    def __init__(self, channel, core, plugin_id, num_tracks=4, num_steps=16):
-        if core is None:
-            self.num_steps = num_steps % 256
-            self.num_tracks = num_tracks % 256
-            init_var = (self.num_steps * 256) + (self.num_tracks)
+    def __init__(self, channel_core, container, plugin_id, num_tracks=4, num_steps=16, name = None):
+        self.num_steps = num_steps % 256
+        self.num_tracks = num_tracks % 256
+        init_var = (self.num_steps * 256) + (self.num_tracks)
 
-            super().__init__(channel, core, plugin_id, init_var=init_var)
+        super().__init__(channel_core, container, plugin_id, init_var=init_var, name = name)
 
-            tracktable = [-32767] + ([0] * self.num_steps)
-            self.table = tracktable * self.num_tracks
-        else:
-            super().__init__(channel, core, None)
-            self.num_tracks = self.init_var % 256
-            self.num_steps = (self.init_var // 256) % 256
+        tracktable = [-32767] + ([0] * self.num_steps)
+        self.table = tracktable * self.num_tracks
 
     def __repr__(self):
         ret = super().__repr__()
diff --git a/components/bl00mbox/micropython/bl00mbox/_user.py b/components/bl00mbox/micropython/bl00mbox/_user.py
index 2f99bca564c0f347ba1503fcd082c24b23ba1d8c..1d305bdd195859ce42a931eaeeab31a3e4ea9c28 100644
--- a/components/bl00mbox/micropython/bl00mbox/_user.py
+++ b/components/bl00mbox/micropython/bl00mbox/_user.py
@@ -8,45 +8,72 @@ import math
 import uctypes
 import bl00mbox
 from bl00mbox import _helpers as helpers
+from bl00mbox import _plugins
 from bl00mbox._patches import _Patch as Patch
 
+from collections import OrderedDict
 
 class Bl00mboxError(Exception):
     pass
 
+class SignalTuple(tuple):
+    def __repr__(self):
+        return self.show()
 
-def _make_signal(plugin, signal_num=0, core=None):
-    if core is None:
-        core = sys_bl00mbox.SignalCore(plugin._core, signal_num)
-    is_input = bool(core.hints & sys_bl00mbox.SIGNAL_HINT_INPUT)
-    is_output = bool(core.hints & sys_bl00mbox.SIGNAL_HINT_OUTPUT)
-    if is_output == is_input:
-        raise Bl00mboxError("signal must be either input or output")
-    if is_input:
-        signal = SignalInput(plugin, core)
-    else:
-        signal = SignalOutput(plugin, core)
-    return signal
+    def show(self, *, name = None):
+        lines = []
+        for sig in self:
+            lines.append(sig.show(name = ""))
+        ret = helpers.indentify(lines)
+        title = "signals" if name is None else name
+        return f"[{title}]\n{ret}"
 
+class SignalContainer:
+    def __init__(self):
+        self._ordereddict = OrderedDict()
 
-class ChannelMixer:
-    def __init__(self, core):
-        self._core = core
+    def __setattr__(self, key, value):
+        if hasattr(self, "_ordereddict"):
+            self._ordereddict[key] = value
+        super().__setattr__(key, value)
 
-    def __repr__(self):
-        ret = f"[channel mixer] ({len(self.connections)} connections)"
-        for con in self.connections:
-            ret += f"\n  {con.name} in {con._plugin._repr_no_signals()}"
+    @staticmethod
+    def _from_plugin(plugin):
+        ret = SignalContainer()
+        mpxd = OrderedDict()
+        for signal_num in range(plugin._core.num_signals):
+            signal = Signal._from_plugin(plugin, signal_num)
+            name = signal.name.split(" ")[0]
+            if signal._mpx == -1:
+                setattr(ret, name, signal)
+            else:
+                mpx_list = mpxd.get(name)
+                if mpx_list is None:
+                    mpx_list = list()
+                    mpxd[name] = mpx_list
+                extra_len = 1 + signal._mpx - len(mpx_list)
+                if extra_len > 0:
+                    mpx_list += [None] * extra_len
+                mpx_list[signal._mpx] = signal
+        for name in mpxd:
+            sigs = SignalTuple(mpxd[name])
+            setattr(ret, name, sigs)
         return ret
 
-    @property
-    def connections(self):
-        cons = []
-        signal_cores = self._core.get_connected_mx()
-        for core in signal_cores:
-            plugin = bl00mbox._plugins._get_plugin(core.plugin_core)
-            cons.append(_make_signal(plugin, core=core))
-        return cons
+    def __repr__(self):
+        return self.show()
+
+    def show(self, *, name = None):
+        lines = []
+        for key in self._ordereddict:
+            lines += getattr(self, key).show(name = key).split("\n")
+        for key in self.__dict__:
+            if key.startswith("_") or key in self._ordereddict:
+                continue
+            lines += getattr(self, key).show(name = key).split("\n")
+        ret = helpers.indentify(lines)
+        title = "signals" if name is None else name
+        return f"[{title}]\n{ret}"
 
 
 class ValueSwitch:
@@ -72,14 +99,39 @@ class ValueSwitch:
                 return k
 
 
-class Signal:
+class Signal(helpers._CoreKeyFallthrough):
+    _core_keys = (
+        "name",
+        "description",
+        "unit",
+        "plugin",
+    )
+
+    @staticmethod
+    def _from_plugin(plugin, signal_num):
+        core = plugin._core.signal_cores[signal_num]
+        is_input = bool(core.hints & sys_bl00mbox.SIGNAL_HINT_INPUT)
+        is_output = bool(core.hints & sys_bl00mbox.SIGNAL_HINT_OUTPUT)
+        if is_output == is_input:
+            raise Bl00mboxError("signal must be either input or output")
+        if is_input:
+            signal = SignalInput(plugin, core)
+        else:
+            signal = SignalOutput(plugin, core)
+        return signal
+
     def __init__(self, plugin, core):
+        if core.outer is not None:
+            raise Bl00mboxError("double signal initialization error")
         self._core = core
-        self._plugin = plugin
+        core.outer = self
         self._mpx = core.mpx
         self._unit = core.unit
         self._description = core.description
         constants = {}
+        self._dev = plugin.channel.dev
+        if not self._dev:
+            self._plugin = plugin
 
         another_round = True
         while another_round:
@@ -117,34 +169,40 @@ class Signal:
             hint_list.append("deprecated")
         self._hints = "/".join(hint_list)
 
+    @property
+    def hints(self):
+        return self._hints
+
     @property
     def connections(self):
-        cons = []
-        signal_cores = self._core.get_connected()
-        for core in signal_cores:
-            plugin = bl00mbox._plugins._get_plugin(core.plugin_core)
-            cons.append(_make_signal(plugin, core=core))
+        cons = self._core.get_connected()
         if self._core.connected_mx:
             cons.append(ChannelMixer(None))
         return cons
 
+    def show(self, *, name = None, description = False):
+        ret = self._no_desc()
+        if description and len(self._description):
+            ret += "\n  " + "\n  ".join(self._description.split("\n"))
+        return ret
+
     def __repr__(self):
         ret = self._no_desc()
         if len(self._description):
             ret += "\n  " + "\n  ".join(self._description.split("\n"))
         return ret
 
-    def _no_desc(self):
-        self._plugin._check_existence()
+    def _no_desc(self, name = None):
+        self.plugin._check_existence()
 
-        ret = self.name
+        ret = self.name if name is None else name
         if self._mpx != -1:
-            ret += "[" + str(self._mpx) + "]"
+            ret += f"[{self._mpx}]"
 
         if len(self.unit):
-            ret += " [" + self.unit + "]"
+            ret += f" [{self.unit}]"
 
-        ret += " [" + self.hints + "]: "
+        ret += f" [{self.hints}]: "
 
         direction = " <?> "
         val = self.value
@@ -174,7 +232,7 @@ class Signal:
         conret = []
         for con in self.connections:
             if isinstance(con, Signal):
-                conret += [f"{direction}{con.name} in {con._plugin._repr_no_signals()}"]
+                conret += [f"{direction}{con.name} in {con.plugin.show(oneliner = True)}"]
             if isinstance(con, ChannelMixer):
                 conret += [f"{direction}[channel mixer]"]
         if len(conret) > 1:
@@ -184,22 +242,6 @@ class Signal:
             ret += conret[0]
         return ret
 
-    @property
-    def name(self):
-        return self._core.name
-
-    @property
-    def description(self):
-        return self._core.description
-
-    @property
-    def unit(self):
-        return self._core.unit
-
-    @property
-    def hints(self):
-        return self._hints
-
     @property
     def value(self):
         return self._core.value
@@ -210,13 +252,10 @@ class Signal:
 
     @tone.setter
     def tone(self, val):
-        if isinstance(self, SignalInput):
-            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)
-        else:
-            raise AttributeError("can't set output signal")
+        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):
@@ -225,11 +264,8 @@ class Signal:
 
     @freq.setter
     def freq(self, val):
-        if isinstance(self, SignalInput):
-            tone = 12 * math.log(val / 440, 2)
-            self.value = (32767 - 2400 * 6) + 200 * tone
-        else:
-            raise AttributeError("can't set output signal")
+        tone = 12 * math.log(val / 440, 2)
+        self.value = (32767 - 2400 * 6) + 200 * tone
 
     @property
     def dB(self):
@@ -239,10 +275,7 @@ class Signal:
 
     @dB.setter
     def dB(self, val):
-        if isinstance(self, SignalInput):
-            self.value = int(4096 * (10 ** (val / 20)))
-        else:
-            raise AttributeError("can't set output signal")
+        self.value = int(4096 * (10 ** (val / 20)))
 
     @property
     def mult(self):
@@ -250,10 +283,7 @@ class Signal:
 
     @mult.setter
     def mult(self, val):
-        if isinstance(self, SignalInput):
-            self.value = int(4096 * val)
-        else:
-            raise AttributeError("can't set output signal")
+        self.value = int(4096 * val)
 
     def start(self, velocity=32767):
         if self.value > 0:
@@ -269,11 +299,16 @@ class SignalOutput(Signal):
     @Signal.value.setter
     def value(self, val):
         if val is None:
+            conns = self.connections
             self._core.disconnect()
+            if conns and self._dev:
+                return conns
         elif isinstance(val, SignalInput):
             val << self
         elif isinstance(val, ChannelMixer):
             self._core.connect_mx()
+        else:
+            raise AttributeError("can't set output signal")
         # fails silently bc of backwards compatibility :/
         # we'll deprecate this setter entirely someday, use rshift instead
 
@@ -293,13 +328,17 @@ class SignalOutput(Signal):
 class SignalInput(Signal):
     @Signal.value.setter
     def value(self, val):
+        conns = self.connections
         if val is None:
             self._core.disconnect()
         elif isinstance(val, SignalOutput):
             self._core.connect(val._core)
         elif (type(val) == int) or (type(val) == float):
             self._core.value = int(val)
-        # fails silently bc of backwards compatibility :/
+        elif self._dev:
+            raise TypeError(f"can't connect SignalInput to {type(other)}")
+        if conns and self._dev:
+            return conns
 
     def __lshift__(self, other):
         if not (
@@ -315,87 +354,6 @@ class SignalInput(Signal):
         raise TypeError("input signals can't send data to other signals")
 
 
-class SignalMpxList:
-    def __init__(self):
-        self._list = []
-        self._setattr_allowed = False
-
-    def __len__(self):
-        return len(self._list)
-
-    def __getitem__(self, index):
-        return self._list[index]
-
-    def __setitem__(self, index, value):
-        try:
-            current_value = self._list[index]
-        except:
-            raise AttributeError("index does not exist")
-        if isinstance(current_value, Signal):
-            current_value.value = value
-        else:
-            raise AttributeError("signal does not exist")
-
-    def __iter__(self):
-        self._iter_index = 0
-        return self
-
-    def __next__(self):
-        self._iter_index += 1
-        if self._iter_index >= len(self._list):
-            raise StopIteration
-        else:
-            return self._list[self._iter_index]
-
-    def add_new_signal(self, signal, index):
-        self._setattr_allowed = True
-        index = int(index)
-        if len(self._list) <= index:
-            self._list += [None] * (1 + index - len(self._list))
-        self._list[index] = signal
-        self._setattr_allowed = False
-
-    def __setattr__(self, key, value):
-        """
-        current_value = getattr(self, key, None)
-        if isinstance(current_value, Signal):
-            current_value.value = value
-            return
-        """
-        if key == "_setattr_allowed" or getattr(self, "_setattr_allowed", True):
-            super().__setattr__(key, value)
-        else:
-            raise AttributeError("signal does not exist")
-
-
-class SignalList:
-    def __init__(self, plugin):
-        self._list = []
-        for signal_num in range(plugin._core.num_signals):
-            signal = _make_signal(plugin, signal_num=signal_num)
-            self._list.append(signal)
-            name = signal.name.split(" ")[0]
-            if signal._mpx == -1:
-                setattr(self, name, signal)
-            else:
-                # <LEGACY SUPPORT>
-                setattr(self, name + str(signal._mpx), signal)
-                # </LEGACY SUPPORT>
-                current_list = getattr(self, name, None)
-                if not isinstance(current_list, SignalMpxList):
-                    setattr(self, name, SignalMpxList())
-                getattr(self, name, None).add_new_signal(signal, signal._mpx)
-        self._setattr_allowed = False
-
-    def __setattr__(self, key, value):
-        current_value = getattr(self, key, None)
-        if isinstance(current_value, Signal):
-            current_value.value = value
-            return
-        elif getattr(self, "_setattr_allowed", True):
-            super().__setattr__(key, value)
-        else:
-            raise AttributeError("signal does not exist")
 
 
 _channel_init_callback = None
@@ -406,8 +364,7 @@ def set_channel_init_callback(callback):
     global _channel_init_callback
     _channel_init_callback = callback
 
-
-class Channel:
+class Channel(_plugins.PluginSpawner):
     _core_keys = (
         "name",
         "num_plugins",
@@ -418,93 +375,179 @@ class Channel:
         "callback",
         "compute_rms",
         "delete",
+        "dev",
     )
 
-    # we are passing through a lot of methods/properties to _core, so why not subclass?
-    # well, the core needs to run a destructor in the engine when it is garbage collected,
-    # and we're not so sure if it still does that when subclassed. we never actually tested
-    # it, but we made bad experiences. also this allows us to hide the sys_ methods from users.
-    #
-    # maybe the proper solution would be to move this entire thing to the backend, but if we
-    # ever wanna play around with fancy things like (de)serialization this might lead to a bigger
-    # code size. it's fine for now.
+    class Render:
+        def __init__(self, core):
+            self._core = core
+            self.rms = self.RMS(core)
+
+        def __repr__(self):
+            ret = self.rms.__repr__()
+            #ret += self.cpu.__repr__()
+            ret = "[render]\n" + helpers.indentify(ret)
+            return ret
+
+        @property
+        def foreground(self):
+            return self._core.foreground
+
+        @foreground.setter
+        def foreground(self, val):
+            self._core.foreground = bool(val)
+
+        @property
+        def background(self):
+            return self._core.background_mute_override
+
+        @background.setter
+        def background(self, val):
+            self._core.background_mute_override = bool(val)
+
+        @property
+        def rms_dB(self):
+            if not self._core.compute_rms:
+                return None
+            ret = self._core.mean_square
+            if ret == 0:
+                return -math.inf
+            mult = abs(self._core.volume) / 32768
+            if mult == 0:
+                return -math.inf
+            ret *= mult * mult
+            return 10 * math.log(ret, 10) + _rms_base
+
+        @rms_dB.setter
+        def rms_dB(self, val):
+            self._core.compute_rms = bool(val)
+
+        @property
+        def load(self):
+            if not self._core.compute_load:
+                return None
+            return self._core.load
+
+        @load.setter
+        def load(self, val):
+            self._core.compute_load = bool(val)
+
+        class RMS():
+            def __init__(self, core):
+                self._core = core
+
+            @property
+            def collect(self):
+                return self._core.compute_rms
+
+
+            @property
+            def rms_dB(self):
+                if not self._core.compute_rms:
+                    return None
+                ret = self._core.mean_square
+                if ret == 0:
+                    return -math.inf
+                mult = abs(self._core.volume) / 32768
+                if mult == 0:
+                    return -math.inf
+                ret *= mult * mult
+                return 10 * math.log(ret, 10) + _rms_base
 
-    def __setattr__(self, key, value):
-        if key in self._core_keys:
-            setattr(self._core, key, value)
-        else:
-            super().__setattr__(key, value)
+            @property
+            def dB(self):
+                if not self._core.compute_rms:
+                    return None
+                ret = self._core.mean_square
+                if ret == 0:
+                    return -math.inf
+                mult = abs(self._core.volume) / 32768
+                if mult == 0:
+                    return -math.inf
+                ret *= mult * mult
+                return 10 * math.log(ret, 10) + _rms_base
 
-    def __getattr__(self, key):
-        # __getattr__ is only fallback, but since we don't allow for keys in _core_keys
-        # to be stored within Channel this should suffice
-        if key in self._core_keys:
-            return getattr(self._core, key)
-        else:
-            raise AttributeError(f"'{type(self)}' object has no attribute {key}")
+            def __repr__(self):
+                if self.collect:
+                    return f"rms volume: {self.dB}dB"
+                else:
+                    return "rms volume: not collected"
 
-    def __init__(self, name="repl"):
+    class _ChannelPlugin(_plugins._Plugin):
+        def __init__(self):
+            self._core = None
+
+        def _attach_core(self, core):
+            if self._core:
+                raise Bl00mboxError("core is already set")
+            self._core = core
+            if self.channel.dev:
+                self.signals = bl00mbox.SignalContainer._from_plugin(self)
+            else:
+                self.signals = bl00mbox.SignalList(self)
+
+    @property
+    def render(self):
+        return self._render
+
+    def __init__(self, name = "repl", dev = 0):
         # never ever hand out the deep core to somebody else to make sure channel gets gc'd right
-        self._deep_core = sys_bl00mbox.ChannelCore(name)
+        self._channel_plugin = self._ChannelPlugin()
+        self._deep_core = sys_bl00mbox.ChannelCore(name, dev, self, self._channel_plugin)
         self._core = self._deep_core.get_weak()
+        self._channel_plugin._attach_core(self._core.channel_plugin)
         # also don't hand out self, use this one instead
         self._weak_channel = WeakChannel(self._core)
+
         if _channel_init_callback is not None:
             _channel_init_callback(self)
-        self._channel_plugin = bl00mbox._plugins._get_plugin(self._core.channel_plugin)
+        self._render = self.Render(self._core)
+
+    @property
+    def _container(self):
+        return None
 
+    @property
+    def _spawn_core(self):
+        return self._core
+    
     @property
     def signals(self):
-        return self._channel_plugin._signals
+        return self._channel_plugin.signals
 
     def __repr__(self):
-        ret = f'[channel "{self.name}"]'
-        if self.foreground:
-            ret += " (foreground)"
-        if self.background_mute_override:
-            ret += " (background mute override)"
-        ret += "\n  gain: " + str(self.gain_dB) + "dB"
-        b = self.num_plugins
-        ret += "\n  plugins: " + str(b)
-        ret += "\n  " + "\n  ".join(repr(self.mixer).split("\n"))
+        return self.show()
+
+    def show(self, *,patches = True, **kwargs):
+        ret = self.render.__repr__()
+        ret += "\n[patches]:"
+        if patches:
+            patches = [p.show(oneliner = True, patches = True) for p in self._core.get_patches()]
+            ret += "\n" + helpers.indentify(patches)
+            
+        title = f'channel "{self.name}"'
+        ret = f"[{title}]\n{helpers.indentify(ret)}"
         return ret
 
     @property
     def free(self):
-        # legacy oof
-        return self._core is None
+        # DEPRECATED
+        try:
+            self.foreground
+            return True
+        except:
+            return False
 
     @free.setter
     def free(self, val):
-        # bigger legacy oof
+        # DEPRECATED
         if val:
-            self.clear()
-            self._core = None
-
-    def _new_plugin(self, thing, *args, **kwargs):
-        if isinstance(thing, bl00mbox._plugins._PluginDescriptor):
-            return bl00mbox._plugins._make_plugin(
-                self._weak_channel, thing.plugin_id, *args, **kwargs
-            )
-        else:
-            raise TypeError("not a plugin")
-
-    def new(self, thing, *args, **kwargs):
-        self.free = False
-        if type(thing) == type:
-            if issubclass(thing, bl00mbox.patches._Patch):
-                return thing(self._weak_channel, *args, **kwargs)
-        elif isinstance(thing, bl00mbox._plugins._PluginDescriptor) or (
-            type(thing) == int
-        ):
-            return self._new_plugin(thing, *args, **kwargs)
-        else:
-            raise TypeError("invalid plugin/patch")
+            self.delete()
 
     @property
     def plugins(self):
-        for core in self._core.get_plugins():
-            yield bl00mbox._plugins._get_plugin(core)
+        for plugin in self._core.get_plugins():
+            yield plugin
 
     @property
     def gain_dB(self):
@@ -525,31 +568,19 @@ class Channel:
 
     @property
     def rms_dB(self):
-        if self.compute_rms:
-            ret = self._core.mean_square
-            if ret == 0:
-                return -math.inf
-            else:
-                # volume is applied together with sys_volume
-                # after rms is calculated. we do want the output
-                # before sys_volume but after regular volume,
-                # so we're compensating here
-                mult = abs(self._core.volume) / 32768
-                if mult == 0:
-                    return -math.inf
-                # squaring because we're getting root later
-                ret *= mult * mult
-                return 10 * math.log(ret, 10) + _rms_base
-        else:
-            return None
+        return self.render.rms.dB
 
     @property
     def mixer(self):
+        if self.dev:
+            return None
         return ChannelMixer(self._core)
 
     @mixer.setter
     def mixer(self, val):
-        if isinstance(val, SignalOutput):
+        if self.dev:
+            raise Bl00mboxError("mixer is deprecated, use signals.line_out instead")
+        elif isinstance(val, SignalOutput):
             val.value = ChannelMixer(self._core)
         elif val is not None:
             # val is None: backwards compatibility, not allowed to throw exception ;w;
@@ -559,16 +590,18 @@ class Channel:
 class WeakChannel(Channel):
     def __init__(self, core):
         self._core = core.get_weak()
-        self._channel_plugin = bl00mbox._plugins._get_plugin(self._core.channel_plugin)
+        self._channel_plugin = self._core.channel_plugin.outer
         self._weak_channel = self
+        self._render = self.Render(self._core)
 
 
 class SysChannel(Channel):
     def __init__(self, core):
         self._deep_core = core
         self._core = core.get_weak()
-        self._channel_plugin = bl00mbox._plugins._get_plugin(self._core.channel_plugin)
+        self._channel_plugin = self._core.channel_plugin.outer
         self._weak_channel = WeakChannel(self._core)
+        self._render = self.Render(self._core)
 
     @property
     def sys_gain_dB(self):
@@ -658,3 +691,110 @@ class Sys:
         for core in sys_bl00mbox.collect_channels(True):
             if core.callback and core.sys_gain:
                 yield core.callback
+
+#
+#
+# END OF NONDEPRECATED CODE
+#
+# ONLY LEGACY GARBAGE DOWN HERE
+#
+#
+
+class ChannelMixer:
+    # DEPRECATED
+    def __init__(self, core):
+        self._core = core
+
+    def __repr__(self):
+        ret = f"[channel mixer] ({len(self.connections)} connections)"
+        for con in self.connections:
+            ret += f"\n  {con.name} in {con.plugin.show(oneliner = True)}"
+        return ret
+
+    @property
+    def connections(self):
+        return self._core.get_connected_mx()
+
+class SignalMpxList:
+    # DEPRECATED
+    def __init__(self):
+        self._list = []
+        self._setattr_allowed = False
+
+    def __len__(self):
+        return len(self._list)
+
+    def __getitem__(self, index):
+        return self._list[index]
+
+    def __setitem__(self, index, value):
+        try:
+            current_value = self._list[index]
+        except:
+            raise AttributeError("index does not exist")
+        if isinstance(current_value, Signal):
+            current_value.value = value
+        else:
+            raise AttributeError("signal does not exist")
+
+    def __iter__(self):
+        self._iter_index = 0
+        return self
+
+    def __next__(self):
+        self._iter_index += 1
+        if self._iter_index >= len(self._list):
+            raise StopIteration
+        else:
+            return self._list[self._iter_index]
+
+    def add_new_signal(self, signal, index):
+        self._setattr_allowed = True
+        index = int(index)
+        if len(self._list) <= index:
+            self._list += [None] * (1 + index - len(self._list))
+        self._list[index] = signal
+        self._setattr_allowed = False
+
+    def __setattr__(self, key, value):
+        """
+        current_value = getattr(self, key, None)
+        if isinstance(current_value, Signal):
+            current_value.value = value
+            return
+        """
+        if key == "_setattr_allowed" or getattr(self, "_setattr_allowed", True):
+            super().__setattr__(key, value)
+        else:
+            raise AttributeError("signal does not exist")
+
+
+class SignalList:
+    # DEPRECATED
+    def __init__(self, plugin):
+        self._list = []
+        for signal_num in range(plugin._core.num_signals):
+            signal = Signal._from_plugin(plugin, signal_num)
+            self._list.append(signal)
+            name = signal.name.split(" ")[0]
+            if signal._mpx == -1:
+                setattr(self, name, signal)
+            else:
+                # <LEGACY SUPPORT>
+                setattr(self, name + str(signal._mpx), signal)
+                # </LEGACY SUPPORT>
+                current_list = getattr(self, name, None)
+                if not isinstance(current_list, SignalMpxList):
+                    setattr(self, name, SignalMpxList())
+                getattr(self, name, None).add_new_signal(signal, signal._mpx)
+        self._setattr_allowed = False
+
+    def __setattr__(self, key, value):
+        current_value = getattr(self, key, None)
+        if isinstance(current_value, Signal):
+            current_value.value = value
+            return
+        elif getattr(self, "_setattr_allowed", True):
+            super().__setattr__(key, value)
+        else:
+            raise AttributeError("signal does not exist")
diff --git a/components/bl00mbox/micropython/mp_sys_bl00mbox.c b/components/bl00mbox/micropython/mp_sys_bl00mbox.c
index 17bdc2981736c7087529676f15800bcdaded981d..76b3e75ae1d3c3cadaf066536b1cb0e13ab3a794 100644
--- a/components/bl00mbox/micropython/mp_sys_bl00mbox.c
+++ b/components/bl00mbox/micropython/mp_sys_bl00mbox.c
@@ -19,19 +19,19 @@ MP_DEFINE_EXCEPTION(ReferenceError, Exception)
 
 typedef struct _channel_core_obj_t {
     mp_obj_base_t base;
+    mp_obj_t outer;
+    
     // will be NULLed on original if channel has been manually deleted
     // both for weak and regular
-    bl00mbox_channel_t *chan;
-
+    bl00mbox_channel_t * chan;
     // pointer to object that weakly references original channel object
     // there is no more than one weak channel reference obj per channel
     mp_obj_t weak;
+    // list of all sources connect to the mixer to avoid gc
+    mp_obj_t sources_mx;
 
-    // things we don't want garbage collected:
-    // - channel plugin
     mp_obj_t channel_plugin;
-    // - list of all sources connect to the mixer
-    mp_obj_t sources_mx;
+    mp_obj_t name;
 
 #ifdef BL00MBOX_CALLBACKS_FUNSAFE
     // the channel may be become reachable after having been unreachable.
@@ -53,11 +53,17 @@ typedef struct _plugin_core_obj_t {
     // important note: the plugin may have been garbage collected along with
     // its channel or it might have been deleted, in either case this pointer
     // is set to NULL, so you need to check before using it. if you do anything
-    // that might trigger a gc (like creating objects) you need to check this
-    // pointer for NULL again.
-    bl00mbox_plugin_t *plugin;
-    // list of all source plugins to avoid gc
+    // that might trigger a gc (like creating objects) you need to check this pointer
+    // for NULL again.
+    bl00mbox_plugin_t * plugin;
+    mp_obj_t outer;
+    // list of all signals
+    mp_obj_t signals;
+    mp_obj_t name;
+
+    // refs purely to avoid gc
     mp_obj_t sources;
+    mp_obj_t container;
 } plugin_core_obj_t;
 
 STATIC const mp_obj_type_t plugin_core_type;
@@ -65,101 +71,164 @@ STATIC const mp_obj_type_t plugin_core_type;
 typedef struct _signal_core_obj_t {
     mp_obj_base_t base;
     mp_obj_t plugin_core;
-    // same rules for garbage collection as for plugin_core_obj_t apply here
-    // too, call plugin_core_verify() on plugin_core after each potential
-    // garbage collection keeping a direct reference to keep the plugin alive as
-    // long as there's a signal in reach (unless the channel gets gc'd of
-    // course)
+    mp_obj_t outer;
+    // same rules for garbage collection as for plugin_core_obj_t apply here too,
+    // call plugin_core_verify() on plugin_core after each potential garbage collection
+    // keeping a direct reference to keep the plugin alive as long as there's
+    // a signal in reach (unless the channel gets gc'd of course)
     bl00mbox_signal_t signal;
 } signal_core_obj_t;
 
 STATIC const mp_obj_type_t signal_core_type;
 
-STATIC mp_obj_t signal_core_make_new(const mp_obj_type_t *type, size_t n_args,
-                                     size_t n_kw, const mp_obj_t *args);
+typedef struct _patch_core_obj_t {
+    mp_obj_base_t base;
+    mp_obj_t outer;
+    mp_obj_t weak_channel;
+    bl00mbox_patch_t * patch;
+    mp_obj_t name;
+    mp_obj_t patch_name;
+    mp_obj_t container;
+} patch_core_obj_t;
+
+STATIC const mp_obj_type_t patch_core_type;
+
+// ========================
+//         HELPERS
+// ========================
 
-static inline void channel_core_verify(channel_core_obj_t *channel_core) {
-    if (!channel_core->chan)
-        mp_raise_msg(&mp_type_ReferenceError,
-                     MP_ERROR_TEXT("channel was deleted"));
+STATIC mp_obj_t _signal_core_make_new(plugin_core_obj_t * plugin_core, int index);
+static mp_obj_t create_channel_plugin(channel_core_obj_t * chan_core, mp_obj_t outer);
+
+static inline void channel_core_verify(channel_core_obj_t * channel_core){
+    if(!channel_core->chan) mp_raise_msg(&mp_type_ReferenceError, MP_ERROR_TEXT("channel was deleted"));
+}
+
+static inline void plugin_core_verify(plugin_core_obj_t * plugin_core){
+    // note: PluginCore.channel assumes this also verifies channel
+    if(!plugin_core->plugin) mp_raise_msg(&mp_type_ReferenceError, MP_ERROR_TEXT("plugin was deleted"));
+}
+
+static inline void patch_core_verify(patch_core_obj_t * patch_core){
+    // note: PatchCore.channel assumes this also verifies channel
+    if(!patch_core->patch) mp_raise_msg(&mp_type_ReferenceError, MP_ERROR_TEXT("patch was deleted"));
+}
+
+static inline channel_core_obj_t * get_channel_core(mp_obj_t channel_core){
+    channel_core_obj_t * self = MP_OBJ_TO_PTR(channel_core);
+    if(self->base.type != &channel_core_type){
+        mp_raise_TypeError(MP_ERROR_TEXT("argument must be ChannelCore"));
+    }
+    channel_core_verify(self);
+    return self;
 }
 
-static inline void plugin_core_verify(plugin_core_obj_t *plugin_core) {
-    if (!plugin_core->plugin)
-        mp_raise_msg(&mp_type_ReferenceError,
-                     MP_ERROR_TEXT("plugin was deleted"));
+static inline bl00mbox_patch_t * get_patch(mp_obj_t container){
+    if (container == mp_const_none) return NULL;
+    patch_core_obj_t * self = MP_OBJ_TO_PTR(container);
+    if(self->base.type == &channel_core_type) return NULL;
+    if(self->base.type == &patch_core_type){
+        patch_core_verify(self);
+        return self->patch;
+    }
+    mp_raise_TypeError(MP_ERROR_TEXT("argument must be PatchCore, ChannelCore or None"));
 }
 
-static void bl00mbox_error_unwrap(bl00mbox_error_t error) {
-    switch (error) {
+static void bl00mbox_error_unwrap(bl00mbox_error_t error){
+    switch(error){
         case BL00MBOX_ERROR_OOM:
             mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("out of memory"));
         case BL00MBOX_ERROR_INVALID_CONNECTION:
             mp_raise_TypeError(MP_ERROR_TEXT("can't connect that"));
         case BL00MBOX_ERROR_INVALID_IDENTIFIER:
             mp_raise_TypeError(MP_ERROR_TEXT("bad identifier"));
-        default:;
+        default:
+            ;
     }
 }
 
-void bl00mbox_disconnect_rx_callback(void *rx, uint16_t signal_index) {
-    plugin_core_obj_t *self = rx;
-    mp_obj_list_t *list = MP_OBJ_TO_PTR(self->sources);
-    if (signal_index < list->len) {
+void bl00mbox_disconnect_rx_callback(void * rx, uint16_t signal_index){
+    plugin_core_obj_t * self = rx;
+    mp_obj_list_t * list = MP_OBJ_TO_PTR(self->sources);
+    if(signal_index < list->len){
         list->items[signal_index] = mp_const_none;
     } else {
         bl00mbox_log_error("plugin signal list too short");
     }
 }
 
-void bl00mbox_connect_rx_callback(void *rx, void *tx, uint16_t signal_index) {
-    plugin_core_obj_t *self = rx;
-    mp_obj_list_t *list = MP_OBJ_TO_PTR(self->sources);
-    if (signal_index < list->len) {
+void bl00mbox_connect_rx_callback(void * rx, void * tx, uint16_t signal_index){
+    plugin_core_obj_t * self = rx;
+    mp_obj_list_t * list = MP_OBJ_TO_PTR(self->sources);
+    if(signal_index < list->len){
         list->items[signal_index] = MP_OBJ_FROM_PTR(tx);
     } else {
         bl00mbox_log_error("plugin signal list too short");
     }
 }
 
-void bl00mbox_disconnect_mx_callback(void *chan, void *tx) {
-    channel_core_obj_t *self = chan;
+void bl00mbox_disconnect_mx_callback(void * chan, void * tx){
+    channel_core_obj_t * self = chan;
     // don't have to check for duplicates, bl00mbox_user does that for us
     mp_obj_list_remove(self->sources_mx, MP_OBJ_FROM_PTR(tx));
 }
 
-void bl00mbox_connect_mx_callback(void *chan, void *tx) {
-    channel_core_obj_t *self = chan;
+void bl00mbox_connect_mx_callback(void * chan, void * tx){
+    channel_core_obj_t * self = chan;
     // don't have to check for duplicates, bl00mbox_user does that for us
     mp_obj_list_append(self->sources_mx, MP_OBJ_FROM_PTR(tx));
 }
 
-static char *strdup_raise(char *str) {
-    char *ret = strdup(str);
-    if (!ret) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
+static char * strdup_raise(char * str){
+    char * ret = strdup(str);
+    if(!ret) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
     return ret;
 }
 
-static mp_obj_t get_connections_from_array(bl00mbox_array_t *array) {
-    if (!array) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
-    if (array->len) {
+static mp_obj_t get_patches_from_container(bl00mbox_patch_container_t * container){
+    bl00mbox_array_t * array = bl00mbox_parents_from_patch_container(container);
+    if(!array) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
+    if(array->len){
+        int len = array->len;
+        mp_obj_t elems[len];
+        bool fail = false;
+        for(int i = 0; i < len; i++){
+            plugin_core_obj_t * core = MP_OBJ_TO_PTR(array->elems[i]);
+            if(core->base.type == &plugin_core_type){
+                elems[i] = core->outer;
+            } else if(core->base.type == &patch_core_type){
+                elems[i] = ((patch_core_obj_t * ) core)->outer;
+            } else {
+                fail = true;
+            }
+        }
+        free(array);
+        if(fail) mp_raise_TypeError(MP_ERROR_TEXT("patch list core type mismatch"));
+
+        return mp_obj_new_list(len, elems);
+    } else {
+        free(array);
+        return mp_obj_new_list(0, NULL);
+    }
+}
+
+static mp_obj_t get_connections_from_array(bl00mbox_array_t * array){
+    if(!array) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
+    if(array->len){
         int len = array->len;
-        bl00mbox_signal_t *signals[len];
+        bl00mbox_signal_t * signals[len];
         memcpy(&signals, &array->elems, sizeof(void *) * len);
         // need to free before we can longjump
         free(array);
         mp_obj_t elems[len];
         int output_index = 0;
-        for (int i = 0; i < len; i++) {
-            plugin_core_obj_t *core = signals[i]->plugin->parent;
+        for(int i = 0; i < len; i++){
+            plugin_core_obj_t * pc = signals[i]->plugin->parent;
             // gc can happen during this loop. the objects we created are safe
-            // as they are on the stack, but unprocessed signals might've been
-            // collected
-            if (!core->plugin) continue;
-            mp_obj_t args[2] = { MP_OBJ_FROM_PTR(core),
-                                 mp_obj_new_int(signals[i]->index) };
-            elems[output_index] =
-                signal_core_make_new(&signal_core_type, 2, 0, args);
+            // as they are on the stack, but unprocessed signals might've been collected
+            if(!pc->plugin) continue;
+            signal_core_obj_t * sc = MP_OBJ_TO_PTR(mp_obj_subscr(pc->signals, mp_obj_new_int(i), MP_OBJ_SENTINEL));
+            elems[output_index] = sc->outer;
             output_index++;
         }
         return mp_obj_new_list(output_index, elems);
@@ -169,21 +238,21 @@ static mp_obj_t get_connections_from_array(bl00mbox_array_t *array) {
     }
 }
 
-static mp_obj_t get_plugins_from_array(bl00mbox_array_t *array) {
-    if (!array) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
-    if (array->len) {
+static mp_obj_t get_plugins_from_array(bl00mbox_array_t * array){
+    if(!array) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
+    if(array->len){
         int len = array->len;
-        bl00mbox_plugin_t *plugins[len];
+        bl00mbox_plugin_t * plugins[len];
         memcpy(&plugins, &array->elems, sizeof(void *) * len);
         // need to free before we can longjump
         free(array);
         mp_obj_t elems[len];
         int output_index = 0;
-        for (int i = 0; i < len; i++) {
-            plugin_core_obj_t *core = plugins[i]->parent;
+        for(int i = 0; i < len; i++){
+            plugin_core_obj_t * core = plugins[i]->parent;
             // gc shouldn't happen during this loop but we keep it in anyways :3
-            if (!core->plugin) continue;
-            elems[output_index] = MP_OBJ_FROM_PTR(core);
+            if(!core->plugin) continue;
+            elems[output_index] = core->outer;
             output_index++;
         }
         return mp_obj_new_list(output_index, elems);
@@ -241,27 +310,15 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_plugin_registry_num_plugins_obj,
 //        CHANNELS
 // ========================
 
-static mp_obj_t create_channel_plugin(bl00mbox_channel_t *chan) {
-    plugin_core_obj_t *self = m_new_obj_with_finaliser(plugin_core_obj_t);
-    self->base.type = &plugin_core_type;
-    self->plugin = chan->channel_plugin;
-    self->plugin->parent = self;
-    self->plugin->parent_self_ref = &self->plugin;
-
-    size_t num_signals = self->plugin->rugin->len_signals;
-    mp_obj_t nones[num_signals];
-    for (size_t i = 0; i < num_signals; i++) nones[i] = mp_const_none;
-    self->sources = mp_obj_new_list(num_signals, nones);
-
-    return MP_OBJ_FROM_PTR(self);
-}
-
-static mp_obj_t create_weak_channel_core(channel_core_obj_t *self) {
-    channel_core_obj_t *weak = m_new_obj_with_finaliser(channel_core_obj_t);
+static mp_obj_t create_weak_channel_core(channel_core_obj_t * self) {
+    channel_core_obj_t * weak = m_new_obj_with_finaliser(channel_core_obj_t);
     weak->base.type = &channel_core_type;
     weak->weak = MP_OBJ_FROM_PTR(weak);
     weak->chan = self->chan;
-    weak->chan->weak_parent_self_ref = &weak->chan;
+    weak->chan->weak_ref.self_ref = &weak->chan;
+
+    weak->outer = MP_OBJ_NULL;
+    weak->name = MP_OBJ_NULL;
 
     // only the real channel should prevent these from being gc'd
     weak->sources_mx = MP_OBJ_NULL;
@@ -274,43 +331,45 @@ static mp_obj_t create_weak_channel_core(channel_core_obj_t *self) {
 
 STATIC mp_obj_t channel_core_make_new(const mp_obj_type_t *type, size_t n_args,
                                       size_t n_kw, const mp_obj_t *args) {
-    mp_arg_check_num(n_args, n_kw, 1, 1, false);
-    channel_core_obj_t *self = m_new_obj_with_finaliser(channel_core_obj_t);
-    self->chan = NULL;  // must initialize if obj gets gc'd due to exception
+    mp_arg_check_num(n_args, n_kw, 4, 4, false);
+    channel_core_obj_t * self = m_new_obj_with_finaliser(channel_core_obj_t);
+    self->chan = NULL; // must initialize if obj gets gc'd due to exception
 
     self->base.type = &channel_core_type;
     self->sources_mx = mp_obj_new_list(0, NULL);
-    const char *name = strdup_raise(mp_obj_str_get_str(args[0]));
+    self->name = args[0];
+    char * name = mp_obj_str_get_str(self->name);
+    int32_t dev = mp_obj_get_int(args[1]);
 
-    bl00mbox_channel_t *chan = bl00mbox_channel_create();
-    if (!chan) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
+    bl00mbox_channel_t * chan = bl00mbox_channel_create();
+    if(!chan) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
 
-    chan->name = name;
-    chan->parent_self_ref = &self->chan;
+    chan->dev = dev;
+    chan->ref.self_ref = &self->chan;
     chan->parent = self;
 
+    self->outer = args[2];
     self->chan = chan;
     self->weak = create_weak_channel_core(self);
-    self->channel_plugin = create_channel_plugin(chan);
+    self->channel_plugin = create_channel_plugin(self, args[3]);
 #ifdef BL00MBOX_CALLBACKS_FUNSAFE
     self->callback = mp_const_none;
 #endif
-    bl00mbox_log_info("created channel %s", chan->name);
+    bl00mbox_log_info("created channel %s", name);
     return MP_OBJ_FROM_PTR(self);
 }
 
 STATIC mp_obj_t mp_channel_core_get_weak(mp_obj_t self_in) {
-    channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+    channel_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     channel_core_verify(self);
     return self->weak;
 }
-MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_get_weak_obj,
-                          mp_channel_core_get_weak);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_get_weak_obj, mp_channel_core_get_weak);
 
 mp_obj_t mp_channel_core_del(mp_obj_t self_in) {
-    channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
-    if (self->chan && (self->weak != self_in)) {
-        bl00mbox_log_info("destroyed channel %s", self->chan->name);
+    channel_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    if(self->chan && (self->weak != self_in)){
+        bl00mbox_log_info("destroyed channel");
         bl00mbox_channel_destroy(self->chan);
     }
     return mp_const_none;
@@ -318,16 +377,16 @@ mp_obj_t mp_channel_core_del(mp_obj_t self_in) {
 MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_del_obj, mp_channel_core_del);
 
 mp_obj_t mp_channel_core_delete(mp_obj_t self_in) {
-    channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+    channel_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     channel_core_verify(self);
-    bl00mbox_log_info("destroyed channel %s", self->chan->name);
+    bl00mbox_log_info("destroyed channel");
     bl00mbox_channel_destroy(self->chan);
     return mp_const_none;
 }
 MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_delete_obj, mp_channel_core_delete);
 
-mp_obj_t mp_channel_core_clear(mp_obj_t self_in) {
-    channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+mp_obj_t mp_channel_core_clear(mp_obj_t self_in){
+    channel_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     channel_core_verify(self);
     self->callback = mp_const_none;
     bl00mbox_channel_clear(self->chan);
@@ -335,32 +394,36 @@ mp_obj_t mp_channel_core_clear(mp_obj_t self_in) {
 }
 MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_clear_obj, mp_channel_core_clear);
 
-STATIC mp_obj_t mp_channel_core_get_connected_mx(mp_obj_t self_in) {
-    channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+STATIC mp_obj_t mp_channel_core_get_connected_mx(mp_obj_t self_in){
+    channel_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     channel_core_verify(self);
-    bl00mbox_array_t *array =
-        bl00mbox_channel_collect_connections_mx(self->chan);
+    bl00mbox_array_t * array = bl00mbox_channel_collect_connections_mx(self->chan);
     return get_connections_from_array(array);
 }
-MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_get_connected_mx_obj,
-                          mp_channel_core_get_connected_mx);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_get_connected_mx_obj, mp_channel_core_get_connected_mx);
 
-STATIC mp_obj_t mp_channel_core_get_plugins(mp_obj_t self_in) {
-    channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+STATIC mp_obj_t mp_channel_core_get_patches(mp_obj_t self_in){
+    channel_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     channel_core_verify(self);
-    bl00mbox_array_t *array = bl00mbox_channel_collect_plugins(self->chan);
-    return get_plugins_from_array(array);
+    return get_patches_from_container(&self->chan->patches);
+}
+MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_get_patches_obj, mp_channel_core_get_patches);
+
+static inline channel_core_obj_t * strong(bl00mbox_channel_t * chan){
+    return chan->parent;
+}
+
+static inline channel_core_obj_t * strong_obj(mp_obj_t core){
+    return ((channel_core_obj_t *) MP_OBJ_TO_PTR(core))->chan->parent;
 }
-MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_get_plugins_obj,
-                          mp_channel_core_get_plugins);
 
-STATIC void channel_core_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
-    channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
-    bl00mbox_channel_t *chan = self->chan;
+STATIC void channel_core_attr(mp_obj_t self_in, qstr attr,
+                                       mp_obj_t *dest) {
+    channel_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    bl00mbox_channel_t * chan = self->chan;
 
-    // if gc_sweep tries to load __del__ we mustn't raise exceptions, setjmp
-    // isn't set up
-    if (attr != MP_QSTR___del__) channel_core_verify(self);
+    // if gc_sweep tries to load __del__ we mustn't raise exceptions, setjmp isn't set up
+    if(attr != MP_QSTR___del__) channel_core_verify(self);
 
     if (dest[0] != MP_OBJ_NULL) {
         bool attr_exists = true;
@@ -370,24 +433,27 @@ STATIC void channel_core_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
         } else if (attr == MP_QSTR_sys_gain) {
             int gain = abs(mp_obj_get_int(dest[1]));
             chan->sys_gain = gain > 32767 ? 32767 : gain;
+        } else if (attr == MP_QSTR_compute_load) {
+            chan->compute_time = mp_obj_is_true(dest[1]);
         } else if (attr == MP_QSTR_compute_rms) {
             chan->compute_rms = mp_obj_is_true(dest[1]);
-            if (!chan->compute_rms) chan->mean_square = 0;
+            if(!chan->compute_rms) chan->mean_square = 0;
         } else if (attr == MP_QSTR_background_mute_override) {
-            bl00mbox_channel_set_background_mute_override(
-                chan, mp_obj_is_true(dest[1]));
+            bl00mbox_channel_set_background_mute_override(chan, mp_obj_is_true(dest[1]));
         } else if (attr == MP_QSTR_foreground) {
             bl00mbox_channel_set_foreground(chan, mp_obj_is_true(dest[1]));
 #ifdef BL00MBOX_CALLBACKS_FUNSAFE
         } else if (attr == MP_QSTR_callback) {
-            ((channel_core_obj_t *)chan->parent)->callback = dest[1];
+            strong(chan)->callback = dest[1];
 #endif
         } else {
             attr_exists = false;
         }
-        if (attr_exists) dest[0] = MP_OBJ_NULL;
+        if(attr_exists) dest[0] = MP_OBJ_NULL;
     } else {
-        if (attr == MP_QSTR_volume) {
+        if (attr == MP_QSTR_outer){
+            dest[0] = strong(chan)->outer;
+        } else if (attr == MP_QSTR_volume) {
             dest[0] = mp_obj_new_int(chan->volume);
         } else if (attr == MP_QSTR_sys_gain) {
             dest[0] = mp_obj_new_int(chan->sys_gain);
@@ -395,15 +461,19 @@ STATIC void channel_core_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
             dest[0] = mp_obj_new_int(chan->mean_square);
         } else if (attr == MP_QSTR_compute_rms) {
             dest[0] = mp_obj_new_bool(chan->compute_rms);
+        } else if (attr == MP_QSTR_dev) {
+            dest[0] = mp_obj_new_int(chan->dev);
+        } else if (attr == MP_QSTR_compute_load) {
+            dest[0] = mp_obj_new_bool(chan->compute_time);
+        } else if (attr == MP_QSTR_load) {
+            dest[0] = mp_obj_new_float(((float) chan->time)/BL00MBOX_REF_TIME);
         } else if (attr == MP_QSTR_name) {
-            char *ret = strdup_raise(chan->name);
-            dest[0] = mp_obj_new_str(ret, strlen(ret));
-            free(ret);
+            dest[0] = strong(chan)->name;
         } else if (attr == MP_QSTR_channel_plugin) {
-            dest[0] = ((channel_core_obj_t *)chan->parent)->channel_plugin;
+            dest[0] = strong(chan)->channel_plugin;
         } else if (attr == MP_QSTR_callback) {
 #ifdef BL00MBOX_CALLBACKS_FUNSAFE
-            dest[0] = ((channel_core_obj_t *)chan->parent)->callback;
+            dest[0] = ((channel_core_obj_t *) chan->parent)->callback;
 #else
             dest[0] = mp_const_none;
 #endif
@@ -421,41 +491,36 @@ STATIC void channel_core_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
             dest[1] = MP_OBJ_SENTINEL;
         }
     }
-}
+}      
 
 STATIC const mp_rom_map_elem_t channel_core_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_channel_core_del_obj) },
     { MP_ROM_QSTR(MP_QSTR_delete), MP_ROM_PTR(&mp_channel_core_delete_obj) },
     { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&mp_channel_core_clear_obj) },
-    { MP_ROM_QSTR(MP_QSTR_get_weak),
-      MP_ROM_PTR(&mp_channel_core_get_weak_obj) },
-    { MP_ROM_QSTR(MP_QSTR_get_connected_mx),
-      MP_ROM_PTR(&mp_channel_core_get_connected_mx_obj) },
-    { MP_ROM_QSTR(MP_QSTR_get_plugins),
-      MP_ROM_PTR(&mp_channel_core_get_plugins_obj) },
+    { MP_ROM_QSTR(MP_QSTR_get_weak), MP_ROM_PTR(&mp_channel_core_get_weak_obj) },
+    { MP_ROM_QSTR(MP_QSTR_get_connected_mx), MP_ROM_PTR(&mp_channel_core_get_connected_mx_obj) },
+    { MP_ROM_QSTR(MP_QSTR_get_patches), MP_ROM_PTR(&mp_channel_core_get_patches_obj) },
 };
 STATIC MP_DEFINE_CONST_DICT(channel_core_locals_dict,
                             channel_core_locals_dict_table);
 
-STATIC MP_DEFINE_CONST_OBJ_TYPE(channel_core_type, MP_QSTR_ChannelCore,
-                                MP_TYPE_FLAG_NONE, make_new,
-                                channel_core_make_new, locals_dict,
-                                &channel_core_locals_dict, attr,
-                                channel_core_attr);
-
-static mp_obj_t mp_collect_channels(mp_obj_t active_in) {
-    // critical phase: make sure to not trigger any garbage collection until
-    // these are on the stack!!
-    bl00mbox_array_t *chans =
-        bl00mbox_collect_channels(mp_obj_is_true(active_in));
+STATIC MP_DEFINE_CONST_OBJ_TYPE(channel_core_type, MP_QSTR_ChannelCore, MP_TYPE_FLAG_NONE,
+                                make_new, channel_core_make_new,
+                                locals_dict, &channel_core_locals_dict,
+                                attr, channel_core_attr);
+
+static mp_obj_t mp_collect_channels(mp_obj_t active_in){
+    // critical phase: make sure to not trigger any garbage collection until these
+    // are on the stack!!
+    bl00mbox_array_t * chans = bl00mbox_collect_channels(mp_obj_is_true(active_in));
     mp_obj_t ret = MP_OBJ_NULL;
-    if (chans && chans->len) {
+    if(chans && chans->len){
         mp_obj_t elems[chans->len];
         size_t output_index = 0;
-        for (size_t input_index = 0; input_index < chans->len; input_index++) {
-            bl00mbox_channel_t *chan = chans->elems[input_index];
-            channel_core_obj_t *core = chan->parent;
-            if (core->base.type != &channel_core_type) {
+        for(size_t input_index = 0; input_index < chans->len; input_index++){
+            bl00mbox_channel_t * chan = chans->elems[input_index];
+            channel_core_obj_t * core = chan->parent;
+            if(core->base.type != &channel_core_type){
                 bl00mbox_log_error("channel core corrupted");
                 continue;
             }
@@ -469,56 +534,84 @@ static mp_obj_t mp_collect_channels(mp_obj_t active_in) {
     free(chans);
     return ret;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_collect_channels_obj, mp_collect_channels);
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_collect_channels_obj,
+                                 mp_collect_channels);
 
 // ========================
 //         PLUGINS
 // ========================
 
+static mp_obj_t create_channel_plugin(channel_core_obj_t * chan_core, mp_obj_t outer){
+    bl00mbox_channel_t * chan = chan_core->chan;
+    plugin_core_obj_t * self = m_new_obj_with_finaliser(plugin_core_obj_t);
+    self->base.type = &plugin_core_type;
+    self->plugin = chan->channel_plugin;
+    self->plugin->parent = self;
+    self->plugin->ref.self_ref = &self->plugin;
+
+    self->outer = outer;
+
+    size_t num_signals = self->plugin->rugin->len_signals;
+    mp_obj_t elems[num_signals];
+    for(size_t i = 0; i < num_signals; i++) elems[i] = mp_const_none;
+    self->sources = mp_obj_new_list(num_signals, elems);
+    for(size_t i = 0; i < num_signals; i++) elems[i] = _signal_core_make_new(self, i);
+    self->signals = mp_obj_new_tuple(num_signals, elems);
+
+    return MP_OBJ_FROM_PTR(self);
+}
+
 STATIC mp_obj_t plugin_core_make_new(const mp_obj_type_t *type, size_t n_args,
-                                     size_t n_kw, const mp_obj_t *args) {
-    mp_arg_check_num(n_args, n_kw, 3, 3, false);
-    plugin_core_obj_t *self = m_new_obj_with_finaliser(plugin_core_obj_t);
+                                      size_t n_kw, const mp_obj_t *args) {
+    // args: ChannelCore, outer, container, plugin_type_id, init_var, [nick]
+    mp_arg_check_num(n_args, n_kw, 5, 6, false);
+    plugin_core_obj_t * self = m_new_obj_with_finaliser(plugin_core_obj_t);
     self->base.type = &plugin_core_type;
+    self->outer = args[1];
 
-    channel_core_obj_t *chan_core = MP_OBJ_TO_PTR(args[0]);
-    if (chan_core->base.type != &channel_core_type) {
-        mp_raise_TypeError(MP_ERROR_TEXT("first argument must be ChannelCore"));
-    }
-    channel_core_verify(chan_core);
-    bl00mbox_channel_t *chan = chan_core->chan;
-    int plugin_kind = mp_obj_get_int(args[1]);
-    int init_var = mp_obj_get_int(args[2]);
+    channel_core_obj_t * chan_core = get_channel_core(args[0]);
+    bl00mbox_channel_t * chan = chan_core->chan;
 
-    bl00mbox_plugin_t *plugin =
-        bl00mbox_plugin_create(chan, plugin_kind, init_var);
+    bl00mbox_patch_t * container = get_patch(args[2]);
+    self->container = container ? args[2] : mp_const_none;
 
-    if (!plugin) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
+    int plugin_type_id = mp_obj_get_int(args[3]);
+    int init_var = mp_obj_get_int(args[4]);
+
+    bl00mbox_plugin_t * plugin = bl00mbox_plugin_create(chan, container, plugin_type_id, init_var);
+
+    if(!plugin) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
 
     self->plugin = plugin;
     plugin->parent = self;
-    plugin->parent_self_ref = &self->plugin;
+    plugin->ref.self_ref = &self->plugin;
+
+    if(n_kw > 5 && args[5] != mp_const_none){
+        self->name = args[5];
+    } else {
+        char * name = plugin->rugin->descriptor->name;
+        self->name = mp_obj_new_str(name, strlen(name));
+    }
 
-    // create empty source list
     size_t num_signals = plugin->rugin->len_signals;
-    mp_obj_t nones[num_signals];
-    for (size_t i = 0; i < num_signals; i++) nones[i] = mp_const_none;
-    self->sources = mp_obj_new_list(num_signals, nones);
+    mp_obj_t elems[num_signals];
+
+    for(size_t i = 0; i < num_signals; i++) elems[i] = mp_const_none;
+    self->sources = mp_obj_new_list(num_signals, elems);
 
-    bl00mbox_log_info("created plugin %s",
-                      self->plugin->rugin->descriptor->name);
+    for(size_t i = 0; i < num_signals; i++) elems[i] = _signal_core_make_new(self, i);
+    self->signals = mp_obj_new_tuple(num_signals, elems);
+
+    bl00mbox_log_info("created plugin %s", self->plugin->rugin->descriptor->name);
     return MP_OBJ_FROM_PTR(self);
 }
 
 mp_obj_t mp_plugin_core_del(mp_obj_t self_in) {
-    plugin_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
-    // do not verify here as it will result in prints from the gc if the channel
-    // object was already collected. don't gc the channel plugin, it is deleted
-    // with the channel.
-    if (self->plugin &&
-        (self->plugin->rugin->descriptor->id != BL00MBOX_CHANNEL_PLUGIN_ID)) {
-        bl00mbox_log_info("deleted plugin %s",
-                          self->plugin->rugin->descriptor->name);
+    plugin_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    // do not verify here as it will result in prints from the gc if the channel object
+    // was already collected. don't gc the channel plugin, it is deleted with the channel.
+    if(self->plugin && (self->plugin->rugin->descriptor->id != BL00MBOX_CHANNEL_PLUGIN_ID)){
+        bl00mbox_log_info("deleted plugin %s", self->plugin->rugin->descriptor->name);
         bl00mbox_plugin_destroy(self->plugin);
     }
     return mp_const_none;
@@ -526,11 +619,10 @@ mp_obj_t mp_plugin_core_del(mp_obj_t self_in) {
 MP_DEFINE_CONST_FUN_OBJ_1(mp_plugin_core_del_obj, mp_plugin_core_del);
 
 mp_obj_t mp_plugin_core_delete(mp_obj_t self_in) {
-    plugin_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+    plugin_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     plugin_core_verify(self);
-    if (self->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) {
-        mp_raise_TypeError(
-            MP_ERROR_TEXT("cannot manually delete channel plugin"));
+    if(self->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID){
+        mp_raise_TypeError(MP_ERROR_TEXT("cannot manually delete channel plugin"));
     }
     bl00mbox_log_info("deleted plugin");
     bl00mbox_plugin_destroy(self->plugin);
@@ -538,30 +630,57 @@ mp_obj_t mp_plugin_core_delete(mp_obj_t self_in) {
 }
 MP_DEFINE_CONST_FUN_OBJ_1(mp_plugin_core_delete_obj, mp_plugin_core_delete);
 
-STATIC void plugin_core_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
-    plugin_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
-    bl00mbox_plugin_t *plugin = self->plugin;
+mp_obj_t mp_plugin_core_get_sources(mp_obj_t self_in) {
+    plugin_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    bl00mbox_plugin_t * plugin = self->plugin;
+    plugin_core_verify(self);
+    int max_len = plugin->rugin->len_signals;
+    int index = 0;
+    mp_obj_t elems[max_len];
+    mp_obj_list_t * list = MP_OBJ_TO_PTR(self->sources);
+    for(int x = 0; x < max_len; x++){
+        mp_obj_t elem = list->items[x];
+        if(elem != mp_const_none){
+            elems[index] = elem;
+            index++;
+        }
+    }
+    return mp_obj_new_list(index, elems);
+}
+MP_DEFINE_CONST_FUN_OBJ_1(mp_plugin_core_get_sources_obj, mp_plugin_core_get_sources);
+
+STATIC void plugin_core_attr(mp_obj_t self_in, qstr attr,
+                                       mp_obj_t *dest) {
+    plugin_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    bl00mbox_plugin_t * plugin = self->plugin;
 
-    // if gc_sweep tries to load __del__ we mustn't raise exceptions, setjmp
-    // isn't set up
-    if (attr != MP_QSTR___del__) plugin_core_verify(self);
+    // if gc_sweep tries to load __del__ we mustn't raise exceptions, setjmp isn't set up
+    if(attr != MP_QSTR___del__) plugin_core_verify(self);
 
     if (dest[0] != MP_OBJ_NULL) {
         if (attr == MP_QSTR_always_render) {
             bl00mbox_plugin_set_always_render(plugin, mp_obj_is_true(dest[1]));
             dest[0] = MP_OBJ_NULL;
-        }
+        } 
     } else {
         if (attr == MP_QSTR_name) {
+            dest[0] = self->name;
+        } else if (attr == MP_QSTR_plugin_name) {
             // no need to strdup here, descriptors don't die
-            char *ret = plugin->rugin->descriptor->name;
+            char * ret = plugin->rugin->descriptor->name;
             dest[0] = mp_obj_new_str(ret, strlen(ret));
         } else if (attr == MP_QSTR_id) {
             dest[0] = mp_obj_new_int(plugin->id);
+        } else if (attr == MP_QSTR_signal_cores) {
+            dest[0] = self->signals;
         } else if (attr == MP_QSTR_plugin_id) {
             dest[0] = mp_obj_new_int(plugin->rugin->descriptor->id);
         } else if (attr == MP_QSTR_init_var) {
             dest[0] = mp_obj_new_int(plugin->init_var);
+        } else if (attr == MP_QSTR_outer) {
+            dest[0] = self->outer;
+        } else if (attr == MP_QSTR_channel) {
+            dest[0] = strong(plugin->channel)->outer;
         } else if (attr == MP_QSTR_num_signals) {
             dest[0] = mp_obj_new_int(plugin->rugin->len_signals);
         } else if (attr == MP_QSTR_always_render) {
@@ -570,51 +689,44 @@ STATIC void plugin_core_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
             dest[0] = mp_obj_new_int(plugin->rugin->plugin_table_len);
         } else if (attr == MP_QSTR_table_pointer) {
             // if there's no table this returns 0, which is fine by us
-            int16_t *ret = plugin->rugin->plugin_table;
-            dest[0] = mp_obj_new_int_from_uint((uint32_t)ret);
+            int16_t * ret = plugin->rugin->plugin_table;
+            dest[0] = mp_obj_new_int_from_uint((uint32_t) ret);
         } else {
             dest[1] = MP_OBJ_SENTINEL;
         }
     }
-}
+}      
 
 STATIC const mp_rom_map_elem_t plugin_core_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_plugin_core_del_obj) },
     { MP_ROM_QSTR(MP_QSTR_delete), MP_ROM_PTR(&mp_plugin_core_delete_obj) },
+    { MP_ROM_QSTR(MP_QSTR_get_sources), MP_ROM_PTR(&mp_plugin_core_get_sources_obj) },
 };
 STATIC MP_DEFINE_CONST_DICT(plugin_core_locals_dict,
                             plugin_core_locals_dict_table);
 
-STATIC MP_DEFINE_CONST_OBJ_TYPE(plugin_core_type, MP_QSTR_PluginCore,
-                                MP_TYPE_FLAG_NONE, make_new,
-                                plugin_core_make_new, locals_dict,
-                                &plugin_core_locals_dict, attr,
-                                plugin_core_attr);
+STATIC MP_DEFINE_CONST_OBJ_TYPE(plugin_core_type, MP_QSTR_PluginCore, MP_TYPE_FLAG_NONE,
+                                make_new, plugin_core_make_new,
+                                locals_dict, &plugin_core_locals_dict,
+                                attr, plugin_core_attr);
+
 
 // ========================
 //         SIGNALS
 // ========================
 
-STATIC mp_obj_t signal_core_make_new(const mp_obj_type_t *type, size_t n_args,
-                                     size_t n_kw, const mp_obj_t *args) {
-    mp_arg_check_num(n_args, n_kw, 2, 2, false);
-
-    plugin_core_obj_t *plugin_core = MP_OBJ_TO_PTR(args[0]);
-    if (plugin_core->base.type != &plugin_core_type) {
-        mp_raise_TypeError(MP_ERROR_TEXT("first argument must be PluginCore"));
-    }
+STATIC mp_obj_t _signal_core_make_new(plugin_core_obj_t * plugin_core, int index){
     // do this before verification
-    signal_core_obj_t *self = m_new_obj(signal_core_obj_t);
-    int index = mp_obj_get_int(args[1]);
+    signal_core_obj_t * self = m_new_obj(signal_core_obj_t);
 
     plugin_core_verify(plugin_core);
-
-    radspa_t *rugin = plugin_core->plugin->rugin;
-    if (index >= rugin->len_signals) {
-        mp_raise_msg(&mp_type_IndexError,
-                     MP_ERROR_TEXT("signal index out of range"));
+    
+    radspa_t * rugin = plugin_core->plugin->rugin;
+    if(index >= rugin->len_signals){
+        mp_raise_msg(&mp_type_IndexError, MP_ERROR_TEXT("signal index out of range"));
     }
     self->base.type = &signal_core_type;
+    self->outer = mp_const_none;
     self->plugin_core = plugin_core;
     self->signal.rignal = &rugin->signals[index];
     self->signal.plugin = plugin_core->plugin;
@@ -622,91 +734,105 @@ STATIC mp_obj_t signal_core_make_new(const mp_obj_type_t *type, size_t n_args,
     return MP_OBJ_FROM_PTR(self);
 }
 
-STATIC mp_obj_t mp_signal_core_connect(mp_obj_t self_in, mp_obj_t other_in) {
-    signal_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+/*
+STATIC mp_obj_t signal_core_make_new(const mp_obj_type_t *type, size_t n_args,
+                                      size_t n_kw, const mp_obj_t *args) {
+    mp_arg_check_num(n_args, n_kw, 2, 2, false);
+
+    plugin_core_obj_t * plugin_core = MP_OBJ_TO_PTR(args[0]);
+    int index = mp_obj_get_int(args[1]);
+    if(plugin_core->base.type != &plugin_core_type){
+        mp_raise_TypeError(MP_ERROR_TEXT("first argument must be PluginCore"));
+    }
+    return mp_obj_subscr(plugin_core->signals, mp_obj_new_int(index), MP_OBJ_SENTINEL);
+}
+*/
+
+STATIC mp_obj_t mp_signal_core_connect(mp_obj_t self_in, mp_obj_t other_in){
+    signal_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     plugin_core_verify(MP_OBJ_TO_PTR(self->plugin_core));
-    signal_core_obj_t *other = MP_OBJ_TO_PTR(other_in);
+    signal_core_obj_t * other = MP_OBJ_TO_PTR(other_in);
     plugin_core_verify(MP_OBJ_TO_PTR(other->plugin_core));
-    bl00mbox_error_unwrap(
-        bl00mbox_signal_connect(&self->signal, &other->signal));
+    bl00mbox_error_unwrap(bl00mbox_signal_connect(&self->signal, &other->signal));
     return mp_const_none;
 }
 MP_DEFINE_CONST_FUN_OBJ_2(mp_signal_core_connect_obj, mp_signal_core_connect);
 
 // legacy support
-STATIC mp_obj_t mp_signal_core_connect_mx(mp_obj_t self_in) {
-    signal_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+STATIC mp_obj_t mp_signal_core_connect_mx(mp_obj_t self_in){
+    signal_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     plugin_core_verify(MP_OBJ_TO_PTR(self->plugin_core));
     bl00mbox_error_unwrap(bl00mbox_signal_connect_mx(&self->signal));
     return mp_const_none;
 }
-MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_connect_mx_obj,
-                          mp_signal_core_connect_mx);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_connect_mx_obj, mp_signal_core_connect_mx);
 
-STATIC mp_obj_t mp_signal_core_disconnect(mp_obj_t self_in) {
-    signal_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+STATIC mp_obj_t mp_signal_core_disconnect(mp_obj_t self_in){
+    signal_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     plugin_core_verify(MP_OBJ_TO_PTR(self->plugin_core));
     bl00mbox_signal_disconnect(&self->signal);
     return mp_const_none;
 }
-MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_disconnect_obj,
-                          mp_signal_core_disconnect);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_disconnect_obj, mp_signal_core_disconnect);
 
-STATIC mp_obj_t mp_signal_core_get_connected(mp_obj_t self_in) {
-    signal_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
+STATIC mp_obj_t mp_signal_core_get_connected(mp_obj_t self_in){
+    signal_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
     plugin_core_verify(MP_OBJ_TO_PTR(self->plugin_core));
-    bl00mbox_array_t *array =
-        bl00mbox_signal_collect_connections(&self->signal);
+    bl00mbox_array_t * array = bl00mbox_signal_collect_connections(&self->signal);
     return get_connections_from_array(array);
 }
-MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_get_connected_obj,
-                          mp_signal_core_get_connected);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_get_connected_obj, mp_signal_core_get_connected);
 
-STATIC void signal_core_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
-    signal_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
-    plugin_core_obj_t *plugin_core = MP_OBJ_TO_PTR(self->plugin_core);
-    bl00mbox_signal_t *signal = &self->signal;
-    radspa_signal_t *rignal = signal->rignal;
+STATIC void signal_core_attr(mp_obj_t self_in, qstr attr,
+                                       mp_obj_t *dest) {
+    signal_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    plugin_core_obj_t * plugin_core = MP_OBJ_TO_PTR(self->plugin_core);
+    bl00mbox_signal_t * signal = &self->signal;
+    radspa_signal_t * rignal = signal->rignal;
 
-    // if gc_sweep tries to load __del__ we mustn't raise exceptions, setjmp
-    // isn't set up
-    if (attr != MP_QSTR___del__) plugin_core_verify(plugin_core);
+    // if gc_sweep tries to load __del__ we mustn't raise exceptions, setjmp isn't set up
+    if(attr != MP_QSTR___del__) plugin_core_verify(plugin_core);
 
     if (dest[0] != MP_OBJ_NULL) {
         if (attr == MP_QSTR_value) {
-            if (bl00mbox_signal_is_input(signal)) {
-                bl00mbox_error_unwrap(
-                    bl00mbox_signal_set_value(signal, mp_obj_get_int(dest[1])));
+            if(bl00mbox_signal_is_input(signal)){
+                bl00mbox_error_unwrap(bl00mbox_signal_set_value(signal, mp_obj_get_int(dest[1])));
+                dest[0] = MP_OBJ_NULL;
+            }
+        } else if (attr == MP_QSTR_outer) {
+            if(self->outer == mp_const_none){
+                self->outer = dest[1];
                 dest[0] = MP_OBJ_NULL;
             }
         }
     } else {
         if (attr == MP_QSTR_index) {
             dest[0] = mp_obj_new_int(signal->index);
-        } else if (attr == MP_QSTR_plugin_core) {
-            dest[0] = plugin_core;
+        } else if (attr == MP_QSTR_plugin) {
+            dest[0] = plugin_core->outer;
         } else if (attr == MP_QSTR_name) {
-            char *ret = strdup_raise(rignal->name);
+            char * ret = strdup_raise(rignal->name);
             dest[0] = mp_obj_new_str(ret, strlen(ret));
             free(ret);
         } else if (attr == MP_QSTR_mpx) {
             dest[0] = mp_obj_new_int(rignal->name_multiplex);
+        } else if (attr == MP_QSTR_outer) {
+            dest[0] = self->outer;
         } else if (attr == MP_QSTR_description) {
-            char *ret = strdup_raise(rignal->description);
+            char * ret = strdup_raise(rignal->description);
             dest[0] = mp_obj_new_str(ret, strlen(ret));
             free(ret);
         } else if (attr == MP_QSTR_unit) {
-            char *ret = strdup_raise(rignal->unit);
+            char * ret = strdup_raise(rignal->unit);
             dest[0] = mp_obj_new_str(ret, strlen(ret));
             free(ret);
         } else if (attr == MP_QSTR_value) {
             dest[0] = mp_obj_new_int(bl00mbox_signal_get_value(signal));
         } else if (attr == MP_QSTR_connected_mx) {
             bool ret = false;
-            if (bl00mbox_signal_is_output(signal)) {
-                bl00mbox_connection_t *conn =
-                    bl00mbox_connection_from_signal(signal);
-                if (conn) ret = conn->connected_to_mixer;
+            if(bl00mbox_signal_is_output(signal)){
+                bl00mbox_connection_t * conn = bl00mbox_connection_from_signal(signal);
+                if(conn) ret = conn->connected_to_mixer;
             }
             dest[0] = mp_obj_new_bool(ret);
         } else if (attr == MP_QSTR_hints) {
@@ -716,25 +842,118 @@ STATIC void signal_core_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
             dest[1] = MP_OBJ_SENTINEL;
         }
     }
-}
+}      
 
 STATIC const mp_rom_map_elem_t signal_core_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&mp_signal_core_connect_obj) },
-    { MP_ROM_QSTR(MP_QSTR_connect_mx),
-      MP_ROM_PTR(&mp_signal_core_connect_mx_obj) },
-    { MP_ROM_QSTR(MP_QSTR_disconnect),
-      MP_ROM_PTR(&mp_signal_core_disconnect_obj) },
-    { MP_ROM_QSTR(MP_QSTR_get_connected),
-      MP_ROM_PTR(&mp_signal_core_get_connected_obj) },
+    { MP_ROM_QSTR(MP_QSTR_connect_mx), MP_ROM_PTR(&mp_signal_core_connect_mx_obj) },
+    { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&mp_signal_core_disconnect_obj) },
+    { MP_ROM_QSTR(MP_QSTR_get_connected), MP_ROM_PTR(&mp_signal_core_get_connected_obj) },
 };
 STATIC MP_DEFINE_CONST_DICT(signal_core_locals_dict,
                             signal_core_locals_dict_table);
 
-STATIC MP_DEFINE_CONST_OBJ_TYPE(signal_core_type, MP_QSTR_SignalCore,
-                                MP_TYPE_FLAG_NONE, make_new,
-                                signal_core_make_new, locals_dict,
-                                &signal_core_locals_dict, attr,
-                                signal_core_attr);
+STATIC MP_DEFINE_CONST_OBJ_TYPE(signal_core_type, MP_QSTR_SignalCore, MP_TYPE_FLAG_NONE,
+                                //make_new, signal_core_make_new,
+                                locals_dict, &signal_core_locals_dict,
+                                attr, signal_core_attr);
+
+// ========================
+//         PATCHES
+// ========================
+
+STATIC mp_obj_t patch_core_make_new(const mp_obj_type_t *type, size_t n_args,
+                                      size_t n_kw, const mp_obj_t *args) {
+    // args: ChannelCore, outer, container, patch_name, [name]
+    mp_arg_check_num(n_args, n_kw, 4, 5, false);
+
+    channel_core_obj_t * chan_core = get_channel_core(args[0]);
+
+    bl00mbox_patch_t * container = get_patch(args[2]);
+
+    patch_core_obj_t * self = m_new_obj_with_finaliser(patch_core_obj_t);
+    self->base.type = &patch_core_type;
+    self->container = container ? args[2] : mp_const_none;
+
+    bl00mbox_patch_t * patch = bl00mbox_patch_create(chan_core->chan, container);
+
+    if(!patch) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
+
+    patch->parent = self;
+    patch->ref.self_ref = &self->patch;
+    self->patch = patch;
+    self->weak_channel = chan_core->weak;
+
+    self->outer = args[1];
+    self->patch_name = args[3];
+
+    self->name = n_kw > 4 ? args[4] : mp_const_none;
+
+    bl00mbox_log_info("created patch %s", mp_obj_str_get_str(self->patch_name));
+    return MP_OBJ_FROM_PTR(self);
+}
+
+STATIC void patch_core_attr(mp_obj_t self_in, qstr attr,
+                                       mp_obj_t *dest) {
+    patch_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+
+    if (dest[0] == MP_OBJ_NULL) {
+        if (attr == MP_QSTR_name) {
+            dest[0] = self->name;
+        } else if (attr == MP_QSTR_patch_name) {
+            dest[0] = self->patch_name;
+        } else if (attr == MP_QSTR_outer) {
+            dest[0] = self->outer;
+        } else if (attr == MP_QSTR_channel) {
+            channel_core_verify(self->weak_channel);
+            dest[0] = strong_obj(self->weak_channel)->outer;
+        } else {
+            dest[1] = MP_OBJ_SENTINEL;
+        }
+    }
+}
+mp_obj_t mp_patch_core_del(mp_obj_t self_in) {
+    patch_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    if(self->patch){
+        bl00mbox_log_info("deleted patch");
+        bl00mbox_patch_destroy(self->patch);
+    }
+    return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(mp_patch_core_del_obj, mp_patch_core_del);
+
+mp_obj_t mp_patch_core_delete(mp_obj_t self_in) {
+    patch_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    patch_core_verify(self);
+    bl00mbox_log_info("deleted patch");
+    bl00mbox_patch_destroy(self->patch);
+    return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(mp_patch_core_delete_obj, mp_patch_core_delete);
+
+STATIC mp_obj_t mp_patch_core_get_patches(mp_obj_t self_in){
+    patch_core_obj_t * self = MP_OBJ_TO_PTR(self_in);
+    patch_core_verify(self);
+    return get_patches_from_container(&self->patch->patches);
+}
+MP_DEFINE_CONST_FUN_OBJ_1(mp_patch_core_get_patches_obj, mp_patch_core_get_patches);
+
+STATIC const mp_rom_map_elem_t patch_core_locals_dict_table[] = {
+    { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_patch_core_del_obj) },
+    { MP_ROM_QSTR(MP_QSTR_delete), MP_ROM_PTR(&mp_patch_core_delete_obj) },
+    { MP_ROM_QSTR(MP_QSTR_get_patches), MP_ROM_PTR(&mp_patch_core_get_patches_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(patch_core_locals_dict,
+                            patch_core_locals_dict_table);
+
+STATIC MP_DEFINE_CONST_OBJ_TYPE(patch_core_type, MP_QSTR_PatchCore, MP_TYPE_FLAG_NONE,
+                                locals_dict, &patch_core_locals_dict,
+                                make_new, patch_core_make_new,
+                                attr, patch_core_attr);
+
+// ========================
+//         MODULE
+// ========================
 
 STATIC const mp_rom_map_elem_t bl00mbox_globals_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR___name__),
@@ -750,16 +969,15 @@ STATIC const mp_rom_map_elem_t bl00mbox_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR_plugin_index_get_description),
       MP_ROM_PTR(&mp_plugin_index_get_description_obj) },
 
-    // CHANNELS
+    // CORES
     { MP_OBJ_NEW_QSTR(MP_QSTR_ChannelCore), (mp_obj_t)&channel_core_type },
-    { MP_ROM_QSTR(MP_QSTR_collect_channels),
-      MP_ROM_PTR(&mp_collect_channels_obj) },
-
-    // PLUGINS
     { MP_OBJ_NEW_QSTR(MP_QSTR_PluginCore), (mp_obj_t)&plugin_core_type },
-
-    // SIGNALS
     { MP_OBJ_NEW_QSTR(MP_QSTR_SignalCore), (mp_obj_t)&signal_core_type },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_PatchCore), (mp_obj_t)&patch_core_type },
+
+    // GLOBALS
+    { MP_ROM_QSTR(MP_QSTR_collect_channels),
+      MP_ROM_PTR(&mp_collect_channels_obj) },
 
     // CONSTANTS
     { MP_ROM_QSTR(MP_QSTR_SIGNAL_HINT_OUTPUT),
@@ -777,8 +995,7 @@ STATIC const mp_rom_map_elem_t bl00mbox_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR_BL00MBOX_CHANNEL_PLUGIN_ID),
       MP_ROM_INT(BL00MBOX_CHANNEL_PLUGIN_ID) },
 
-    { MP_ROM_QSTR(MP_QSTR_ReferenceError),
-      MP_ROM_PTR(&mp_type_ReferenceError) },
+    { MP_ROM_QSTR(MP_QSTR_ReferenceError), MP_ROM_PTR(&mp_type_ReferenceError) },
 };
 
 STATIC MP_DEFINE_CONST_DICT(mp_module_bl00mbox_globals, bl00mbox_globals_table);
diff --git a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.c b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.c
index b5a4c756193636dbb32a293967f6191d162455a4..2581b1868f39900df012cf960ab0dd58e3c708ac 100644
--- a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.c
+++ b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.c
@@ -14,7 +14,7 @@ radspa_descriptor_t bl00mbox_channel_plugin_desc = {
 // this could be faster at some point by adding a special case but we don't wanna think about it now.
 
 typedef struct {
-    int16_t buffer[BL00MBOX_MAX_BUFFER_LEN];
+    int16_t buffer[RADSPA_BUFFER_LEN];
     uint32_t buffer_render_pass_id;
     int16_t value;
     int16_t value_render_pass_id;
@@ -22,9 +22,9 @@ typedef struct {
 
 static line_in_singleton_t line_in;
 
-static inline void update_line_in_buffer(uint16_t num_samples, uint32_t render_pass_id){
+static inline void update_line_in_buffer(uint32_t render_pass_id){
     if(line_in.buffer_render_pass_id == render_pass_id) return;
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         int32_t val = bl00mbox_line_in_interlaced[2*i];
         val += bl00mbox_line_in_interlaced[2*i+1];
         val = radspa_clip(val>>1);
@@ -44,14 +44,14 @@ static inline void update_line_in_value(uint32_t render_pass_id){
     line_in.value_render_pass_id = render_pass_id;
 }
 
-void bl00mbox_channel_plugin_run(radspa_t * channel_plugin, uint16_t num_samples, uint32_t render_pass_id){
+void bl00mbox_channel_plugin_run(radspa_t * channel_plugin, uint32_t render_pass_id){
     if(!bl00mbox_line_in_interlaced) return;
 
     // handle line in signal, only render full if requested
     radspa_signal_t * input_sig = radspa_signal_get_by_index(channel_plugin, 0);
     if(input_sig->buffer){
-        update_line_in_buffer(num_samples, render_pass_id);
-        memcpy(input_sig->buffer, line_in.buffer, sizeof(int16_t) * num_samples);
+        update_line_in_buffer(render_pass_id);
+        memcpy(input_sig->buffer, line_in.buffer, sizeof(int16_t) * RADSPA_BUFFER_LEN);
     }
     // do NOT render output here, if somebody has requested channel this would be too early
     // we're accessing the source buffer directly if present to avoid needing memcpy
diff --git a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.h b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.h
index 0e5adefaf7da3e2fa55da3e8e14a23d8289d94ed..83ea14d56646376ef217afc9be4b2a7e8128b787 100644
--- a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.h
+++ b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.h
@@ -8,6 +8,6 @@
 
 extern radspa_descriptor_t bl00mbox_channel_plugin_desc;
 radspa_t * bl00mbox_channel_plugin_create(uint32_t init_var);
-void bl00mbox_channel_plugin_run(radspa_t * channel_plugin, uint16_t num_samples, uint32_t render_pass_id);
+void bl00mbox_channel_plugin_run(radspa_t * channel_plugin, uint32_t render_pass_id);
 
 void bl00mbox_channel_plugin_update_values(radspa_t * channel_plugin, uint32_t render_pass_id);
diff --git a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.c b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.c
index b0de70b2b66a2045d01ffabf571bfee75eb60cc5..b5bce1d088a9209559fa01bafcb383e6b349bb9d 100644
--- a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.c
+++ b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.c
@@ -14,14 +14,14 @@ radspa_descriptor_t bl00mbox_line_in_desc = {
 // pointer serves as a channel-specific source plugin identifier, but we could at least reduce
 // the overhead to a memcpy.
 
-void bl00mbox_line_in_run(radspa_t * line_in, uint16_t num_samples, uint32_t render_pass_id){
+void bl00mbox_line_in_run(radspa_t * line_in, uint32_t render_pass_id){
     if(bl00mbox_line_in_interlaced == NULL) return;
     radspa_signal_t * left_sig = radspa_signal_get_by_index(line_in, 0);
     radspa_signal_t * right_sig = radspa_signal_get_by_index(line_in, 1);
     radspa_signal_t * mid_sig = radspa_signal_get_by_index(line_in, 2);
     radspa_signal_t * gain_sig = radspa_signal_get_by_index(line_in, 3);
 
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         int32_t gain = radspa_signal_get_value(gain_sig, i, render_pass_id);
         if(left_sig->buffer != NULL){
             int16_t left = radspa_gain(bl00mbox_line_in_interlaced[2*i], gain);
diff --git a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.h b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.h
index 1a895b646393e4c0df9be0c45cd7944d29548455..f2f87315f94853096af15628040acd25ae0abb1d 100644
--- a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.h
+++ b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.h
@@ -6,4 +6,4 @@
 
 extern radspa_descriptor_t bl00mbox_line_in_desc;
 radspa_t * bl00mbox_line_in_create(uint32_t init_var);
-void bl00mbox_line_in_run(radspa_t * line_in, uint16_t num_samples, uint32_t render_pass_id);
+void bl00mbox_line_in_run(radspa_t * line_in, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/radspa.h b/components/bl00mbox/radspa/radspa.h
index 0223beaae88019cf19dcbd7e48169d341a3c0390..f0befef404d9a44029f36f71a4ed34063ef8dc84 100644
--- a/components/bl00mbox/radspa/radspa.h
+++ b/components/bl00mbox/radspa/radspa.h
@@ -23,6 +23,8 @@
 #include <string.h>
 #include <stdbool.h>
 #include <stdint.h>
+#include <stdio.h>
+#include "radspa_config.h"
 
 /* CONVENTIONS
  *
@@ -50,9 +52,10 @@
 
 struct _radspa_descriptor_t;
 struct _radspa_signal_t;
+struct _radspa_buffer_t;
 struct _radspa_t;
 
-typedef void (* radspa_render_t)(struct _radspa_t * plugin, uint16_t num_samples, uint32_t render_pass_id);
+typedef void (* radspa_render_t)(struct _radspa_t * plugin, uint32_t render_pass_id);
 
 typedef struct _radspa_descriptor_t{
     char * name;
@@ -77,11 +80,10 @@ typedef struct _radspa_signal_t{
     // -1 to disable signal multiplexing
     int8_t name_multiplex;
     // buffer full of samples, may be NULL.
-    int16_t * buffer;
+    struct _radspa_buffer_t * buffer;
     // static value to be used when buffer is NULL for input signals only
     int16_t value;
     // when the signal has last requested to render its source
-    uint32_t render_pass_id;
 } radspa_signal_t;
 
 typedef struct _radspa_t{
@@ -107,6 +109,11 @@ typedef struct _radspa_t{
     radspa_signal_t signals[]; 
 } radspa_t;
 
+typedef struct _radspa_buffer_t{
+    int16_t data[RADSPA_BUFFER_LEN];
+    radspa_t * plugin;
+} radspa_buffer_t;
+
 /* REQUIREMENTS
  * Hosts must provide implementations for the following functions:
  */
@@ -117,8 +124,14 @@ typedef struct _radspa_t{
  */
 extern uint32_t radspa_sct_to_rel_freq(int16_t sct, int16_t undersample_pow);
 
-// Return 1 if the buffer wasn't rendered already, 0 otherwise.
-extern bool radspa_host_request_buffer_render(int16_t * buf);
+extern int16_t radspa_random();
 
+inline void radspa_render_plugin(radspa_t * plugin, uint32_t render_pass_id){
+    if (plugin->render_pass_id == render_pass_id) return;
+    plugin->render_pass_id = render_pass_id;
+    plugin->render(plugin, render_pass_id);
+}
 
-extern int16_t radspa_random();
+inline void radspa_render_buffer(radspa_buffer_t * buf, uint32_t render_pass_id){
+    radspa_render_plugin(buf->plugin, render_pass_id);
+}
diff --git a/components/bl00mbox/radspa/radspa_helpers.h b/components/bl00mbox/radspa/radspa_helpers.h
index 4a0dd878d6963e855e3e4657b3526d1b35c8639e..b85264670983f02e4f404329ca5a416b4698b021 100644
--- a/components/bl00mbox/radspa/radspa_helpers.h
+++ b/components/bl00mbox/radspa/radspa_helpers.h
@@ -68,23 +68,22 @@ inline radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t
     return &(plugin->signals[signal_index]);
 }
 
-/* returns the value that a signal has at a given moment in time. time is
- * represented as the buffer index. requests rendering from host and requires implementation
- * of radspa_host_request_buffer_render.
- */
+inline void radspa_render_signal(radspa_signal_t * sig, uint32_t render_pass_id){
+    radspa_render_buffer(sig->buffer, render_pass_id);
+}
 
 inline int16_t radspa_signal_get_value(radspa_signal_t * sig, int16_t index, uint32_t render_pass_id){
     if(sig->buffer){
-        if(sig->render_pass_id != render_pass_id) radspa_host_request_buffer_render(sig->buffer);
-        if(sig->buffer[1] == -32768) return sig->buffer[0];
-        return sig->buffer[index];
+        radspa_render_signal(sig, render_pass_id);
+        if(sig->buffer->data[1] == -32768) return sig->buffer->data[0];
+        return sig->buffer->data[index];
     }
     return sig->value;
 }
 
 inline void radspa_signal_set_value_noclip(radspa_signal_t * sig, int16_t index, int16_t val){
     if(sig->buffer != NULL){
-        sig->buffer[index] = val;
+        sig->buffer->data[index] = val;
     } else if(!index){
         sig->value = val;
     }
@@ -92,24 +91,24 @@ inline void radspa_signal_set_value_noclip(radspa_signal_t * sig, int16_t index,
 
 inline void radspa_signal_set_value(radspa_signal_t * sig, int16_t index, int32_t val){
     if(sig->buffer != NULL){
-        sig->buffer[index] = radspa_clip(val);
+        sig->buffer->data[index] = radspa_clip(val);
     } else if(!index){
         sig->value = radspa_clip(val);
     }
 }
 
-inline void radspa_signal_copy(radspa_signal_t * input, radspa_signal_t * output, uint32_t buffer_len, uint32_t render_pass_id){
+inline void radspa_signal_copy(radspa_signal_t * input, radspa_signal_t * output, uint32_t render_pass_id){
     if(!output->buffer){
         output->value = radspa_signal_get_value(input, 0, render_pass_id);
     } else if(!input->buffer){
-        output->buffer[0] = input->value;
-        output->buffer[1] = -32768;
+        output->buffer->data[0] = input->value;
+        output->buffer->data[1] = RADSPA_SIGNAL_NONCONST;
     } else {
-        if(input->render_pass_id != render_pass_id) radspa_host_request_buffer_render(input->buffer);
-        if(input->buffer[1] == -32768){
-            memcpy(output->buffer, input->buffer, sizeof(int16_t) * 2);
+        radspa_render_signal(input, render_pass_id);
+        if(input->buffer->data[1] == RADSPA_SIGNAL_NONCONST){
+            memcpy(&output->buffer->data, &input->buffer->data, sizeof(int16_t) * 2);
         } else {
-            memcpy(output->buffer, input->buffer, sizeof(int16_t) * buffer_len);
+            memcpy(&output->buffer->data, &input->buffer->data, sizeof(int16_t) * RADSPA_BUFFER_LEN);
         }
     }
 }
@@ -125,18 +124,18 @@ inline void radspa_signal_set_value_check_const(radspa_signal_t * sig, int16_t i
     }
     val = radspa_clip(val);
     if(index == 0){
-        sig->buffer[0] = val;
+        sig->buffer->data[0] = val;
     } else if(index == 1){
-        if(val == sig->buffer[0]) sig->buffer[1] = -32768;
+        if(val == sig->buffer->data[0]) sig->buffer->data[1] = RADSPA_SIGNAL_NONCONST;
     } else {
-        if((sig->buffer[1] == -32768) && (val != sig->buffer[0])) sig->buffer[1] = sig->buffer[0];
-        sig->buffer[index] = val;
+        if((sig->buffer->data[1] == -32768) && (val != sig->buffer->data[0])) sig->buffer->data[1] = sig->buffer->data[0];
+        sig->buffer->data[index] = val;
     }
 }
 
 inline int16_t radspa_signal_set_value_check_const_result(radspa_signal_t * sig){
     if(sig->buffer != NULL){
-        if(sig->buffer[1] == -32768) return sig->buffer[0];
+        if(sig->buffer->data[1] == RADSPA_SIGNAL_NONCONST) return sig->buffer->data[0];
         return RADSPA_SIGNAL_NONCONST;
     }
     return sig->value;
@@ -146,8 +145,8 @@ inline void radspa_signal_set_const_value(radspa_signal_t * sig, int32_t val){
     if(sig->buffer == NULL){
         sig->value = radspa_clip(val);
     } else {
-        sig->buffer[0] = radspa_clip(val);
-        sig->buffer[1] = -32768;
+        sig->buffer->data[0] = radspa_clip(val);
+        sig->buffer->data[1] = RADSPA_SIGNAL_NONCONST;
     }
 }
 
@@ -157,29 +156,26 @@ inline void radspa_signal_set_values(radspa_signal_t * sig, uint16_t start, uint
     } else {
         val = radspa_clip(val);
         for(uint16_t i = start; i < stop; i++){
-            sig->buffer[i] = val;
+            sig->buffer->data[i] = val;
         }
     }
 }
 
 inline int16_t radspa_signal_get_const_value(radspa_signal_t * sig, uint32_t render_pass_id){
     if(sig->buffer != NULL){
-        if(sig->render_pass_id != render_pass_id){
-            radspa_host_request_buffer_render(sig->buffer);
-            sig->render_pass_id = render_pass_id;
-        }
-        if(sig->buffer[1] == -32768) return sig->buffer[0];
+        radspa_render_signal(sig, render_pass_id);
+        if(sig->buffer->data[1] == RADSPA_SIGNAL_NONCONST) return sig->buffer->data[0];
         return RADSPA_SIGNAL_NONCONST;
     }
     return sig->value;
 }
 
-inline int16_t radspa_trigger_get_const(radspa_signal_t * sig, int16_t * hist, uint16_t * index, uint16_t num_samples, uint32_t render_pass_id){
+inline int16_t radspa_trigger_get_const(radspa_signal_t * sig, int16_t * hist, uint16_t * index, uint32_t render_pass_id){
     (* index) = 0;
     int16_t ret_const = radspa_signal_get_const_value(sig, render_pass_id);
     if(ret_const != RADSPA_SIGNAL_NONCONST) return radspa_trigger_get(ret_const, hist);
     int16_t ret = 0;
-    for(uint16_t i = 0; i< num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         int16_t tmp = radspa_trigger_get(radspa_signal_get_value(sig, i, render_pass_id), hist);
         if(tmp){
             ret = tmp;
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.c b/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.c
index 8d6c2da6787e8b1579c73804a5883602760e950f..ce57ab5cd07d42a1cbe515e7166960813e277a67 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.c
@@ -21,7 +21,7 @@ radspa_descriptor_t ampliverter_desc = {
 #define AMPLIVERTER_GAIN 2
 #define AMPLIVERTER_BIAS 3
 
-void ampliverter_run(radspa_t * ampliverter, uint16_t num_samples, uint32_t render_pass_id){
+void ampliverter_run(radspa_t * ampliverter, uint32_t render_pass_id){
     // step 1: get signal pointers. since these are stored in a linked list this is a rather
     // slow operation and should ideally be only be done once per call.
     // if no signals with output hint are being read the run function may exit early.
@@ -33,7 +33,7 @@ void ampliverter_run(radspa_t * ampliverter, uint16_t num_samples, uint32_t rend
     
 
     static int16_t ret = 0;
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         // step 2: render the outputs. most of the time a simple for loop will be fine.
         // using {*radspa_signal_t}->get_value is required to automatically switch between
         // static values and streamed data at various sample rates, don't access the data directly
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.h b/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.h
index 4056316a25f9be994c20ebeb9ffd423ee3c03597..08e97c6319cdfd8f1b074a89d3088eff2e594e55 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.h
@@ -4,5 +4,5 @@
 
 extern radspa_descriptor_t ampliverter_desc;
 radspa_t * ampliverter_create(uint32_t init_var);
-void ampliverter_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void ampliverter_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/buffer.c b/components/bl00mbox/radspa/standard_plugin_lib/buffer.c
index 7672bf30bf5b03469242bb547d14711b468ef824..c4a372cc36dbcb8477e5c0b9d11f5b010df68295 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/buffer.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/buffer.c
@@ -8,7 +8,7 @@ radspa_descriptor_t buffer_desc = {
     .destroy_plugin_instance = radspa_standard_plugin_destroy
 };
 
-void buffer_run(radspa_t * buffer, uint16_t num_samples, uint32_t render_pass_id){
+void buffer_run(radspa_t * buffer, uint32_t render_pass_id){
     // note: this could be more lightweight by simply forwarding the buffer,
     // however at this point the radspa protocol has no built-in flag for this.
     // a host may still choose to simply do so to save CPU.
@@ -17,7 +17,7 @@ void buffer_run(radspa_t * buffer, uint16_t num_samples, uint32_t render_pass_id
     // (running a single channel mixer).
     radspa_signal_t * output = radspa_signal_get_by_index(buffer, 0);
     radspa_signal_t * input = radspa_signal_get_by_index(buffer, 1);
-    radspa_signal_copy(input, output, num_samples, render_pass_id);
+    radspa_signal_copy(input, output, render_pass_id);
 }
 
 radspa_t * buffer_create(uint32_t init_var){
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/buffer.h b/components/bl00mbox/radspa/standard_plugin_lib/buffer.h
index 18b5344f3a7e065c35bcfb63180af58dba500b59..7c16f1bcb3882d3caf6f1ef14f31155d5801a6dd 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/buffer.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/buffer.h
@@ -4,4 +4,4 @@
 
 extern radspa_descriptor_t buffer_desc;
 radspa_t * buffer_create(uint32_t init_var);
-void buffer_run(radspa_t * buffer, uint16_t num_samples, uint32_t render_pass_id);
+void buffer_run(radspa_t * buffer, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/delay.c b/components/bl00mbox/radspa/standard_plugin_lib/delay.c
index f6521569bb2af7190f700550b0214dca7d1d09a8..05d68b9df262455f3bd3c322cf619b9a51f5a534 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/delay.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/delay.c
@@ -19,7 +19,7 @@ radspa_descriptor_t delay_desc = {
 #define DELAY_DRY_VOL 5
 #define DELAY_REC_VOL 6
 
-void delay_run(radspa_t * delay, uint16_t num_samples, uint32_t render_pass_id){
+void delay_run(radspa_t * delay, uint32_t render_pass_id){
     radspa_signal_t * output_sig = radspa_signal_get_by_index(delay, DELAY_OUTPUT);
     delay_data_t * data = delay->plugin_data;
     int16_t * buf = delay->plugin_table;
@@ -49,7 +49,7 @@ void delay_run(radspa_t * delay, uint16_t num_samples, uint32_t render_pass_id){
     int16_t rec_vol = radspa_signal_get_value(rec_vol_sig, 0, render_pass_id);
 
     
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
 
         data->write_head_position++;
         while(data->write_head_position >= buffer_size) data->write_head_position -= buffer_size; // maybe faster than %
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/delay.h b/components/bl00mbox/radspa/standard_plugin_lib/delay.h
index 6bd82790eb37fb58d15255995c4df13ed86a3fe3..8d3a7e43d1efa1c165eb1df39f254a1b8c13c5fb 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/delay.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/delay.h
@@ -11,5 +11,5 @@ typedef struct {
 
 extern radspa_descriptor_t delay_desc;
 radspa_t * delay_create(uint32_t init_var);
-void delay_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void delay_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/distortion.c b/components/bl00mbox/radspa/standard_plugin_lib/distortion.c
index e5a9cc68b7253e6db8cf1a0e4678752024f480e0..adb8f05934f1cf43736e0870a45662d30591f0fc 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/distortion.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/distortion.c
@@ -23,7 +23,7 @@ static inline int32_t distort(int32_t input, int16_t * dist, uint8_t lerp_glitch
     return ret >> lerp_glitch;
 }
 
-void distortion_run(radspa_t * distortion, uint16_t num_samples, uint32_t render_pass_id){
+void distortion_run(radspa_t * distortion, 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;
@@ -33,7 +33,7 @@ void distortion_run(radspa_t * distortion, uint16_t num_samples, uint32_t render
     if(input != RADSPA_SIGNAL_NONCONST){
         radspa_signal_set_const_value(output_sig, distort(input, dist, lerp_glitch));
     } else {
-        for(uint16_t i = 0; i < num_samples; i++){
+        for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
             int32_t input = radspa_signal_get_value(input_sig, i, render_pass_id);
             radspa_signal_set_value(output_sig, i, distort(input, dist, lerp_glitch));
         }
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/distortion.h b/components/bl00mbox/radspa/standard_plugin_lib/distortion.h
index 87455bad8d1e3a903d0bea099254ec9a6c3029ab..0dfe617f520584528c987f42a26a7941cbb64f87 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/distortion.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/distortion.h
@@ -4,5 +4,5 @@
 
 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);
+void distortion_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.c b/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.c
index f907e2d9e578198c116f26339b63e95fe1c294b5..43d989a7a13bf0d3f3e053a82efcda6ab794e382 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.c
@@ -25,34 +25,32 @@ radspa_descriptor_t env_adsr_desc = {
 #define ENV_ADSR_PHASE_SUSTAIN 3
 #define ENV_ADSR_PHASE_RELEASE 4
 
-#define SAMPLE_RATE_SORRY 48000
-
-static inline uint32_t env_adsr_time_ms_to_val_rise(int16_t time_ms, uint32_t val, uint16_t num_samples){
+static inline uint32_t env_adsr_time_ms_to_val_rise(int16_t time_ms, uint32_t val){
     if(!time_ms) return UINT32_MAX;
     if(time_ms < 0) time_ms = -time_ms;
-    uint32_t div = time_ms * ((SAMPLE_RATE_SORRY)/1000);
+    uint32_t div = time_ms * ((RADSPA_SAMPLE_RATE)/1000);
     uint32_t input =  val/div;
-    if(((uint64_t) input * num_samples) >> 32){
+    if(((uint64_t) input * RADSPA_BUFFER_LEN) >> 32){
         return UINT32_MAX; // sat
     } else {
-        return input * num_samples;
+        return input * RADSPA_BUFFER_LEN;
     }
 }
 
-static inline void update_attack_coeffs(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){
+static inline void update_attack_coeffs(radspa_t * env_adsr, uint32_t render_pass_id){
     env_adsr_data_t * data = env_adsr->plugin_data;
     int16_t attack = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_ATTACK], 0, render_pass_id);
-    if((data->attack_prev_ms != attack) || (data->num_samples_prev != num_samples)){
-        data->attack_raw = env_adsr_time_ms_to_val_rise(attack, UINT32_MAX, num_samples);
+    if(data->attack_prev_ms != attack){
+        data->attack_raw = env_adsr_time_ms_to_val_rise(attack, UINT32_MAX);
         data->attack_prev_ms = attack;
     }
 }
 
-static inline void update_release_coeffs(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){
+static inline void update_release_coeffs(radspa_t * env_adsr, uint32_t render_pass_id){
     env_adsr_data_t * data = env_adsr->plugin_data;
     int16_t release = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_RELEASE], 0, render_pass_id);
-    if((data->release_prev_ms != release) || (data->num_samples_prev != num_samples) || data->release_init_val_prev != data->release_init_val){
-        data->release_raw = env_adsr_time_ms_to_val_rise(release, data->release_init_val, num_samples);
+    if((data->release_prev_ms != release) || (data->release_init_val_prev != data->release_init_val)){
+        data->release_raw = env_adsr_time_ms_to_val_rise(release, data->release_init_val);
         data->release_prev_ms = release;
         if(data->release_init_val_prev != data->release_init_val){;
             uint8_t phase = ENV_ADSR_PHASE_RELEASE;
@@ -68,19 +66,19 @@ static inline void update_release_coeffs(radspa_t * env_adsr, uint16_t num_sampl
     }
 }
 
-static inline void update_sustain_coeffs(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){
+static inline void update_sustain_coeffs(radspa_t * env_adsr, uint32_t render_pass_id){
     env_adsr_data_t * data = env_adsr->plugin_data;
     int16_t sustain = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_SUSTAIN], 0, render_pass_id);
     sustain = sustain < 0 ? -sustain : sustain;
     data->sustain = ((uint32_t) sustain) << 17UL;
 }
 
-static inline void update_decay_coeffs(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){
-    update_sustain_coeffs(env_adsr, num_samples, render_pass_id);
+static inline void update_decay_coeffs(radspa_t * env_adsr, uint32_t render_pass_id){
+    update_sustain_coeffs(env_adsr, render_pass_id);
     env_adsr_data_t * data = env_adsr->plugin_data;
     int32_t decay = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_DECAY], 0, render_pass_id);
-    if((data->decay_prev_ms != decay) || (data->sustain_prev != data->sustain) || (data->num_samples_prev != num_samples)){
-        data->decay_raw = env_adsr_time_ms_to_val_rise(decay, UINT32_MAX - data->sustain, num_samples);
+    if((data->decay_prev_ms != decay) || (data->sustain_prev != data->sustain)){
+        data->decay_raw = env_adsr_time_ms_to_val_rise(decay, UINT32_MAX - data->sustain);
         data->decay_prev_ms = decay;
         if(data->sustain_prev != data->sustain){
             uint8_t phase = ENV_ADSR_PHASE_DECAY;
@@ -97,11 +95,11 @@ static inline void update_decay_coeffs(radspa_t * env_adsr, uint16_t num_samples
 }
 
 
-void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){
+void env_adsr_run(radspa_t * env_adsr, uint32_t render_pass_id){
     env_adsr_data_t * data = env_adsr->plugin_data;
 
     uint16_t throwaway;
-    int16_t vel = radspa_trigger_get_const(&env_adsr->signals[ENV_ADSR_TRIGGER], &data->trigger_prev, &throwaway, num_samples, render_pass_id);
+    int16_t vel = radspa_trigger_get_const(&env_adsr->signals[ENV_ADSR_TRIGGER], &data->trigger_prev, &throwaway, render_pass_id);
     if(vel > 0 ){ // start
         data->env_phase = ENV_ADSR_PHASE_ATTACK;
         data->velocity = vel;
@@ -118,7 +116,7 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas
             data->env_counter = 0;;
             break;
         case ENV_ADSR_PHASE_ATTACK:
-            update_attack_coeffs(env_adsr, num_samples, render_pass_id);
+            update_attack_coeffs(env_adsr, render_pass_id);
             tmp = data->env_counter + data->attack_raw;
             if(tmp < data->env_counter){ // overflow
                 tmp = ~((uint32_t) 0); // max out
@@ -127,7 +125,7 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas
             data->env_counter = tmp;
             break;
         case ENV_ADSR_PHASE_DECAY:
-            update_decay_coeffs(env_adsr, num_samples, render_pass_id);
+            update_decay_coeffs(env_adsr, render_pass_id);
             tmp = data->env_counter - data->decay_raw;
             if(tmp > data->env_counter){ // underflow
                 tmp = 0; //bottom out
@@ -142,12 +140,12 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas
 
             break;
         case ENV_ADSR_PHASE_SUSTAIN:
-            update_sustain_coeffs(env_adsr, num_samples, render_pass_id);
+            update_sustain_coeffs(env_adsr, render_pass_id);
             if(data->sustain == 0) data->env_phase = ENV_ADSR_PHASE_OFF;
             data->env_counter = data->sustain;
             break;
         case ENV_ADSR_PHASE_RELEASE:
-            update_release_coeffs(env_adsr, num_samples, render_pass_id);
+            update_release_coeffs(env_adsr, render_pass_id);
             tmp = data->env_counter - data->release_raw;
             if(tmp > data->env_counter){ // underflow
                 tmp = 0; //bottom out
@@ -156,7 +154,6 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas
             data->env_counter = tmp;
             break;
     }
-    data->num_samples_prev = num_samples;
 
     int32_t gain = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_GAIN], 0, render_pass_id);
 
@@ -185,8 +182,8 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas
 
     int16_t ret = radspa_signal_get_const_value(&env_adsr->signals[ENV_ADSR_INPUT], render_pass_id);
     if(ret == RADSPA_SIGNAL_NONCONST){
-        int32_t env_slope = ((env - data->env_prev) << 15) / num_samples;
-        for(uint16_t i = 0; i < num_samples; i++){
+        int32_t env_slope = ((env - data->env_prev) << 15) / RADSPA_BUFFER_LEN;
+        for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
             ret = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_INPUT], i, render_pass_id);
             int32_t env_inter = data->env_prev + ((env_slope * i)>>15);
             ret = (ret * env_inter) >> 12;
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.h b/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.h
index 513afa385589cc262499d26bdfe48ac8960d38ac..d096cf2868a6e24566b25125eb53a3ee5c3ff305 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.h
@@ -8,7 +8,6 @@ typedef struct {
     uint32_t    square_mult[5];
     uint32_t    square_min[5];
     int16_t     trigger_prev;
-    uint16_t    num_samples_prev;
     int32_t     env_prev;
     uint8_t     env_phase;
     int16_t     attack_prev_ms;
@@ -25,5 +24,5 @@ typedef struct {
 
 extern radspa_descriptor_t env_adsr_desc;
 radspa_t * env_adsr_create(uint32_t init_var);
-void env_adsr_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void env_adsr_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/filter.c b/components/bl00mbox/radspa/standard_plugin_lib/filter.c
index b821b827b119e33b63148359699b47421815016e..69004dd0b7e49f1b30e8b61f30a6cdb00a140609 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/filter.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/filter.c
@@ -118,7 +118,7 @@ static inline void get_coeffs(filter_data_t * data, int16_t pitch, int16_t reso,
     data->mode_prev = mode;
 }
 
-void filter_run(radspa_t * filter, uint16_t num_samples, uint32_t render_pass_id){
+void filter_run(radspa_t * filter, uint32_t render_pass_id){
     filter_data_t * data = filter->plugin_data;
 
     radspa_signal_t * output_sig = radspa_signal_get_by_index(filter, FILTER_OUTPUT);
@@ -154,9 +154,9 @@ void filter_run(radspa_t * filter, uint16_t num_samples, uint32_t render_pass_id
         return;
     }
 
-    int16_t out[num_samples];
+    int16_t out[RADSPA_BUFFER_LEN];
 
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         if(!(i & (RADSPA_EVENT_MASK))){
             if(always_update_coeffs){
                 if(pitch_const == RADSPA_SIGNAL_NONCONST) pitch = radspa_signal_get_value(pitch_sig, i, render_pass_id);
@@ -207,11 +207,11 @@ void filter_run(radspa_t * filter, uint16_t num_samples, uint32_t render_pass_id
     }
 
     if(input_const == RADSPA_SIGNAL_NONCONST){
-        for(uint16_t i = 0; i < num_samples; i++){
+        for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
             radspa_signal_set_value(output_sig, i, out[i]);
         }
     } else if(data->const_output == RADSPA_SIGNAL_NONCONST){
-        for(uint16_t i = 0; i < num_samples; i++){
+        for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
             radspa_signal_set_value_check_const(output_sig, i, out[i]);
         }
         data->const_output = radspa_signal_set_value_check_const_result(output_sig);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/filter.h b/components/bl00mbox/radspa/standard_plugin_lib/filter.h
index c8a2bd1943a1467b9ef33730f1317a1321e01b1c..0b6857fd850629713b8180221ef67fd55b2984ad 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/filter.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/filter.h
@@ -21,4 +21,4 @@ typedef struct {
 
 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);
+void filter_run(radspa_t * osc, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/flanger.c b/components/bl00mbox/radspa/standard_plugin_lib/flanger.c
index 40dee9c70e54dbd126bde5fb6c5a4557b896e851..f735446a69cbc99408ccc4d228b1a1e057761e31 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/flanger.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/flanger.c
@@ -48,7 +48,7 @@ static inline int32_t fixed_point_list_access(int32_t * buf, int32_t fp_index, u
  * delay_reso_int = sct_to_rel_freq(offset - (delay_time_ms * 2400)/decay_ms_per_6dB)
  */
 
-void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_id){
+void flanger_run(radspa_t * flanger, uint32_t render_pass_id){
     radspa_signal_t * output_sig = radspa_signal_get_by_index(flanger, FLANGER_OUTPUT);
     if(output_sig->buffer == NULL) return;
     flanger_data_t * data = flanger->plugin_data;
@@ -93,7 +93,7 @@ void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_
     }
     data->manual_prev = manual;
 
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         int32_t dry = radspa_signal_get_value(input_sig, i, render_pass_id);
 
         data->write_head_position++;
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/flanger.h b/components/bl00mbox/radspa/standard_plugin_lib/flanger.h
index 717037a0baa569090818a0aa460f40214c43e886..5f37c527c95b85e76f1feb99d620c43658f526d8 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/flanger.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/flanger.h
@@ -13,5 +13,5 @@ typedef struct {
 
 extern radspa_descriptor_t flanger_desc;
 radspa_t * flanger_create(uint32_t init_var);
-void flanger_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void flanger_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/lowpass.c b/components/bl00mbox/radspa/standard_plugin_lib/lowpass.c
index d59421b2584cf609178e742c9f493d3c454c0b97..82b0aba453477920f9d0404445d1ee97459c4260 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/lowpass.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/lowpass.c
@@ -51,7 +51,7 @@ static void set_lowpass_coeffs(lowpass_data_t * data, int32_t freq, int32_t mq){
 }
 
 
-void lowpass_run(radspa_t * lowpass, uint16_t num_samples, uint32_t render_pass_id){
+void lowpass_run(radspa_t * lowpass, 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;
@@ -60,7 +60,7 @@ void lowpass_run(radspa_t * lowpass, uint16_t num_samples, uint32_t render_pass_
     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);
 
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         int16_t input = radspa_signal_get_value(input_sig, i, render_pass_id);
         int32_t freq = radspa_signal_get_value(freq_sig, i, render_pass_id);
         int16_t q = radspa_signal_get_value(q_sig, i, render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/lowpass.h b/components/bl00mbox/radspa/standard_plugin_lib/lowpass.h
index 6bac0a5d127af80dc98fcc245aae21961f102f8a..a37975b19b98da72024d78fdf318aaf8add95bc4 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/lowpass.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/lowpass.h
@@ -15,4 +15,4 @@ typedef struct {
 
 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);
+void lowpass_run(radspa_t * osc, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/mixer.c b/components/bl00mbox/radspa/standard_plugin_lib/mixer.c
index 91cb1e6910d3381ae92a43eb06002945bc92508f..d246ba5f6627f17075cfbd204d439d6ef89fc615 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/mixer.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/mixer.c
@@ -8,10 +8,10 @@ radspa_descriptor_t mixer_desc = {
     .destroy_plugin_instance = radspa_standard_plugin_destroy
 };
 
-void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
+void mixer_run(radspa_t * mixer, uint32_t render_pass_id){
     mixer_data_t * data = mixer->plugin_data;
 
-    int32_t ret[num_samples];
+    int32_t ret[RADSPA_BUFFER_LEN];
     bool ret_init = false;
     bool ret_const_init = false;
 
@@ -30,7 +30,7 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
         if(!input_const) continue;
         if((input_const != RADSPA_SIGNAL_NONCONST) && (input_gain_const != RADSPA_SIGNAL_NONCONST)){
             if(ret_init){
-                for(uint16_t i = 0; i < num_samples; i++){
+                for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                     ret[i] += (input_gain_const * input_const) >> 12;
                 }
             } else if(ret_const_init){
@@ -41,41 +41,41 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
             }
         } else {
             if(ret_const_init){
-                for(uint16_t i = 0; i < num_samples; i++){
+                for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                     ret[i] = ret[0];
                 }
                 ret_const_init = false;
             }
             if(input_gain_const == RADSPA_SIGNAL_VAL_UNITY_GAIN){
                 if(ret_init){
-                    for(uint16_t i = 0; i < num_samples; i++){
+                    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         ret[i] += radspa_signal_get_value(input_sigs[j], i, render_pass_id);
                     }
                 } else {
-                    for(uint16_t i = 0; i < num_samples; i++){
+                    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         ret[i] = radspa_signal_get_value(input_sigs[j], i, render_pass_id);
                     }
                     ret_init = true;
                 }
             } else if(input_gain_const == -RADSPA_SIGNAL_VAL_UNITY_GAIN){
                 if(ret_init){
-                    for(uint16_t i = 0; i < num_samples; i++){
+                    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         ret[i] -= radspa_signal_get_value(input_sigs[j], i, render_pass_id);
                     }
                 } else {
-                    for(uint16_t i = 0; i < num_samples; i++){
+                    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         ret[i] = -radspa_signal_get_value(input_sigs[j], i, render_pass_id);
                     }
                     ret_init = true;
                 }
             } else if(input_gain_const == RADSPA_SIGNAL_NONCONST){
                 if(ret_init){
-                    for(uint16_t i = 0; i < num_samples; i++){
+                    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         int32_t input_gain = radspa_signal_get_value(input_gain_sigs[j], i, render_pass_id);
                         ret[i] += (input_gain * radspa_signal_get_value(input_sigs[j], i, render_pass_id)) >> 12;
                     }
                 } else {
-                    for(uint16_t i = 0; i < num_samples; i++){
+                    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         int32_t input_gain = radspa_signal_get_value(input_gain_sigs[j], i, render_pass_id);
                         ret[i] = (input_gain * radspa_signal_get_value(input_sigs[j], i, render_pass_id)) >> 12;
                     }
@@ -83,11 +83,11 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
                 }
             } else {
                 if(ret_init){
-                    for(uint16_t i = 0; i < num_samples; i++){
+                    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         ret[i] += (input_gain_const * radspa_signal_get_value(input_sigs[j], i, render_pass_id)) >> 12;
                     }
                 } else {
-                    for(uint16_t i = 0; i < num_samples; i++){
+                    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         ret[i] = (input_gain_const * radspa_signal_get_value(input_sigs[j], i, render_pass_id)) >> 12;
                     }
                     ret_init = true;
@@ -103,7 +103,7 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
 
     if(block_dc){
         if(ret_init){
-            for(uint16_t i = 0; i < num_samples; i++){
+            for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                 bool invert = data->dc < 0;
                 if(invert) data->dc = -data->dc;
                 data->dc = ((uint64_t) data->dc * (((1UL<<12) - 1)<<20)) >> 32;
@@ -113,7 +113,7 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
             }
         } else {
             if(data->dc){
-                for(uint16_t i = 0; i < num_samples; i++){
+                for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                     bool invert = data->dc < 0;
                     if(invert) data->dc = -data->dc;
                     data->dc = ((uint64_t) data->dc * (((1UL<<12) - 1)<<20)) >> 32;
@@ -130,17 +130,17 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
         int32_t gain = gain_const;
         if(gain_const != RADSPA_SIGNAL_VAL_UNITY_GAIN){
             if(gain_const == RADSPA_SIGNAL_NONCONST){
-                for(uint16_t i = 0; i < num_samples; i++){
+                for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         gain = radspa_signal_get_value(gain_sig, i, render_pass_id);
                         ret[i] = (ret[i] * gain) >> 12;
                 }
             } else {
-                for(uint16_t i = 0; i < num_samples; i++){
+                for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                     ret[i] = (ret[i] * gain) >> 12;
                 }
             }
         }
-        for(uint16_t i = 0; i < num_samples; i++){
+        for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
             radspa_signal_set_value(output_sig, i, ret[i]);
         }
     } else if(ret_const_init){
@@ -148,7 +148,7 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
         int32_t gain = gain_const;
         if(gain_const != RADSPA_SIGNAL_VAL_UNITY_GAIN){
             if(gain_const == RADSPA_SIGNAL_NONCONST){
-                for(uint16_t i = 0; i < num_samples; i++){
+                for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                         gain = radspa_signal_get_value(gain_sig, i, render_pass_id);
                         ret[i] = (ret[0] * gain) >> 12;
                 }
@@ -157,7 +157,7 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
             }
         }
         if(gain_const == RADSPA_SIGNAL_NONCONST){
-            for(uint16_t i = 0; i < num_samples; i++){
+            for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                 radspa_signal_set_value(output_sig, i, ret[i]);
             }
         } else {
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/mixer.h b/components/bl00mbox/radspa/standard_plugin_lib/mixer.h
index 14d9f01fbf265c7e6821bb653cc27db2974b3637..99911a2d2bf57637b4771d0ecaf9c22ecce7d6ea 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/mixer.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/mixer.h
@@ -9,5 +9,5 @@ typedef struct {
 
 extern radspa_descriptor_t mixer_desc;
 radspa_t * mixer_create(uint32_t init_var);
-void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id);
+void mixer_run(radspa_t * mixer, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/multipitch.c b/components/bl00mbox/radspa/standard_plugin_lib/multipitch.c
index 30a772a2e022b59ace289d870958e26b042c0177..6c5ab5c1836aa90debc3d1dd9304541657ec8370 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/multipitch.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/multipitch.c
@@ -41,12 +41,12 @@ static inline int32_t pitch_limit(int32_t pitch, int32_t min, int32_t max){
     return pitch;
 }
 
-void multipitch_run(radspa_t * multipitch, uint16_t num_samples, uint32_t render_pass_id){
+void multipitch_run(radspa_t * multipitch, uint32_t render_pass_id){
     uint8_t num_outputs = (multipitch->len_signals - (NUM_SIGNALS))/2;
     multipitch_data_t * data = multipitch->plugin_data;
 
     uint16_t i;
-    int16_t trigger_in = radspa_trigger_get_const(&multipitch->signals[TRIGGER_IN], &data->trigger_in_prev, &i, num_samples, render_pass_id);
+    int16_t trigger_in = radspa_trigger_get_const(&multipitch->signals[TRIGGER_IN], &data->trigger_in_prev, &i, render_pass_id);
     if(trigger_in > 0) radspa_trigger_start(trigger_in, &(data->trigger_thru_prev));
     if(trigger_in < 0) radspa_trigger_stop(&(data->trigger_thru_prev));
     radspa_signal_set_const_value(&multipitch->signals[TRIGGER_THRU], data->trigger_thru_prev);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/multipitch.h b/components/bl00mbox/radspa/standard_plugin_lib/multipitch.h
index 7e830c8024688eb2f3895353860a938b125e1add..7459a573a94a53f2cfa0dca777a07e4cf83fda67 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/multipitch.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/multipitch.h
@@ -4,5 +4,5 @@
 
 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);
+void multipitch_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/noise.c b/components/bl00mbox/radspa/standard_plugin_lib/noise.c
index 3c9ecfb061ea82ca09b6b0311b853bf96227b11b..b2212ec3c1c80291958f8bd0e39ad3cfe5ac2ac0 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/noise.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/noise.c
@@ -13,14 +13,14 @@ radspa_descriptor_t noise_desc = {
 #define NOISE_OUTPUT 0
 #define NOISE_SPEED 1
 
-void noise_run(radspa_t * noise, uint16_t num_samples, uint32_t render_pass_id){
+void noise_run(radspa_t * noise, uint32_t render_pass_id){
     radspa_signal_t * output_sig = radspa_signal_get_by_index(noise, NOISE_OUTPUT);
     radspa_signal_t * speed_sig = radspa_signal_get_by_index(noise, NOISE_SPEED);
 
     if(radspa_signal_get_value(speed_sig, 0, render_pass_id) < 0){
         radspa_signal_set_const_value(output_sig, radspa_random());
     } else {
-        for(uint16_t i = 0; i < num_samples; i++){
+        for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
             radspa_signal_set_value(output_sig, i, radspa_random());
         }
     }
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/noise.h b/components/bl00mbox/radspa/standard_plugin_lib/noise.h
index 709f71bd9866f99faf4a10fcb0d44e1585ef1a69..4d578287fcbb2741ef30e8ad8cc33ff4a9d2adee 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/noise.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/noise.h
@@ -4,5 +4,5 @@
 
 extern radspa_descriptor_t noise_desc;
 radspa_t * noise_create(uint32_t init_var);
-void noise_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void noise_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.c b/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.c
index ed73a4a2d7819370e208faf486f45f64b5f245ee..a9bf3641a638639f8935cf42beb6671d9759f53e 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.c
@@ -25,7 +25,7 @@ radspa_t * noise_burst_create(uint32_t init_var){
 
 #define SAMPLE_RATE_SORRY 48000
 
-void noise_burst_run(radspa_t * noise_burst, uint16_t num_samples, uint32_t render_pass_id){
+void noise_burst_run(radspa_t * noise_burst, uint32_t render_pass_id){
     noise_burst_data_t * plugin_data = noise_burst->plugin_data;
     radspa_signal_t * output_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_OUTPUT);
     radspa_signal_t * trigger_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_TRIGGER);
@@ -47,7 +47,7 @@ void noise_burst_run(radspa_t * noise_burst, uint16_t num_samples, uint32_t rend
 
     int16_t out = plugin_data->last_out;
 
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         if(!(i && trigger_const)){
             if(!trigger_const){
                 trigger = radspa_signal_get_value(trigger_sig, i, render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.h b/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.h
index 6653858f69ebf767978518fc55bd5e5d5d3c543c..3231f9d9aab75dfabc35f83b90b83d013b82e967 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.h
@@ -12,5 +12,5 @@ typedef struct {
 
 extern radspa_descriptor_t noise_burst_desc;
 radspa_t * noise_burst_create(uint32_t init_var);
-void noise_burst_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void noise_burst_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc.c b/components/bl00mbox/radspa/standard_plugin_lib/osc.c
index 7212d20cb771e87abb1a28c5a8f183a8cb81199e..f852b814d7b6ae4a4bb8305af3c80c6cc8feb27f 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/osc.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/osc.c
@@ -60,7 +60,7 @@ radspa_descriptor_t osc_desc = {
 
 #define OSC_MEGASWITCH \
         case 0: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 RINGMOD_READ \
                 FM_READ \
                 OSCILLATE \
@@ -70,7 +70,7 @@ radspa_descriptor_t osc_desc = {
             } \
             break; \
         case 1: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 FM_READ \
                 OSCILLATE \
                 SYNC_IN_READ \
@@ -79,7 +79,7 @@ radspa_descriptor_t osc_desc = {
             } \
             break; \
         case 2: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 RINGMOD_READ \
                 OSCILLATE \
                 OUT_WRITE \
@@ -87,14 +87,14 @@ radspa_descriptor_t osc_desc = {
             } \
             break; \
         case 3: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 OSCILLATE \
                 OUT_WRITE \
                 SYNC_OUT_WRITE \
             } \
             break; \
         case 4: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 RINGMOD_READ \
                 FM_READ \
                 OSCILLATE \
@@ -103,7 +103,7 @@ radspa_descriptor_t osc_desc = {
             } \
             break; \
         case 5: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 FM_READ \
                 OSCILLATE \
                 SYNC_IN_READ \
@@ -111,20 +111,20 @@ radspa_descriptor_t osc_desc = {
             } \
             break; \
         case 6: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 RINGMOD_READ \
                 OSCILLATE \
                 SYNC_OUT_WRITE \
             } \
             break; \
         case 7: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 OSCILLATE \
                 SYNC_OUT_WRITE \
             } \
             break; \
         case 8: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 RINGMOD_READ \
                 FM_READ \
                 OSCILLATE \
@@ -133,7 +133,7 @@ radspa_descriptor_t osc_desc = {
             } \
             break; \
         case 9: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 FM_READ \
                 OSCILLATE \
                 SYNC_IN_READ \
@@ -141,14 +141,14 @@ radspa_descriptor_t osc_desc = {
             } \
             break; \
         case 10: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 RINGMOD_READ \
                 OSCILLATE \
                 OUT_WRITE \
             } \
             break; \
         case 11: \
-            for(; i < num_samples; i++){ \
+            for(; i < RADSPA_BUFFER_LEN; i++){ \
                 OSCILLATE \
                 OUT_WRITE \
             } \
@@ -356,7 +356,7 @@ static inline int16_t apply_waveform(osc_data_t * data, int16_t input, int32_t i
 
 #define ANTIALIASING_INDEX 64
 
-void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id){
+void osc_run(radspa_t * osc, uint32_t render_pass_id){
     osc_data_t * data = osc->plugin_data;
     int8_t * table = (int8_t * ) osc->plugin_table;
     radspa_signal_t * speed_sig = radspa_signal_get_by_index(osc, OSC_SPEED);
@@ -394,7 +394,7 @@ void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id){
 
         OSCILLATE
 
-        if(lfo) data->counter += incr * ((num_samples - 1) << 3);
+        if(lfo) data->counter += incr * ((RADSPA_BUFFER_LEN - 1) << 3);
 
         if(radspa_trigger_get(sync_in, &(data->sync_in)) > 0){
             SYNC_IN_APPLY
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc.h b/components/bl00mbox/radspa/standard_plugin_lib/osc.h
index e24e21b1a46e700becba96cdf7ef73a5cac400d7..f4b98072d374016b0313a60936c38d9a1eca874d 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/osc.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/osc.h
@@ -21,4 +21,4 @@ typedef struct {
 
 extern radspa_descriptor_t osc_desc;
 radspa_t * osc_create(uint32_t init_var);
-void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void osc_run(radspa_t * osc, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.c b/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.c
index 972fa17e844c8b412dd9de3541325f9f0226c583..9e06bc7219e0a7ef3602af490815279de091e7d0 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.c
@@ -77,7 +77,7 @@ static inline int16_t waveshaper(int32_t saw, int16_t shape){
     return tmp;
 }
 
-void osc_fm_run(radspa_t * osc_fm, uint16_t num_samples, uint32_t render_pass_id){
+void osc_fm_run(radspa_t * osc_fm, uint32_t render_pass_id){
     osc_fm_data_t * data = osc_fm->plugin_data;
 
     int16_t pitch_const = radspa_signal_get_const_value(data->pitch_sig, render_pass_id);
@@ -101,7 +101,7 @@ void osc_fm_run(radspa_t * osc_fm, uint16_t num_samples, uint32_t render_pass_id
 
     if(fm_thru_const) radspa_signal_set_const_value(data->fm_pitch_thru_sig, pitch_const + fm_pitch_offset_const - RADSPA_SIGNAL_VAL_SCT_A440);
 
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         if(pitch_const == -32768){
             pitch = radspa_signal_get_value(data->pitch_sig, i, render_pass_id);
             if(pitch != data->prev_pitch){
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.h b/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.h
index 720ac9a9a04430efdb0b190e56a42684b5921ce4..7cd03c60e61d1ede95571b9f1ff9c0fec4a3dec2 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.h
@@ -16,5 +16,5 @@ typedef struct {
 
 extern radspa_descriptor_t osc_fm_desc;
 radspa_t * osc_fm_create(uint32_t init_var);
-void osc_fm_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void osc_fm_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.c b/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.c
index 2c3a79c4d731655616808f224a481752023f47ad..fdfaef75b2e80e31edaea0142c83e50021218793 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.c
@@ -115,7 +115,7 @@ static void voice_stop(poly_squeeze_voice_t * voice){
     voice->trigger_out = tmp;
 } 
 
-void poly_squeeze_run(radspa_t * poly_squeeze, uint16_t num_samples, uint32_t render_pass_id){
+void poly_squeeze_run(radspa_t * poly_squeeze, uint32_t render_pass_id){
     poly_squeeze_data_t * data = poly_squeeze->plugin_data;
     poly_squeeze_note_t * notes = (void *) (&(data[1]));
     poly_squeeze_input_t * inputs = (void *) (&(notes[data->num_notes]));
@@ -124,7 +124,7 @@ void poly_squeeze_run(radspa_t * poly_squeeze, uint16_t num_samples, uint32_t re
     for(uint8_t j = 0; j < data->num_inputs; j++){
         uint16_t pitch_index;
         int16_t trigger_in = radspa_trigger_get_const(&poly_squeeze->signals[TRIGGER_INPUT + NUM_MPX*j],
-                                &inputs[j].trigger_in_hist, &pitch_index, num_samples, render_pass_id);
+                                &inputs[j].trigger_in_hist, &pitch_index, render_pass_id);
         notes[j].pitch = radspa_signal_get_value(&poly_squeeze->signals[PITCH_INPUT + NUM_MPX*j], pitch_index, render_pass_id);
         // should order events by pitch index some day maybe
         if(trigger_in > 0){
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.h b/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.h
index 40f954d3cfc25f6b34a17a19fc716eac477eccf9..7b90871c9658053c8c7c15e4aa28312d4e9e68ad 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.h
@@ -31,4 +31,4 @@ typedef struct {
 
 extern radspa_descriptor_t poly_squeeze_desc;
 radspa_t * poly_squeeze_create(uint32_t init_var);
-void poly_squeeze_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void poly_squeeze_run(radspa_t * osc, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c b/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c
index ff9c6da3c7cd34bfe36280693440a99cc06f864d..7ff83b17b44d7e438c499b4f47cc28f62d11edaa 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c
@@ -41,7 +41,7 @@ radspa_descriptor_t range_shifter_desc = {
     } \
 }
 
-void range_shifter_run(radspa_t * range_shifter, uint16_t num_samples, uint32_t render_pass_id){
+void range_shifter_run(radspa_t * range_shifter, uint32_t render_pass_id){
     radspa_signal_t * output_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_OUTPUT);
     radspa_signal_t * output_a_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_OUTPUT_A);
     radspa_signal_t * output_b_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_OUTPUT_B);
@@ -81,14 +81,14 @@ void range_shifter_run(radspa_t * range_shifter, uint16_t num_samples, uint32_t
             APPLY_GAIN
             radspa_signal_set_const_value(output_sig, ret);
         } else {
-            for(uint16_t i = 0; i < num_samples; i++){
+            for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
                 int32_t ret = radspa_signal_get_value(input_sig, i, render_pass_id);
                 APPLY_GAIN
                 radspa_signal_set_value(output_sig, i, ret);
             }
         }
     } else {
-        for(uint16_t i = 0; i < num_samples; i++){
+        for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
             uint16_t k = i;
             GET_GAIN
             if(!output_span){
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.h b/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.h
index 254099f8a2a0be8e175429c1be9fc07e20e55299..e24f0bb864804cf96771038a6556c6c685a4f137 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.h
@@ -4,4 +4,4 @@
 
 extern radspa_descriptor_t range_shifter_desc;
 radspa_t * range_shifter_create(uint32_t init_var);
-void range_shifter_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void range_shifter_run(radspa_t * osc, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/sampler.c b/components/bl00mbox/radspa/standard_plugin_lib/sampler.c
index 7fe4d65ba65114da0fa157b6b8f9a36dd52a6b85..02706cb1873d5e507f4b314ae6c6735fef861df5 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/sampler.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/sampler.c
@@ -32,7 +32,7 @@ radspa_descriptor_t sampler_desc = {
 #define STATUS_RECORD_NEW_EVENT 4
 #define BUFFER_OFFSET 11
 
-void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_id){
+void sampler_run(radspa_t * sampler, uint32_t render_pass_id){
     radspa_signal_t * output_sig = radspa_signal_get_by_index(sampler, SAMPLER_OUTPUT);
     radspa_signal_t * trigger_sig = radspa_signal_get_by_index(sampler, SAMPLER_TRIGGER);
     radspa_signal_t * rec_trigger_sig = radspa_signal_get_by_index(sampler, SAMPLER_REC_TRIGGER);
@@ -64,13 +64,14 @@ void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_
         buf32[SAMPLE_RATE/2] = 1;
     }
     uint32_t buffer_size = sampler->plugin_table_len - BUFFER_OFFSET;
-    uint64_t buffer_size_long = buffer_size * 48000;
+    uint64_t buffer_size_long = buffer_size * RADSPA_SAMPLE_RATE;
 
     if(sample_len >= buffer_size) sample_len = buffer_size - 1;
     if(sample_start >= buffer_size) sample_start = buffer_size - 1;
 
     bool output_mute = !data->playback_active;
     bool output_const = sample_rate < 100;
+    uint16_t num_samples = RADSPA_BUFFER_LEN;
     if(output_const){
         sample_rate *= num_samples;
         num_samples = 1;
@@ -93,7 +94,7 @@ void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_
         }
         if(data->rec_active){
             int16_t rec_in = radspa_signal_get_value(rec_in_sig, i, render_pass_id);
-            int32_t write_head_pos = (data->write_head_pos_long * 699) >> 25; // equiv to x/48000 (acc 0.008%)
+            int32_t write_head_pos = data->write_head_pos_long/RADSPA_SAMPLE_RATE;
             if(data->write_head_pos_prev == write_head_pos){
                 if(data->write_steps){
                     data->rec_acc += rec_in;
@@ -156,14 +157,14 @@ void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_
         int32_t read_head_pos;
         int8_t read_head_pos_subsample;
         if(data->playback_active){
-            read_head_pos = (data->read_head_pos_long * 699) >> (25-6); // equiv to (x<<6)/48000 (acc 0.008%)
+            read_head_pos = (data->read_head_pos_long << 6)/RADSPA_SAMPLE_RATE;
             read_head_pos_subsample = read_head_pos & 0b111111;
             read_head_pos = read_head_pos >> 6;
             if(read_head_pos >= sample_len){
                 if(buf[STATUS] & (1<<(STATUS_PLAYBACK_LOOP))){
                     while(read_head_pos > sample_len){
                         if(sample_len){
-                            data->read_head_pos_long -= (uint64_t) sample_len * 48000;
+                            data->read_head_pos_long -= (uint64_t) sample_len * RADSPA_SAMPLE_RATE;
                             read_head_pos -= sample_len;
                         } else {
                             data->read_head_pos_long = 0;
@@ -228,7 +229,7 @@ void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_
     }
 }
 
-#define MAX_SAMPLE_LEN (48000UL*300)
+#define MAX_SAMPLE_LEN (RADSPA_SAMPLE_RATE * 300UL)
 
 radspa_t * sampler_create(uint32_t init_var){
     if(init_var == 0) return NULL; //doesn't make sense
@@ -247,7 +248,7 @@ radspa_t * sampler_create(uint32_t init_var){
 
     int16_t * buf = sampler->plugin_table;
     uint32_t * buf32 = (uint32_t *) buf;
-    buf32[SAMPLE_RATE/2] = 48000;
+    buf32[SAMPLE_RATE/2] = RADSPA_SAMPLE_RATE;
     buf[STATUS] = 1<<(STATUS_RECORD_OVERFLOW);
     return sampler;
 }
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/sampler.h b/components/bl00mbox/radspa/standard_plugin_lib/sampler.h
index 61c064dcdc2cd71a1aeac7a20939ad6e6aa51c73..6505a1651e6ccf4d3a37321c6da491c59f3b4a93 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/sampler.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/sampler.h
@@ -21,5 +21,5 @@ typedef struct {
 
 extern radspa_descriptor_t sampler_desc;
 radspa_t * sampler_create(uint32_t init_var);
-void sampler_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void sampler_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/sequencer.c b/components/bl00mbox/radspa/standard_plugin_lib/sequencer.c
index e5d2e9398781c464d65e7ddb5840be82d7f29bef..bb8666d63f7e9ecd7d72820ef5c0c9d9c96a8a46 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/sequencer.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/sequencer.c
@@ -25,10 +25,10 @@ radspa_descriptor_t sequencer_desc = {
 
 static uint64_t target(uint64_t step_len, uint64_t bpm, uint64_t beat_div){
         if(bpm == 0) return 0;
-        return (48000ULL * 60 * 4) / (bpm * beat_div);
+        return (RADSPA_SAMPLE_RATE * 60ULL * 4) / (bpm * beat_div);
 }
 
-void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_pass_id){
+void sequencer_run(radspa_t * sequencer, uint32_t render_pass_id){
     bool output_request = false;
     sequencer_data_t * data = sequencer->plugin_data;
     sequencer_track_data_t * tracks = (void *) (&data[1]);
@@ -67,7 +67,7 @@ void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_p
         data->beat_div_prev = beat_div;
     }
 
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         int16_t sync_in = radspa_trigger_get(radspa_signal_get_value(sync_in_sig, i, render_pass_id),
                 &(data->sync_in_hist));
         if(sync_in){
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/sequencer.h b/components/bl00mbox/radspa/standard_plugin_lib/sequencer.h
index 4d53c4f9d5a047680802d094b24c668aa66ddcc0..9d43116e660583586d05390d424830a42f090593 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/sequencer.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/sequencer.h
@@ -29,4 +29,4 @@ typedef struct {
 
 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);
+void sequencer_run(radspa_t * osc, uint32_t render_pass_id);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/slew_rate_limiter.c b/components/bl00mbox/radspa/standard_plugin_lib/slew_rate_limiter.c
index e2c8ec7a4baa9bdfd984afb0c6c6611475a4176c..1787a0fe83e97e3e6e612cfc1535bd1cc110ad71 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/slew_rate_limiter.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/slew_rate_limiter.c
@@ -22,7 +22,7 @@ radspa_t * slew_rate_limiter_create(uint32_t init_var){
     return slew_rate_limiter;
 }
 
-void slew_rate_limiter_run(radspa_t * slew_rate_limiter, uint16_t num_samples, uint32_t render_pass_id){
+void slew_rate_limiter_run(radspa_t * slew_rate_limiter, 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);
@@ -31,7 +31,7 @@ void slew_rate_limiter_run(radspa_t * slew_rate_limiter, uint16_t num_samples, u
     slew_rate_limiter_data_t * data = slew_rate_limiter->plugin_data;
 
     int32_t ret = 0;
-    for(uint16_t i = 0; i < num_samples; i++){
+    for(uint16_t i = 0; i < RADSPA_BUFFER_LEN; i++){
         int32_t input = radspa_signal_get_value(input_sig, i, render_pass_id);
         int32_t slew_rate = (uint16_t) radspa_signal_get_value(slew_rate_sig, i, render_pass_id);
         ret = data->prev;
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/slew_rate_limiter.h b/components/bl00mbox/radspa/standard_plugin_lib/slew_rate_limiter.h
index 4c92240b201abab055a92d0493dfa36f3ee54988..047089a331c02e1dc34e8e0d6bc6fa5c2deb6a9d 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/slew_rate_limiter.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/slew_rate_limiter.h
@@ -8,5 +8,5 @@ typedef struct {
 
 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);
+void slew_rate_limiter_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/trigger_merge.c b/components/bl00mbox/radspa/standard_plugin_lib/trigger_merge.c
index d6e1982279c3c3a4c13cfa6eb4751e5f3633db06..f0c951111a1e91e5ee4ef9e3efae65e70f4935b2 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/trigger_merge.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/trigger_merge.c
@@ -17,7 +17,7 @@ typedef struct {
     int16_t trigger_in_prev[];
 } trigger_merge_data_t;
 
-void trigger_merge_run(radspa_t * plugin, uint16_t num_samples, uint32_t render_pass_id){
+void trigger_merge_run(radspa_t * plugin, uint32_t render_pass_id){
     int num_inputs = plugin->len_signals - 1;
     trigger_merge_data_t * data = plugin->plugin_data;
     bool block_stop_events = plugin->plugin_table[0];
@@ -26,7 +26,7 @@ void trigger_merge_run(radspa_t * plugin, uint16_t num_samples, uint32_t render_
     int16_t merged_trigger = 0; 
     int16_t last_timestamp = -1;
     for(uint8_t j = 0; j < num_inputs; j++){
-        int16_t trigger_in = radspa_trigger_get_const(&plugin->signals[j], &data->trigger_in_prev[j], (uint16_t *) &i, num_samples, render_pass_id);
+        int16_t trigger_in = radspa_trigger_get_const(&plugin->signals[j], &data->trigger_in_prev[j], (uint16_t *) &i, render_pass_id);
         if((last_timestamp > i) || (!trigger_in)) continue;
         if((trigger_in > 0) && (last_timestamp == i)){
             if(merged_trigger != -1){
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/trigger_merge.h b/components/bl00mbox/radspa/standard_plugin_lib/trigger_merge.h
index 484ec37bda4f3b5fd81dc8cc0aeebf0088b8e494..d9dad9f9ecbe42ac1f679216fb76babdaa854a77 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/trigger_merge.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/trigger_merge.h
@@ -4,5 +4,5 @@
 
 extern radspa_descriptor_t trigger_merge_desc;
 radspa_t * trigger_merge_create(uint32_t init_var);
-void trigger_merge_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id);
+void trigger_merge_run(radspa_t * osc, uint32_t render_pass_id);
 
diff --git a/components/flow3r_bsp/flow3r_bsp_max98091.c b/components/flow3r_bsp/flow3r_bsp_max98091.c
index 67ce09f81041b3c4ada1e5485e7d0656b3202bda..eb5b9d490878c44fbbdb86a6b6f0c29b2a72a917 100644
--- a/components/flow3r_bsp/flow3r_bsp_max98091.c
+++ b/components/flow3r_bsp/flow3r_bsp_max98091.c
@@ -461,7 +461,6 @@ void flow3r_bsp_max98091_init(void) {
     // jack detect enable
     POKE(JACK_DETECT, 1 << 7);
 
-    // TODO(q3k): mute this
     ESP_LOGI(
         TAG,
         "Codec initilialied! Don't worry about the readback errors above.");
diff --git a/components/st3m/st3m_audio.c b/components/st3m/st3m_audio.c
index 214bd6726e699c5081d623f83abcfe19ff06a807..40f15041e28846254e5460a73a41d4a612ff0856 100644
--- a/components/st3m/st3m_audio.c
+++ b/components/st3m/st3m_audio.c
@@ -14,46 +14,17 @@
 #include "freertos/task.h"
 
 #include "bl00mbox.h"
+#include "bl00mbox_config.h"
+#if FLOW3R_BSP_AUDIO_SAMPLE_RATE != BL00MBOX_SAMPLE_RATE
+#error bl00mbox sample rate mismatch
+#endif
+#if FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE != BL00MBOX_BUFFER_LEN
+#error bl00mbox buffer size mismatch
+#endif
 #include "st3m_pcm.h"
 
 static const char *TAG = "st3m-audio";
 
-// TODO: clean up
-static void bl00mbox_init_wrapper(uint32_t sample_rate, uint16_t max_len) {
-    bl00mbox_init();
-}
-static bool bl00mbox_audio_render_wrapper(int16_t *rx, int16_t *tx,
-                                          uint16_t len) {
-    bl00mbox_audio_render(rx, tx, len);
-    return true;
-}
-
-/* You can add your own audio engine here by simply adding a valid struct to
- * this list! For details about the fields check out st3m_audio.h.
- */
-
-static const st3m_audio_engine_t engines[] = {
-    {
-        .name = "bl00mbox",
-        .render_fun = bl00mbox_audio_render_wrapper,
-        .init_fun = bl00mbox_init_wrapper,
-    },
-    {
-        .name = "PCM",
-        .render_fun = st3m_pcm_audio_render,
-        .init_fun = NULL,
-    }
-};
-
-static const uint8_t num_engines =
-    (sizeof(engines)) / (sizeof(st3m_audio_engine_t));
-
-typedef struct {
-    int32_t volume;
-    bool mute;
-    bool active;  // whether the engine has been filling tx in the last run
-} _engine_data_t;
-
 #define TIMEOUT_MS 1000
 
 static void _audio_player_task(void *data);
@@ -247,8 +218,6 @@ typedef struct {
     st3m_audio_input_source_t thru_target_source;
     st3m_audio_input_source_t source;
 
-    _engine_data_t *engines_data;
-
     // Software-based audio pipe settings.
     int32_t input_thru_vol;
     int32_t input_thru_vol_int;
@@ -533,24 +502,7 @@ void st3m_audio_init(void) {
     assert(state_mutex != NULL);
 
     flow3r_bsp_audio_init();
-    {
-        _engine_data_t *tmp = malloc(sizeof(_engine_data_t) * num_engines);
-        LOCK;
-        state.engines_data = tmp;
-        UNLOCK;
-    }
-
-    for (uint8_t i = 0; i < num_engines; i++) {
-        LOCK;
-        state.engines_data[i].volume = 4096;
-        state.engines_data[i].mute = false;
-        state.engines_data[i].active = false;  // is ignored by engine anyways
-        UNLOCK;
-        if (engines[i].init_fun != NULL) {
-            (*engines[i].init_fun)(FLOW3R_BSP_AUDIO_SAMPLE_RATE,
-                                   FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE);
-        }
-    }
+    bl00mbox_init();
 
     _update_routing();
     _output_apply(&state.speaker);
@@ -564,20 +516,21 @@ void st3m_audio_init(void) {
     ESP_LOGI(TAG, "Audio task started");
 }
 
+#define BUF_LEN (FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2)
+
 static void _audio_player_task(void *data) {
     (void)data;
 
-    int16_t buffer_tx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
-    int16_t buffer_rx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
-    int16_t buffer_rx_dummy[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
-    int32_t output_acc[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
-    int16_t engines_tx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2];
-    memset(buffer_tx, 0, sizeof(buffer_tx));
+    int16_t buffer_rx[BUF_LEN];
+    int16_t buffer_empty[BUF_LEN];
+    int16_t buffer_acc[BUF_LEN];
     memset(buffer_rx, 0, sizeof(buffer_rx));
-    memset(buffer_rx_dummy, 0, sizeof(buffer_rx_dummy));
+    memset(buffer_empty, 0, sizeof(buffer_empty));
 
     size_t count;
     st3m_audio_input_source_t source_prev = st3m_audio_input_source_none;
+    int source_chan = 0; // 0: stereo, 1: left-to-stereo, 2: right-to-stereo
+    int16_t * source_gain = NULL; // *256: unity gain, NULL: don't render source
 
     while (true) {
         count = 0;
@@ -593,136 +546,119 @@ static void _audio_player_task(void *data) {
             continue;
         }
 
-        int32_t engines_vol[num_engines];
-        bool engines_mute[num_engines];
-        bool engines_active[num_engines];
-
         LOCK;
-        for (uint8_t e = 0; e < num_engines; e++) {
-            engines_vol[e] = state.engines_data[e].volume;
-            engines_mute[e] = state.engines_data[e].mute;
-        }
         st3m_audio_input_source_t source = state.source;
         st3m_audio_input_source_t engines_source = state.engines_source;
         st3m_audio_input_source_t thru_source = state.thru_source;
-        bool headphones = _headphones_connected();
-        int32_t software_volume = headphones ? state.headphones.volume_software
-                                             : state.speaker.volume_software;
+        int32_t software_volume = _headphones_connected() ?
+                                    state.headphones.volume_software :
+                                    state.speaker.volume_software;
         bool input_thru_mute = state.input_thru_mute;
         int32_t input_thru_vol_int = state.input_thru_vol_int;
-        UNLOCK;
 
         // <RX SIGNAL PREPROCESSING>
 
-        int32_t rx_gain = 256;  // unity
-        uint8_t rx_chan = 3;    // stereo = 0; left = 1; right = 2; off = 3;
         if (source != source_prev) {
             // state change: throw away buffer
             source_prev = source;
             memset(buffer_rx, 0, sizeof(buffer_rx));
-        } else {
-            LOCK;
+            source_chan = 0;
             if (source == st3m_audio_input_source_headset_mic) {
-                rx_gain = state.headset_mic_gain_software;
-                rx_chan = 0;  // not sure, don't have one here, need to test
+                source_gain = &state.headset_mic_gain_software;
+                //source_chan = ?;  // not sure, don't have one here, need to test
             } else if (source == st3m_audio_input_source_line_in) {
-                rx_gain = state.line_in_gain_software;
-                rx_chan = 0;
+                source_gain = &state.line_in_gain_software;
             } else if (source == st3m_audio_input_source_onboard_mic) {
-                rx_gain = state.onboard_mic_gain_software;
-                rx_chan = 1;
-            }
-            UNLOCK;
-        }
-
-        if (rx_chan == 0) {
-            // keep stereo image
-            for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2;
-                 i++) {
-                buffer_rx[i] = (buffer_rx[i] * rx_gain) >> 8;
-            }
-        } else if (rx_chan < 3) {
-            // mix one of the input channels to both rx stereo chans (easier
-            // mono sources)
-            for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2;
-                 i++) {
-                uint16_t j = (i / 2) * 2 + rx_chan - 1;
-                buffer_rx[i] = (buffer_rx[j] * rx_gain) >> 8;
+                source_gain = &state.onboard_mic_gain_software;
+                source_chan = 1;
+            } else if (source == st3m_audio_input_source_none) {
+                source_gain = NULL;
             }
         }
+        UNLOCK;
 
         int16_t *engines_rx;
 
-        if (engines_source == st3m_audio_input_source_none) {
-            engines_rx = buffer_rx_dummy;
+        if (!source_gain) {
+            engines_rx = buffer_empty;
         } else {
+            int32_t rx_gain = *source_gain; // 256 is unity
+            if (source_chan == 0) {
+                for (uint16_t i = 0; i < BUF_LEN; i++) {
+                    buffer_rx[i] = (buffer_rx[i] * rx_gain) >> 8;
+                }
+            } else if (source_chan == 1) {
+                for (uint16_t i = 0; i < BUF_LEN; i += 2) {
+                    buffer_rx[i] = buffer_rx[i+1] = (buffer_rx[i] * rx_gain) >> 8;
+                }
+            } else if (source_chan == 2){
+                for (uint16_t i = 0; i < BUF_LEN; i += 2) {
+                    buffer_rx[i] = buffer_rx[i+1] = (buffer_rx[i+1] * rx_gain) >> 8;
+                }
+            }
             engines_rx = buffer_rx;
         }
-        // </RX SIGNAL PREPROCESSING>
 
         // <ACCUMULATING ENGINES>
 
-        bool output_acc_uninit = true;
-        for (uint8_t e = 0; e < num_engines; e++) {
-            // always run function even when muted, else the engine
-            // might suffer from being deprived of the passage of time
-            engines_active[e] = (*engines[e].render_fun)(
-                engines_rx, engines_tx, FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2);
-            if ((!engines_active[e]) || (!engines_vol[e]) || engines_mute[e])
-                continue;
-            if (output_acc_uninit) {
-                for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2;
-                     i++) {
-                    output_acc[i] = (engines_tx[i] * engines_vol[e]) >> 12;
+        // note: engines may not write to engines_rx as it might be the empty ref buffer
+
+        bool buffer_acc_init = st3m_pcm_audio_render(engines_rx, buffer_acc, BUF_LEN);
+        if(buffer_acc_init){
+            // bl00mbox handles clipping for us
+            bl00mbox_audio_render_adding(engines_rx, buffer_acc);
+        } else {
+            buffer_acc_init = bl00mbox_audio_render(engines_rx, buffer_acc);
+        }
+
+        // <THRU>
+
+        if ((thru_source != st3m_audio_input_source_none) &&
+            ((engines_source == thru_source) ||
+             (engines_source == st3m_audio_input_source_none)) &&
+            (!input_thru_mute)) {
+            if(buffer_acc_init){
+                for (int i = 0; i < BUF_LEN; i += 1) {
+                    int32_t val = (buffer_rx[i] * input_thru_vol_int) >> 15;
+                    val += buffer_acc[i];
+                    // clip here manually
+                    buffer_acc[i] = (val > 32767) ? 32767 : ((val < -32767) ? -32767 : val);
                 }
             } else {
-                for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2;
-                     i++) {
-                    output_acc[i] += (engines_tx[i] * engines_vol[e]) >> 12;
+                for (int i = 0; i < BUF_LEN; i += 1) {
+                    int32_t val = (buffer_rx[i] * input_thru_vol_int) >> 15;
+                    // not sure if clipping is crequired here clip here manually
+                    buffer_acc[i] = (val > 32767) ? 32767 : ((val < -32767) ? -32767 : val);
                 }
+                buffer_acc_init = true;
             }
-            output_acc_uninit = false;
-        }
-        if (output_acc_uninit) {
-            memset(output_acc, 0, sizeof(output_acc));
-        }
-
-        LOCK;
-        for (uint8_t e = 0; e < num_engines; e++) {
-            state.engines_data[e].active = engines_active[e];
         }
-        UNLOCK;
 
-        // </ACCUMULATING ENGINES>
+        // <SCOPE>
 
-        // <VOLUME AND THRU>
-
-        for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE; i++) {
-            st3m_scope_write((output_acc[2 * i] + output_acc[2 * i + 1]) >> 3);
-        }
-
-        for (int i = 0; i < (FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2); i += 1) {
-            if ((thru_source != st3m_audio_input_source_none) &&
-                ((engines_source == thru_source) ||
-                 (engines_source == st3m_audio_input_source_none)) &&
-                (!input_thru_mute)) {
-                output_acc[i] += (buffer_rx[i] * input_thru_vol_int) >> 15;
+        // there should be an off switch for this
+        if (buffer_acc_init) {
+            for (uint16_t i = 0; i < BUF_LEN; i+=2) {
+                st3m_scope_write((buffer_acc[i] + buffer_acc[i + 1]) >> 3);
             }
+        }
+        // <VOLUME>
 
-            output_acc[i] = (output_acc[i] * software_volume) >> 15;
-
-            if (output_acc[i] > 32767) output_acc[i] = 32767;
-            if (output_acc[i] < -32767) output_acc[i] = -32767;
+        int16_t * buffer_tx = buffer_empty;
 
-            buffer_tx[i] = output_acc[i];
+        if (buffer_acc_init) {
+            for (uint16_t i = 0; i < BUF_LEN; i++) {
+                buffer_acc[i] = (buffer_acc[i] * software_volume) >> 15;
+            }
+            buffer_tx = buffer_acc;
         }
 
-        // </VOLUME AND THRU>
+        // <WRITE>
 
-        flow3r_bsp_audio_write(buffer_tx, sizeof(buffer_tx), &count, 1000);
-        if (count != sizeof(buffer_tx)) {
+        flow3r_bsp_audio_write(buffer_tx, sizeof(buffer_empty), &count, 1000);
+        if (count != sizeof(buffer_empty)) {
             ESP_LOGE(TAG, "audio_write: count (%d) != length (%d)\n", count,
-                     sizeof(buffer_tx));
+                     sizeof(buffer_empty));
             abort();
         }
     }
diff --git a/components/st3m/st3m_audio.h b/components/st3m/st3m_audio.h
index 51ea9339f27a33d33e28671f71561e15a47b114b..e06daa236d02880502a4eaf9e945715ba2cfdfd4 100644
--- a/components/st3m/st3m_audio.h
+++ b/components/st3m/st3m_audio.h
@@ -16,48 +16,6 @@ typedef enum {
     st3m_audio_input_source_auto = 4
 } st3m_audio_input_source_t;
 
-/* Initializes the audio engine and passes sample rate as well as max buffer
- * length. At this point those values are always 48000/128, but this might
- * become variable in the future. However, we see flow3r primarily as a real
- * time instrument, and longer buffers introduce latency; the current buffer
- * length corresponds to 1.3ms latency which isn't much, but given the up to
- * 10ms captouch latency on top we shouldn't be super careless here.
- */
-typedef void (*st3m_audio_engine_init_function_t)(uint32_t sample_rate,
-                                                  uint16_t max_len);
-
-/* Renders the output of the audio engine and returns whether or not it has
- * overwritten tx. Always called for each buffer, no exceptions. This means you
- * can keep track of time within the engine easily and use the audio player task
- * to handle musical events (the current 1.3ms buffer rate is well paced for
- * this), but it also puts the burden on you of exiting early if there's nothing
- * to do.
- *
- * rx (input) and tx (output) are both stereo interlaced, i.e. the even indices
- * represent the left channel, the odd indices represent the right channel. The
- * length is the total length of the buffer so that each channel has len/2 data
- * points. len is always even.
- *
- * The function must never modify rx. This is so that we can pass the same
- * buffer to all the engines without having to memcpy by default, so if you need
- * to modify rx please do your own memcpy of it.
- *
- * In a similar manner, tx is not cleaned up when calling the function, it
- * carries random junk data that is not supposed to be read by the user. The
- * return value indicates whether tx should be used or if tx should be ignored
- * andit should be treated as if you had written all zeroes into it (without you
- * actually doing so). If you choose to return true please make sure you have
- * overwritten the entirety of tx with valid data.
- */
-typedef bool (*st3m_audio_engine_render_function_t)(int16_t* rx, int16_t* tx,
-                                                    uint16_t len);
-
-typedef struct {
-    char* name;  // used for UI, no longer than 14 characters
-    st3m_audio_engine_render_function_t render_fun;
-    st3m_audio_engine_init_function_t init_fun;  // optional, else NULL
-} st3m_audio_engine_t;
-
 /* Initializes I2S bus, the audio task and required data structures.
  * Expects an initialized I2C bus, will fail ungracefully otherwise (TODO).
  */
diff --git a/components/st3m/st3m_media.c b/components/st3m/st3m_media.c
index 5750239ecd008bbdf77eb6f698f12172db6bbcc2..e3358d40af80945a237f7856cf43729d49641893 100644
--- a/components/st3m/st3m_media.c
+++ b/components/st3m/st3m_media.c
@@ -1,5 +1,6 @@
 #include "st3m_media.h"
 #include "st3m_audio.h"
+#include "flow3r_bsp.h"
 
 #include <math.h>
 #include <stdio.h>
@@ -106,7 +107,7 @@ float st3m_media_get_position(void) {
 float st3m_media_get_time(void) {
     if (!media_item) return 0;
     if (media_item->time <= 0) return media_item->time;
-    return media_item->time - st3m_pcm_queued() / 48000.0 / 2.0;
+    return media_item->time - st3m_pcm_queued() / ((float) FLOW3R_BSP_AUDIO_SAMPLE_RATE) / 2.0;
 }
 
 void st3m_media_seek(float position) {
diff --git a/components/st3m/st3m_pcm.c b/components/st3m/st3m_pcm.c
index 851df6b0d05579958110d554252ae7336b33f903..6c2c820c6ec9b626917c9772fae0b99a34b06567 100644
--- a/components/st3m/st3m_pcm.c
+++ b/components/st3m/st3m_pcm.c
@@ -1,4 +1,5 @@
 #include "st3m_pcm.h"
+#include "flow3r_bsp.h"
 
 #include <unistd.h>
 
@@ -41,7 +42,7 @@ bool st3m_pcm_audio_render(int16_t *rx, int16_t *tx, uint16_t len) {
 static int phase = 0;
 
 void st3m_pcm_queue_s16(int hz, int ch, int count, int16_t *data) {
-    if (hz == 48000) {
+    if (hz == FLOW3R_BSP_AUDIO_SAMPLE_RATE) {
         if (ch == 2) {
             if (st3m_pcm_gain == 4096) {
                 for (int i = 0; i < count * 2; i++) {
@@ -75,7 +76,7 @@ void st3m_pcm_queue_s16(int hz, int ch, int count, int16_t *data) {
             }
         }
     } else {
-        int fraction = ((48000.0 / hz) - 1.0) * 65536;
+        int fraction = ((((float) FLOW3R_BSP_AUDIO_SAMPLE_RATE) / hz) - 1.0) * 65536;
         if (ch == 2) {
             for (int i = 0; i < count; i++) {
                 st3m_pcm_buffer[st3m_pcm_w++] =
@@ -112,7 +113,7 @@ void st3m_pcm_queue_s16(int hz, int ch, int count, int16_t *data) {
 }
 
 void st3m_pcm_queue_float(int hz, int ch, int count, float *data) {
-    if (hz == 48000) {
+    if (hz == FLOW3R_BSP_AUDIO_SAMPLE_RATE) {
         if (ch == 2) {
             if (st3m_pcm_gain == 4096)
                 for (int i = 0; i < count * 2; i++) {
@@ -144,7 +145,7 @@ void st3m_pcm_queue_float(int hz, int ch, int count, float *data) {
                 }
         }
     } else {
-        int fraction = ((48000.0 / hz) - 1.0) * 65536;
+        int fraction = ((((float) FLOW3R_BSP_AUDIO_SAMPLE_RATE) / hz) - 1.0) * 65536;
         if (ch == 2) {
             for (int i = 0; i < count; i++) {
                 st3m_pcm_buffer[st3m_pcm_w++] =
diff --git a/python_payload/st3m/ui/mixer.py b/python_payload/st3m/ui/mixer.py
index 9df06c90b7138365e4ae0ae3f1da552184903af0..59c1696868968872ee93f5f2694baed6a1b799ed 100644
--- a/python_payload/st3m/ui/mixer.py
+++ b/python_payload/st3m/ui/mixer.py
@@ -40,6 +40,9 @@ class Channel:
     def get_rms_dB(self):
         return -math.inf
 
+    def get_load(self):
+        return 0
+
     def get_mute(self):
         mute = False
         if self.get_name() in session_channel_vol_mute:
@@ -98,6 +101,9 @@ class bl00mboxChannel(Channel):
     def get_rms_dB(self):
         return self.blm.sys_rms_dB
 
+    def get_load(self):
+        return self.blm.render.load
+
 
 class mediaChannel(Channel):
     def get_name(self):
@@ -126,6 +132,7 @@ class ChannelColors:
     name = (0, 1, 1)
     vol_bar = (0, 1, 0)
     rms_bar = (1, 0, 0)
+    load_bar = (0, 0.5, 1)
     mute_bg = ((0.2, 0.2, 0.2), (0.5, 0.0, 0.0))
     mute_fg = ((0.5, 0.5, 0.5), (0.8, 0.8, 0.8))
 
@@ -135,6 +142,7 @@ class HighlightColors:
     name = (0, 1, 1)
     vol_bar = (0, 1, 0)
     rms_bar = (1, 0, 0)
+    load_bar = (0, 0.5, 1)
     mute_bg = ((0.2, 0.2, 0.2), (0.8, 0.0, 0.0))
     mute_fg = ((0.5, 0.5, 0.5), (1.0, 1.0, 1.0))
 
@@ -184,8 +192,10 @@ class AudioMixer(Responder):
         self._chans = [mediaChannel()]
         for c in bl00mbox.Sys.collect_channels(True):
             chan = bl00mboxChannel(c)
-            chan.prev_compute_rms = chan.blm.compute_rms
-            chan.blm.compute_rms = True
+            chan.prev_compute_rms = chan.blm.render.rms_dB is None
+            chan.blm.render.rms_dB = True
+            chan.prev_compute_time = chan.blm.render.load is None
+            chan.blm.render.load = True
             self._chans += [chan]
         """
         for i in range(5):
@@ -335,10 +345,15 @@ class AudioMixer(Responder):
                 ctx.round_rectangle(-chan_width / 2 + 3, 41, 18, 18, 2).stroke()
 
             ctx.rgb(0, 0, 0)
-            ctx.rectangle(chan_width / 2 - 13, bar_bottom - len_bar, 9, len_bar).fill()
+
+            if isinstance(chan, bl00mboxChannel):
+                shift = 0
+            else:
+                shift = 7
+            ctx.rectangle(chan_width / 2 - 16 + shift, bar_bottom - len_bar, 12-shift, len_bar).fill()
 
             # reference notch
-            ctx.move_to(chan_width / 2 - 13, bar_bottom - len_bar * 5 / 7)
+            ctx.move_to(chan_width / 2 - 16 + shift, bar_bottom - len_bar * 5 / 7)
             ctx.rel_line_to(-5, 0).stroke()
 
             # vol bar
@@ -347,7 +362,7 @@ class AudioMixer(Responder):
             vol_bar_len = min(1, max(0, vol_bar_len))
             vol_bar_len *= len_bar
             ctx.rectangle(
-                chan_width / 2 - 12, bar_bottom - vol_bar_len, 3, vol_bar_len
+                chan_width / 2 - 15 + shift, bar_bottom - vol_bar_len, 3, vol_bar_len
             ).fill()
 
             if self._editing_channel == 1 and highlight:
@@ -358,20 +373,31 @@ class AudioMixer(Responder):
                 ctx.rel_line_to(5, -3)
                 ctx.fill()
 
-        ctx.rgb(0, 0, 0)
-        ctx.rectangle(chan_width / 2 - 9, bar_bottom - len_bar, 5, len_bar).fill()
+        if isinstance(chan, bl00mboxChannel):
+            ctx.rgb(0, 0, 0)
+            ctx.rectangle(chan_width / 2 - 11, bar_bottom - len_bar, 7, len_bar).fill()
+
+            # rms bar
+            rms = chan.get_rms_dB() + 15
+            ctx.rgb(*colors.rms_bar)
+            rms_bar_len = (rms + 50) / 70
+            rms_bar_len = min(1, max(0, rms_bar_len))
+            rms_bar_len *= len_bar
+            ctx.rectangle(
+                chan_width / 2 - 11, bar_bottom - rms_bar_len, 3, rms_bar_len
+            ).fill()
 
-        # rms bar
-        rms = chan.get_rms_dB() + 15
-        ctx.rgb(*colors.rms_bar)
-        rms_bar_len = (rms + 50) / 70
-        rms_bar_len = min(1, max(0, rms_bar_len))
-        rms_bar_len *= len_bar
-        ctx.rectangle(
-            chan_width / 2 - 8, bar_bottom - rms_bar_len, 3, rms_bar_len
-        ).fill()
+            # load bar
+            ctx.rgb(*colors.load_bar)
+            load_bar_len = chan.blm.render.load * 5 / 7
+            load_bar_len = min(1, max(0, load_bar_len))
+            load_bar_len *= len_bar
+            ctx.rectangle(
+                chan_width / 2 - 7, bar_bottom - load_bar_len, 2, load_bar_len
+            ).fill()
 
     def on_exit(self):
         for chan in self._chans:
             if isinstance(chan, bl00mboxChannel):
-                chan.blm.compute_rms = chan.prev_compute_rms
+                chan.blm.render.rms_dB = chan.prev_compute_rms
+                chan.blm.render.load = chan.prev_compute_time