From 1e93b0693640f139822dc4e723a56473c7ee3f1a Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Sun, 17 Nov 2024 17:52:05 +0100
Subject: [PATCH] bl00mbox callbacks/weak references/backend rewrite

---
 components/bl00mbox/CMakeLists.txt            |    3 +
 components/bl00mbox/bl00mbox_audio.c          |  480 +++----
 components/bl00mbox/bl00mbox_containers.c     |   86 ++
 components/bl00mbox/bl00mbox_os.c             |   15 +-
 .../bl00mbox/bl00mbox_plugin_registry.c       |   42 +-
 .../bl00mbox/bl00mbox_radspa_requirements.c   |    4 +-
 components/bl00mbox/bl00mbox_user.c           | 1113 +++++----------
 components/bl00mbox/config/bl00mbox_config.h  |   16 +
 components/bl00mbox/include/bl00mbox_audio.h  |  140 +-
 .../bl00mbox/include/bl00mbox_containers.h    |   36 +
 components/bl00mbox/include/bl00mbox_ll.h     |   54 -
 components/bl00mbox/include/bl00mbox_os.h     |   19 +-
 .../include/bl00mbox_plugin_registry.h        |    2 +
 components/bl00mbox/include/bl00mbox_user.h   |   71 +-
 .../bl00mbox/micropython/bl00mbox/_patches.py |   41 +-
 .../bl00mbox/micropython/bl00mbox/_plugins.py |  273 ++--
 .../bl00mbox/micropython/bl00mbox/_user.py    |  666 ++++-----
 .../bl00mbox/micropython/mp_sys_bl00mbox.c    | 1188 ++++++++---------
 .../bl00mbox_channel_plugin.c                 |   73 +
 .../bl00mbox_channel_plugin.h                 |   13 +
 .../bl00mbox_specific/bl00mbox_line_in.c      |    6 +
 components/bl00mbox/radspa/radspa.h           |    1 +
 components/bl00mbox/radspa/radspa_helpers.h   |   24 +-
 .../radspa/standard_plugin_lib/buffer.c       |   30 +
 .../radspa/standard_plugin_lib/buffer.h       |    7 +
 .../radspa/standard_plugin_lib/mixer.c        |    2 +-
 .../radspa/standard_plugin_lib/mixer.h        |    2 +-
 .../radspa/standard_plugin_lib/noise.c        |    2 +-
 .../bl00mbox/radspa/standard_plugin_lib/osc.c |   19 +-
 .../bl00mbox/radspa/standard_plugin_lib/osc.h |    1 +
 .../standard_plugin_lib/range_shifter.c       |    2 +-
 python_payload/st3m/reactor.py                |    6 +
 python_payload/st3m/run.py                    |    4 -
 python_payload/st3m/ui/mixer.py               |   14 +-
 sim/fakes/bl00mbox.py                         |   16 +-
 35 files changed, 2051 insertions(+), 2420 deletions(-)
 create mode 100644 components/bl00mbox/bl00mbox_containers.c
 create mode 100644 components/bl00mbox/include/bl00mbox_containers.h
 delete mode 100644 components/bl00mbox/include/bl00mbox_ll.h
 create mode 100644 components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.c
 create mode 100644 components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.h
 create mode 100644 components/bl00mbox/radspa/standard_plugin_lib/buffer.c
 create mode 100644 components/bl00mbox/radspa/standard_plugin_lib/buffer.h

diff --git a/components/bl00mbox/CMakeLists.txt b/components/bl00mbox/CMakeLists.txt
index 851a55b400..f64b7c81f5 100644
--- a/components/bl00mbox/CMakeLists.txt
+++ b/components/bl00mbox/CMakeLists.txt
@@ -8,6 +8,7 @@ idf_component_register(
         bl00mbox_os.c
         bl00mbox_plugin_registry.c
         bl00mbox_radspa_requirements.c
+        bl00mbox_containers.c
         radspa/standard_plugin_lib/osc.c
         radspa/standard_plugin_lib/osc_fm.c
         radspa/standard_plugin_lib/env_adsr.c
@@ -27,7 +28,9 @@ idf_component_register(
         radspa/standard_plugin_lib/range_shifter.c
         radspa/standard_plugin_lib/poly_squeeze.c
         radspa/standard_plugin_lib/slew_rate_limiter.c
+        radspa/standard_plugin_lib/buffer.c
         plugins/bl00mbox_specific/bl00mbox_line_in.c
+        plugins/bl00mbox_specific/bl00mbox_channel_plugin.c
         radspa/radspa_helpers.c
         extern/xoroshiro64star.c
     INCLUDE_DIRS
diff --git a/components/bl00mbox/bl00mbox_audio.c b/components/bl00mbox/bl00mbox_audio.c
index ae8a3e2cf8..2a0ab1589d 100644
--- a/components/bl00mbox/bl00mbox_audio.c
+++ b/components/bl00mbox/bl00mbox_audio.c
@@ -1,144 +1,197 @@
 //SPDX-License-Identifier: CC0-1.0
 #include "bl00mbox_audio.h"
-#include "bl00mbox_ll.h"
 #include "bl00mbox_user.h"
 #include "bl00mbox_os.h"
+#include "bl00mbox_channel_plugin.h"
+#include <assert.h>
 
-static bool is_initialized = false;
 static uint16_t full_buffer_len;
 
 static uint32_t render_pass_id;
 
 int16_t * bl00mbox_line_in_interlaced = NULL;
 
-static int32_t free_chan_index = 0; // increments
-static bl00mbox_ll_t * all_chans = NULL;
-static bl00mbox_ll_t * background_mute_override_chans = NULL;
+// grab user_lock for r/w
+static bl00mbox_set_t * all_chans = NULL;
+
+// grab user_lock for r/w
+static bl00mbox_set_t * background_mute_override_chans = NULL;
+
+// grab user_lock for r/w
 static bl00mbox_channel_t * foreground_chan = NULL;
-static bl00mbox_lock_t render_lock = NULL;
+static bl00mbox_lock_t user_lock = NULL;
 
+// grab both user_lock and active_chans_lock for writing.
+// for user-side reading grab user_lock.
+// render task reads with active_chans_lock.
+static bl00mbox_array_t * active_chans = NULL;
+
+static bl00mbox_lock_t active_chans_lock = NULL;
 static bl00mbox_channel_t * cached_chan = NULL;
 
-bl00mbox_channel_t * bl00mbox_get_channel(int32_t channel_index){
-    if(channel_index < 0) return NULL;
-    if(cached_chan && (cached_chan->index == channel_index)){
-        return cached_chan;
-    }
-    bl00mbox_ll_t * chll = all_chans;
-    while(chll){
-        bl00mbox_channel_t * chan = chll->content;
-        if(chan->index == channel_index){
-            cached_chan = chan;
-            return chan;
+static void update_active_chans(){
+    // must be called after changing foreground_chan or background_mute_override_chans
+    // while _still_ holding user_lock but not active_chans_lock
+    size_t num_chans = background_mute_override_chans->len;
+    if(foreground_chan) num_chans++;
+    bl00mbox_array_t * new_active_chans = malloc(sizeof(bl00mbox_array_t) + num_chans * sizeof(void *));
+    if(new_active_chans){
+        size_t index = 0;
+        if(foreground_chan){
+            new_active_chans->elems[index] = foreground_chan;
+            index++;
         }
-        chll = chll->next;
-    }
-    return NULL;
-}
-
-bool bl00mbox_get_channel_exists(int32_t channel_index){
-    return (bool) bl00mbox_get_channel(channel_index);
-}
-
-int32_t bl00mbox_get_channel_index_positional_all_chans(int32_t position){
-    bl00mbox_ll_t * chll = all_chans;
-    if(!chll) return -1;
-    while(position){
-        position--;
-        chll = chll->next;
-        if(!chll) return -1;
+        bl00mbox_set_iter_t iter;
+        bl00mbox_set_iter_start(&iter, background_mute_override_chans);
+        bl00mbox_channel_t * chan;
+        while((chan = bl00mbox_set_iter_next(&iter))){
+            new_active_chans->elems[index] = chan; 
+            index++;
+        }
+        new_active_chans->len = num_chans;
+    } else {
+        bl00mbox_log_error("out of memory");
     }
-    bl00mbox_channel_t * chan = chll->content;
-    return chan->index;
-}
 
-int32_t bl00mbox_get_channel_index_positional_background_mute_override_chans(int32_t position){
-    bl00mbox_ll_t * chll = background_mute_override_chans;
-    if(!chll) return -1;
-    while(position){
-        position--;
-        chll = chll->next;
-        if(!chll) return -1;
+#ifdef BL00MBOX_DEBUG
+    if(new_active_chans) bl00mbox_log_error("active chans: %d", (int) new_active_chans->len);
+#endif
+    bl00mbox_array_t * previous_active_chans;
+    bl00mbox_take_lock(&active_chans_lock);
+    previous_active_chans = active_chans;
+    active_chans = new_active_chans;
+    bl00mbox_give_lock(&active_chans_lock);
+    free(previous_active_chans);
+}
+
+bl00mbox_array_t * bl00mbox_collect_channels(bool active){
+    bl00mbox_array_t * ret = NULL;
+    bl00mbox_take_lock(&user_lock);
+    if(active){
+        if(active_chans){
+            size_t ret_size = sizeof(bl00mbox_array_t);
+            ret_size += active_chans->len * sizeof(void *);
+            ret = malloc(ret_size);
+            if(ret) memcpy(ret, active_chans, ret_size);
+        } else {
+            ret = malloc(sizeof(bl00mbox_array_t));
+            if(ret) ret->len = 0;
+        }
+    } else {
+        ret = bl00mbox_set_to_array(all_chans);
     }
-    bl00mbox_channel_t * chan = chll->content;
-    return chan->index;
+    bl00mbox_give_lock(&user_lock);
+    if(!ret) bl00mbox_log_error("out of memory");
+    return ret;
 }
 
-bool bl00mbox_channel_set_background_mute_override(int32_t channel_index, bool enable){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(channel_index);
-    if(!ch) return false;
+void bl00mbox_channel_set_background_mute_override(bl00mbox_channel_t * chan, bool enable){
+    bl00mbox_take_lock(&user_lock);
+    chan->background_mute_override = enable;
+    bool update;
     if(enable){
-        bl00mbox_ll_prepend(&background_mute_override_chans, ch, &render_lock);
+        update = bl00mbox_set_add(background_mute_override_chans, chan);
     } else {
-        bl00mbox_ll_pop(&background_mute_override_chans, ch, &render_lock);
+        update = bl00mbox_set_remove(background_mute_override_chans, chan);
     }
-    return true;
+    if(update) update_active_chans();
+    bl00mbox_give_lock(&user_lock);
 }
 
-bool bl00mbox_channel_get_background_mute_override(int32_t channel_index){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(channel_index);
-    if(!ch) return false;
-    return bl00mbox_ll_contains(&background_mute_override_chans, ch);
+bool bl00mbox_channel_get_foreground(bl00mbox_channel_t * chan){
+    return foreground_chan == chan;
 }
 
-int32_t bl00mbox_channel_get_foreground_index(){
-    if(foreground_chan) return foreground_chan->index;
-    return -1;
+void bl00mbox_channel_set_foreground(bl00mbox_channel_t * chan, bool enable){
+    if(bl00mbox_channel_get_foreground(chan) == enable) return;
+    bl00mbox_take_lock(&user_lock);
+    foreground_chan = enable ? chan : NULL;
+    update_active_chans();
+    bl00mbox_give_lock(&user_lock);
 }
 
-void bl00mbox_channel_set_foreground_index(int32_t channel_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel_index);
-    if(!chan) return;
-    if(foreground_chan != chan){
-        bl00mbox_take_lock(&render_lock);
-        foreground_chan = chan;
-        bl00mbox_give_lock(&render_lock);
-    }
-}
-
-void bl00mbox_channel_event(int32_t index){
+void bl00mbox_channel_event(bl00mbox_channel_t * chan){
 #ifdef BL00MBOX_AUTO_FOREGROUNDING
-    bl00mbox_channel_set_foreground_index(index);
+    bl00mbox_channel_set_foreground(chan, true);
 #endif
 }
 
-static bl00mbox_channel_t * _bl00mbox_channel_create(){
-    if(free_chan_index < 0) return NULL;
+bl00mbox_channel_t * bl00mbox_channel_create(){
     bl00mbox_channel_t * chan = calloc(1, sizeof(bl00mbox_channel_t));
-    if(!chan) return NULL;
-    if(!bl00mbox_ll_prepend(&all_chans, chan, NULL)){
-        free(chan);
-        return NULL;
-    }
+    if(!chan) goto failed;
     chan->volume = BL00MBOX_DEFAULT_CHANNEL_VOLUME;
     chan->sys_gain = 4096;
-    chan->is_active = true;
-    chan->index = free_chan_index;
-    free_chan_index += 1; 
-
-    bl00mbox_create_lock(&chan->render_lock);
-    bl00mbox_take_lock(&render_lock);
-    foreground_chan = chan;
-    bl00mbox_give_lock(&render_lock);
+
+    // must be destroyed manually as it's not in the plugin list
+    chan->channel_plugin = bl00mbox_plugin_create_unlisted(chan, &bl00mbox_channel_plugin_desc, 0);
+    if(!chan->channel_plugin) goto failed;
+
+    if(!bl00mbox_create_lock(&chan->render_lock)) goto failed;
+
+    bl00mbox_take_lock(&user_lock);
+    if(!bl00mbox_set_add_skip_unique_check(all_chans, chan)) goto failed;
+    bl00mbox_give_lock(&user_lock);
+
     return chan;
+
+failed:
+    if(chan){
+        if(chan->channel_plugin){
+            // supress errors
+            chan->channel_plugin->parent_self_ref = &chan->channel_plugin;
+            bl00mbox_plugin_destroy(chan->channel_plugin);
+        }
+        if(chan->render_lock) bl00mbox_delete_lock(&chan->render_lock);
+        free(chan);
+    }
+    bl00mbox_log_error("channel allocation failed");
+    return NULL;
 }
-bl00mbox_channel_t * bl00mbox_channel_create(){
-    bl00mbox_channel_t * chan = _bl00mbox_channel_create();
-    if(!chan) bl00mbox_log_error("channel allocation failed");
-    return chan;
+
+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);
+    }
+    chan->plugin_id = 0;
 }
 
-void  bl00mbox_channel_delete(bl00mbox_channel_t * chan){
-    if(!chan) return;
+void bl00mbox_channel_destroy(bl00mbox_channel_t * chan){
+    // destroy all plugins/connections
+    bl00mbox_channel_clear(chan);
+    bl00mbox_plugin_destroy(chan->channel_plugin);
+
+    // remove from parent
+    if(* (chan->parent_self_ref) != chan){
+        bl00mbox_log_error("channel: parent_self_ref improper");
+    }
+    * (chan->parent_self_ref) = NULL;
+
+    bl00mbox_take_lock(&user_lock);
     if(cached_chan == chan) cached_chan = NULL;
+    bool is_active = false;
     if(foreground_chan == chan){
-        bl00mbox_take_lock(&render_lock);
         foreground_chan = NULL;
-        bl00mbox_give_lock(&render_lock);
+        is_active = true;
+    }
+    if(bl00mbox_set_remove(background_mute_override_chans, chan)){
+        is_active = true;
     }
-    bl00mbox_ll_pop(&background_mute_override_chans, chan, &render_lock);
-    bl00mbox_ll_pop(&all_chans, chan, NULL);
-    bl00mbox_channel_clear(chan->index);
+    if(is_active) update_active_chans();
+    bl00mbox_set_remove(all_chans, chan);
+    bl00mbox_give_lock(&user_lock);
+
+#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
+
     // be really sure that nobody else holds the lock. the renderer
     // doesn't at this point, but if there's multiple tasks running
     // clients there may be collisions.
@@ -146,165 +199,77 @@ void  bl00mbox_channel_delete(bl00mbox_channel_t * chan){
     // it's okay, we can add the feature easily by just wrapping _all_
     // client api in a lock at some point in the future.
     bl00mbox_delete_lock(&chan->render_lock);
+    free(chan->render_plugins);
+    free(chan->render_buffers);
     free(chan->name);
     free(chan);
 }
 
-bool bl00mbox_channel_get_free(int32_t channel_index){
-    // TODO: deprecate
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel_index);
-    return !chan;
-}
-
-bool bl00mbox_channel_set_free(int32_t channel_index, bool set_free){
-    // TODO: deprecate
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel_index);
-    if(!chan) return false;
-    if(set_free) bl00mbox_channel_delete(chan);
-    return true;
-}
-
-int32_t bl00mbox_channel_get_free_index(){
-    bl00mbox_channel_t * chan = bl00mbox_channel_create();
-    if(chan) return chan->index;
-    return -1;
-}
-
-
-char * bl00mbox_channel_get_name(int32_t channel_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel_index);
-    if(!chan) return NULL;
-    return chan->name;
-}
-
-void bl00mbox_channel_set_name(int32_t channel_index, char * new_name){
-    bl00mbox_channel_t * ch =  bl00mbox_get_channel(channel_index);
-    if(!ch) return;
-    if(ch->name != NULL) free(ch->name);
-    ch->name = strdup(new_name);
-}
-
-void bl00mbox_channel_enable(int32_t chan){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return;
-    ch->is_active = true;
-}
-
-void bl00mbox_channel_disable(int32_t chan){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return;
-    ch->is_active = false;
-}
-
-void bl00mbox_channel_set_compute_mean_square(int32_t chan, bool compute){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return;
-    ch->compute_mean_square = compute;
-    if(!compute) ch->mean_square = 0;
-}
-
-bool bl00mbox_channel_get_compute_mean_square(int32_t chan){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return 0;
-    return ch->compute_mean_square;
-}
-
-uint32_t bl00mbox_channel_get_mean_square(int32_t chan){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return 0;
-    return ch->mean_square;
-}
-
-void bl00mbox_channel_set_sys_gain(int32_t chan, int16_t volume){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return;
-    ch->sys_gain = volume;
-}
-
-int16_t bl00mbox_channel_get_sys_gain(int32_t chan){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return 0;
-    return ch->sys_gain;
-}
-
-void bl00mbox_channel_set_volume(int32_t chan, uint16_t volume){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return;
-    ch->volume = volume < 32767 ? volume : 32767;
-}
-
-int16_t bl00mbox_channel_get_volume(int32_t chan){
-    bl00mbox_channel_t * ch = bl00mbox_get_channel(chan);
-    if(!ch) return 0;
-    return ch->volume;
-}
-
-void bl00mbox_audio_bud_render(bl00mbox_bud_t * bud){
-    if(bud->render_pass_id == render_pass_id) return;
+void bl00mbox_audio_plugin_render(bl00mbox_plugin_t * plugin){
+    if(plugin->render_pass_id == render_pass_id) return;
 #ifdef BL00MBOX_LOOPS_ENABLE
-    if(bud->is_being_rendered) return;
+    if(plugin->is_being_rendered) return;
 #endif
-    bud->is_being_rendered = true;
-    bud->plugin->render(bud->plugin, full_buffer_len, render_pass_id);
-    bud->render_pass_id = render_pass_id;
-    bud->is_being_rendered = false;
+    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;
 
-    bl00mbox_bud_list_t * always = chan->always_render;
-    while(always != NULL){
-        bl00mbox_audio_bud_render(always->bud);
-        always = always->next;
+    int32_t vol = radspa_mult_shift(chan->volume, chan->sys_gain);
+    if(!vol) return false; // don't render if muted
+
+    // 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_channel_root_t * root = chan->root_list;
+    bl00mbox_channel_plugin_update_values(chan->channel_plugin->rugin, render_pass_id);
 
-    int32_t vol = radspa_mult_shift(chan->volume, chan->sys_gain);
+    if(!(chan->render_buffers && chan->render_buffers->len)) return false;
 
-    // early exit when no sources or muted:
-    if((root == NULL) || (!vol)){
-        return false;
-    }
 
     int32_t acc[full_buffer_len];
-    bool acc_init = false;
 
-    while(root != NULL){
-        bl00mbox_audio_bud_render(root->con->source_bud);
-        if(root->con->buffer[1] == -32768){
-            if(!acc_init){
-                for(uint16_t i = 0; i < full_buffer_len; i++){
-                    acc[i] = root->con->buffer[0];
-                }
-                acc_init = true;
-            } else if(root->con->buffer[0]){
-                for(uint16_t i = 0; i < full_buffer_len; i++){
-                    acc[i] += root->con->buffer[0];
+    // 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++){
+            acc[i] = buffer[0];
+        }
+    } else {
+        for(size_t i = 0; i < full_buffer_len; i++){
+            acc[i] = buffer[i];
+        }
+    }
+
+    // rest adding
+    for(size_t i = 1; i < chan->render_buffers->len; i++){
+        buffer = chan->render_buffers->elems[i];
+        if(buffer[1] == -32768){
+            if(buffer[0]){
+                for(size_t i = 0; i < full_buffer_len; i++){
+                    acc[i] += buffer[0];
                 }
             }
         } else {
-            if(!acc_init){
-                for(uint16_t i = 0; i < full_buffer_len; i++){
-                    acc[i] = root->con->buffer[i];
-                }
-                acc_init = true;
-            } else {
-                for(uint16_t i = 0; i < full_buffer_len; i++){
-                    acc[i] += root->con->buffer[i];
-                }
+            for(size_t i = 0; i < full_buffer_len; i++){
+                acc[i] += buffer[i];
             }
         }
-        root = root->next;
     }
 
     for(uint16_t i = 0; i < full_buffer_len; i++){
         // flip around for rounding towards zero/mulsh boost
-        bool invert = chan->dc < 0;
-        if(invert) chan->dc = -chan->dc;
+        int invert = chan->dc < 0 ? -1 : 1;
+        chan->dc = chan->dc * invert;
         chan->dc = ((uint64_t) chan->dc * (((1<<12) - 1)<<20)) >> 32;
-        if(invert) chan->dc = -chan->dc;
+        chan->dc = chan->dc * invert;
 
         chan->dc += acc[i];
 
@@ -319,7 +284,7 @@ static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t *
             out[i] = radspa_gain(acc[i], vol);
         }
     }
-    if(chan->compute_mean_square){
+    if(chan->compute_rms){
         for(uint16_t i = 0; i < full_buffer_len; i++){
             int32_t sq = acc[i];
             sq = (sq * sq) - chan->mean_square;
@@ -333,52 +298,51 @@ static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t *
 }
 
 static bool bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t * out, bool adding){
-    if(!chan) return false;
-    if(!chan->is_active) return false;
     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;
     return ret;
 }
 
-static bool _bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len){
-    if(!is_initialized) return false;
-
-    render_pass_id++; // fresh pass, all relevant sources must be recomputed
+void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len){
     full_buffer_len = len/2;
     bl00mbox_line_in_interlaced = rx;
     int16_t acc[full_buffer_len];
     bool acc_init = false;
 
-    bl00mbox_take_lock(&render_lock);
-
-    // re-rendering channels is ok, if render_pass_id didn't change it will just exit
-    acc_init = bl00mbox_audio_channel_render(foreground_chan, acc, acc_init) || acc_init;
-    bl00mbox_ll_t * chll = background_mute_override_chans;
-    while(chll){
-        acc_init = bl00mbox_audio_channel_render(chll->content, acc, acc_init) || acc_init;
-        chll = chll->next;
+    bl00mbox_take_lock(&active_chans_lock);
+    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;
+        }
     }
+    bl00mbox_give_lock(&active_chans_lock);
 
-    bl00mbox_give_lock(&render_lock);
-
-    if(!acc_init) return false;
-
-    for(uint16_t i = 0; i < full_buffer_len; i++){
-        tx[2*i] = acc[i];
-        tx[2*i+1] = acc[i];
+    if(acc_init){
+        for(uint16_t i = 0; i < full_buffer_len; i++){
+            tx[2*i] = acc[i];
+            tx[2*i+1] = acc[i];
+        }
+    } else {
+        memset(tx, 0, len * sizeof(int16_t));
     }
-    return true;
-}
-
-void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len){
-    if(!_bl00mbox_audio_render(rx, tx, len)) memset(tx, 0, len*sizeof(int16_t));
 }
 
 void bl00mbox_audio_init(){
-    if(render_lock) abort();
-    bl00mbox_create_lock(&render_lock);
-    if(!render_lock) abort();
-    is_initialized = true;
+    assert(bl00mbox_create_lock(&active_chans_lock));
+
+    // micropython objects are generally not thread safe, so for most of the user API we need
+    // not care about locking after they've been created. however, bl00mbox Channel objects may
+    // be created by different threads (i.e., thread API), so these sets must be fully thread safe.
+    assert(bl00mbox_create_lock(&user_lock));
+
+    all_chans = calloc(1, sizeof(bl00mbox_set_t));
+    assert(all_chans);
+
+    background_mute_override_chans = calloc(1, sizeof(bl00mbox_set_t));
+    assert(background_mute_override_chans);
 }
diff --git a/components/bl00mbox/bl00mbox_containers.c b/components/bl00mbox/bl00mbox_containers.c
new file mode 100644
index 0000000000..097d57d32b
--- /dev/null
+++ b/components/bl00mbox/bl00mbox_containers.c
@@ -0,0 +1,86 @@
+#include "bl00mbox_containers.h"
+
+static bool equals(void * some, void * other, bl00mbox_set_key_t key){
+    if(!key) return some == other;
+    return key(some, other);
+}
+
+static bool set_add_inner(bl00mbox_set_t * set, void * content){
+    bl00mbox_ll_t * ll = malloc(sizeof(bl00mbox_ll_t));
+    if(!ll) return false;
+    ll->content = content;
+    ll->next = set->start;
+    set->start = ll;
+    set->len++;
+    return true;
+}
+
+bool bl00mbox_set_contains(bl00mbox_set_t * set, void * content){
+    if(!content) return false; // NULL pointers can't be in set
+    if(!set->start) return false;
+    bl00mbox_ll_t * seek = set->start;
+    while(seek){
+        if(equals(seek->content, content, set->key)) break;
+        seek = seek->next;
+    }
+    return seek;
+}
+
+bool bl00mbox_set_add_skip_unique_check(bl00mbox_set_t * set, void * content){
+    if(!content) return false; // don't allow for NULL pointers in set
+#ifdef BL00MBOX_DEBUG
+    if(bl00mbox_set_contains(set, content)){
+        bl00mbox_log_error("set corrupted");
+        return false;
+    }
+#endif
+    return set_add_inner(set, content);
+}
+
+bool bl00mbox_set_add(bl00mbox_set_t * set, void * content){
+    if(bl00mbox_set_contains(set, content)) return false;
+    return set_add_inner(set, content);
+}
+
+bool bl00mbox_set_remove(bl00mbox_set_t * set, void * content){
+    if(!content) return false;
+    bl00mbox_ll_t * seek = set->start;
+    bl00mbox_ll_t * prev = NULL;
+    while(seek){
+        if(equals(seek->content, content, set->key)) break;
+        prev = seek;
+        seek = seek->next;
+    }
+    if(seek){
+        bl00mbox_ll_t ** target = prev ? &(prev->next) : &(set->start);
+        (* target) = seek->next;
+        set->len--;
+    }
+    free(seek);
+    return true;
+}
+
+void bl00mbox_set_iter_start(bl00mbox_set_iter_t * iter, bl00mbox_set_t * set){
+    iter->next = set->start;
+}
+
+void * bl00mbox_set_iter_next(bl00mbox_set_iter_t * iter){
+    if(!iter->next) return NULL;
+    void * ret = iter->next->content;
+    iter->next = iter->next->next;
+    return ret;
+}
+
+bl00mbox_array_t * bl00mbox_set_to_array(bl00mbox_set_t * set){
+    bl00mbox_array_t * ret = malloc(sizeof(bl00mbox_array_t) + set->len * sizeof(void *));
+    if(!ret) return NULL;
+    ret->len = set->len;
+    bl00mbox_set_iter_t iter;
+    bl00mbox_set_iter_start(&iter, set);
+    void * content;
+    size_t index = 0;
+    while((content = bl00mbox_set_iter_next(&iter))){
+        ret->elems[index++] = content;
+    }
+    return ret;
+}
diff --git a/components/bl00mbox/bl00mbox_os.c b/components/bl00mbox/bl00mbox_os.c
index 9f7807012f..09507598c2 100644
--- a/components/bl00mbox/bl00mbox_os.c
+++ b/components/bl00mbox/bl00mbox_os.c
@@ -1,10 +1,17 @@
 #include "bl00mbox_os.h"
 
 #ifdef BL00MBOX_FREERTOS
-void bl00mbox_create_lock(bl00mbox_lock_t * lock){ * lock = xSemaphoreCreateMutex(); }
-void bl00mbox_delete_lock(bl00mbox_lock_t * lock){ if(lock) vSemaphoreDelete(* lock); }
-void bl00mbox_take_lock(bl00mbox_lock_t * lock){ if(lock) xSemaphoreTake(* lock, portMAX_DELAY); }
-void bl00mbox_give_lock(bl00mbox_lock_t * lock){ if(lock) xSemaphoreGive(* lock); }
+bool bl00mbox_create_lock(bl00mbox_lock_t * lock){
+    assert(*lock == NULL);
+    * lock = xSemaphoreCreateMutex();
+    return(*lock != NULL);
+}
+void bl00mbox_delete_lock(bl00mbox_lock_t * lock){ vSemaphoreDelete(* lock); }
+void bl00mbox_take_lock(bl00mbox_lock_t * lock){ xSemaphoreTake(* lock, portMAX_DELAY); }
+void bl00mbox_give_lock(bl00mbox_lock_t * lock){ xSemaphoreGive(* lock); }
+#ifdef BL00MBOX_DEBUG
+void bl00mbox_wait() { vTaskDelay(10); }
+#endif
 #endif
 
 #if defined(BL00MBOX_ESPIDF)
diff --git a/components/bl00mbox/bl00mbox_plugin_registry.c b/components/bl00mbox/bl00mbox_plugin_registry.c
index d7120f5be6..766ed67aa4 100644
--- a/components/bl00mbox/bl00mbox_plugin_registry.c
+++ b/components/bl00mbox/bl00mbox_plugin_registry.c
@@ -1,19 +1,25 @@
 //SPDX-License-Identifier: CC0-1.0
 #include "bl00mbox_plugin_registry.h"
 
-bl00mbox_plugin_registry_t * bl00mbox_plugin_registry = NULL;
-uint16_t bl00mbox_plugin_registry_len = 0;
-bool bl00mbox_plugin_registry_is_initialized = false;
+static bl00mbox_plugin_registry_t * bl00mbox_plugin_registry = NULL;
+static uint16_t bl00mbox_plugin_registry_len = 0;
+static bool bl00mbox_plugin_registry_is_initialized = false;
 
 static void plugin_add(radspa_descriptor_t * descriptor){
+    if(descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID){
+        bl00mbox_log_error("plugin list id collision");
+    }
     if(bl00mbox_plugin_registry_len == 65535){
-        printf("too many plugins registered");
-        abort();
+        bl00mbox_log_error("too many plugins registered");
+        return;
     }
 
     // create plugin registry entry
     bl00mbox_plugin_registry_t * p = malloc(sizeof(bl00mbox_plugin_registry_t));
-    if(p == NULL){ printf("bl00mbox: no memory for plugin list"); abort(); }
+    if(!p){
+        bl00mbox_log_error("no memory for plugin list");
+        return;
+    }
     p->descriptor = descriptor;
     p->next = NULL;
 
@@ -24,8 +30,8 @@ static void plugin_add(radspa_descriptor_t * descriptor){
     } else {
         while(plast->next != NULL){
             if(plast->descriptor->id == p->descriptor->id){
-                printf("bl00mbox: plugin list id collision");
-                abort();
+                bl00mbox_log_error("plugin list id collision");
+                return;
             }
             plast = plast->next;
         }
@@ -58,7 +64,7 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_descriptor_from_index(uint32_
     for(uint16_t i = 0; i < index; i++){
         p = p->next;
         if(p == NULL){
-            printf("bl00mbox: plugin list length error");
+            bl00mbox_log_error("bl00mbox: plugin list length error");
             abort();
         }
     }
@@ -73,7 +79,7 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index)
     for(uint16_t i = 0; i < index; i++){
         p = p->next;
         if(p == NULL){
-            printf("bl00mbox: plugin list length error");
+            bl00mbox_log_error("bl00mbox: plugin list length error");
             abort();
         }
     }
@@ -87,9 +93,8 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index)
  * - use plugin_add in bl00mbox_plugin_registry_init as
  *   exemplified below
  *
- * NOTE: the plugin registry linked list is intended to be filled
- * once at boot time. dynamically adding plugins at runtime may or
- * may not work (but will call abort() if no memory is available),
+ * NOTE: the plugin registry linked list is intended to be filled once at
+ * boot time. dynamically adding plugins at runtime may or may not work,
  * removing plugins from the registry at runtime is not intended.
  */
 
@@ -113,9 +118,11 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index)
 #include "range_shifter.h"
 #include "poly_squeeze.h"
 #include "bl00mbox_line_in.h"
+#include "buffer.h"
 
 void bl00mbox_plugin_registry_init(void){
     if(bl00mbox_plugin_registry_is_initialized) return;
+    // do not add bl00mbox_channel_plugin, it is not to be spawned by users
     plugin_add(&osc_desc);
     plugin_add(&filter_desc);
     plugin_add(&sequencer_desc);
@@ -123,19 +130,18 @@ void bl00mbox_plugin_registry_init(void){
     plugin_add(&multipitch_desc);
     plugin_add(&trigger_merge_desc);
     plugin_add(&bl00mbox_line_in_desc);
+    plugin_add(&buffer_desc);
     plugin_add(&distortion_desc);
     plugin_add(&mixer_desc);
     plugin_add(&flanger_desc);
     plugin_add(&noise_desc);
     plugin_add(&noise_burst_desc);
-    plugin_add(&env_adsr_desc);   
-    plugin_add(&delay_desc);   
-
+    plugin_add(&env_adsr_desc);
+    plugin_add(&delay_desc);
     plugin_add(&range_shifter_desc);
     plugin_add(&poly_squeeze_desc);
     plugin_add(&slew_rate_limiter_desc);
     plugin_add(&ampliverter_desc);
-
     plugin_add(&osc_fm_desc);
-    plugin_add(&lowpass_desc);   
+    plugin_add(&lowpass_desc);
 }
diff --git a/components/bl00mbox/bl00mbox_radspa_requirements.c b/components/bl00mbox/bl00mbox_radspa_requirements.c
index 3887ac5c04..509d58854c 100644
--- a/components/bl00mbox/bl00mbox_radspa_requirements.c
+++ b/components/bl00mbox/bl00mbox_radspa_requirements.c
@@ -2,8 +2,8 @@
 #include "bl00mbox_radspa_requirements.h"
 
 bool radspa_host_request_buffer_render(int16_t * buf){
-    bl00mbox_bud_t * bud = ((bl00mbox_connection_t *) buf)->source_bud;
-    bl00mbox_audio_bud_render(bud);
+    bl00mbox_plugin_t * plugin = ((bl00mbox_connection_t *) buf)->source.plugin;
+    bl00mbox_audio_plugin_render(plugin);
     return 1;
 }
 
diff --git a/components/bl00mbox/bl00mbox_user.c b/components/bl00mbox/bl00mbox_user.c
index 3d81973a09..e80f8d8b90 100644
--- a/components/bl00mbox/bl00mbox_user.c
+++ b/components/bl00mbox/bl00mbox_user.c
@@ -6,865 +6,482 @@ radspa_signal_t * bl00mbox_signal_get_by_index(radspa_t * plugin, uint16_t signa
     return &(plugin->signals[signal_index]);
 }
 
-static uint64_t bl00mbox_bud_index = 1;
-bl00mbox_bud_t * bl00mbox_channel_get_bud_by_index(int32_t channel, uint32_t index);
-
-uint16_t bl00mbox_channel_buds_num(int32_t channel){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    uint16_t ret = 0;
-    if(chan->buds != NULL){
-        bl00mbox_bud_t * last = chan->buds;
-        ret++;
-        while(last->chan_next != NULL){
-            last = last->chan_next;
-            ret++;
-        } 
-    }
-    return ret;
+static inline bl00mbox_connection_t * conn_from_signal(bl00mbox_signal_t * signal){
+    return (bl00mbox_connection_t *) signal->rignal->buffer;
 }
 
-uint64_t bl00mbox_channel_get_bud_by_list_pos(int32_t channel, uint32_t pos){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    uint16_t ret = 0;
-    if(chan->buds != NULL){
-        bl00mbox_bud_t * last = chan->buds;
-        if(pos == ret) return last->index;
-        ret++;
-        while(last->chan_next != NULL){
-            last = last->chan_next;
-            if(pos == ret) return last->index;
-            ret++;
-        } 
-    }
-    return 0;
+static bool signal_is_input(bl00mbox_signal_t * signal){
+    return signal->rignal->hints & RADSPA_SIGNAL_HINT_INPUT ? true : false;
 }
 
-uint16_t bl00mbox_channel_conns_num(int32_t channel){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    uint16_t ret = 0;
-    if(chan->connections != NULL){
-        bl00mbox_connection_t * last = chan->connections;
-        ret++;
-        while(last->chan_next != NULL){
-            last = last->chan_next;
-            ret++;
-        } 
-    }
-    return ret;
+static bool signal_is_output(bl00mbox_signal_t * signal){
+    return signal->rignal->hints & RADSPA_SIGNAL_HINT_OUTPUT ? true : false;
 }
 
-uint16_t bl00mbox_channel_mixer_num(int32_t channel){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    uint16_t ret = 0;
-    if(chan->root_list != NULL){
-        bl00mbox_channel_root_t * last = chan->root_list;
-        ret++;
-        while(last->next != NULL){
-            last = last->next;
-            ret++;
-        } 
-    }
-    return ret;
+static bool signal_equals(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
+    return (some->plugin == other->plugin) && (some->index == other->index);
 }
 
-uint64_t bl00mbox_channel_get_bud_by_mixer_list_pos(int32_t channel, uint32_t pos){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    uint16_t ret = 0;
-    if(chan->buds != NULL){
-        bl00mbox_channel_root_t * last = chan->root_list;
-        if(pos == ret) return last->con->source_bud->index;
-        ret++;
-        while(last->next != NULL){
-            last = last->next;
-            if(pos == ret) return last->con->source_bud->index;
-            ret++;
-        } 
-    }
-    return 0;
+static bool signals_are_connected(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
+    if(!some->rignal->buffer) return false;
+    return some->rignal->buffer == other->rignal->buffer;
 }
 
-uint32_t bl00mbox_channel_get_signal_by_mixer_list_pos(int32_t channel, uint32_t pos){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    uint16_t ret = 0;
-    if(chan->buds != NULL){
-        bl00mbox_channel_root_t * last = chan->root_list;
-        if(pos == ret) return last->con->signal_index;
-        ret++;
-        while(last->next != NULL){
-            last = last->next;
-            if(pos == ret) return last->con->signal_index;
-            ret++;
-        } 
-    }
-    return 0;
+bool bl00mbox_signal_is_output(bl00mbox_signal_t * signal){
+    return signal_is_output(signal);
 }
 
-uint16_t bl00mbox_channel_subscriber_num(int32_t channel, uint64_t bud_index, uint16_t signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return 0;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return 0;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, signal_index);
-    if(sig == NULL) return 0;
-    bl00mbox_connection_t * conn = (bl00mbox_connection_t *) sig->buffer; // buffer sits on top of struct
-    if(conn == NULL) return 0;
-
-    uint16_t ret = 0;
-    if(conn->subs != NULL){
-        bl00mbox_connection_subscriber_t * last = conn->subs;
-        ret++;
-        while(last->next != NULL){
-            last = last->next;
-            ret++;
-        } 
-    }
-    return ret;
+bool bl00mbox_signal_is_input(bl00mbox_signal_t * signal){
+    return signal_is_input(signal);
 }
 
-uint64_t bl00mbox_channel_get_bud_by_subscriber_list_pos(int32_t channel, uint64_t bud_index,
-                uint16_t signal_index, uint8_t pos){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return 0;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return 0;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, signal_index);
-    if(sig == NULL) return 0;
-    bl00mbox_connection_t * conn = (bl00mbox_connection_t *) sig->buffer; // buffer sits on top of struct
-    if(conn == NULL) return 0;
-
-    uint16_t ret = 0;
-    if(conn->subs != NULL){
-        bl00mbox_connection_subscriber_t * last = conn->subs;
-        if(pos == ret) return (last->type == 0) ? last->bud_index : 0;
-        ret++;
-        while(last->next != NULL){
-            last = last->next;
-            if(pos == ret) return (last->type == 0) ? last->bud_index : 0;
-            ret++;
-        } 
-    }
-    return 0;
+bl00mbox_connection_t * bl00mbox_connection_from_signal(bl00mbox_signal_t * signal){
+    return conn_from_signal(signal);
 }
 
-int32_t bl00mbox_channel_get_signal_by_subscriber_list_pos(int32_t channel, uint64_t bud_index,
-                uint16_t signal_index, uint8_t pos){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return 0;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return 0;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, signal_index);
-    if(sig == NULL) return 0;
-    bl00mbox_connection_t * conn = (bl00mbox_connection_t *) sig->buffer; // buffer sits on top of struct
-    if(conn == NULL) return 0;
-
-    uint16_t ret = 0;
-    if(conn->subs != NULL){
-        bl00mbox_connection_subscriber_t * last = conn->subs;
-        if(pos == ret) return (last->type == 0) ? last->signal_index : -1;
-        ret++;
-        while(last->next != NULL){
-            last = last->next;
-            if(pos == ret) return (last->type == 0) ? last->signal_index : -1;
-            ret++;
-        } 
-    }
-    return 0;
+uint16_t bl00mbox_channel_plugins_num(bl00mbox_channel_t * chan){
+    return chan->plugins.len;
 }
 
-uint64_t bl00mbox_channel_get_source_bud(int32_t channel, uint64_t bud_index, uint16_t signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return 0;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return 0;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, signal_index);
-    if(sig == NULL) return 0;
-    bl00mbox_connection_t * conn = (bl00mbox_connection_t *) sig->buffer; // buffer sits on top of struct
-    if(conn == NULL) return 0;
-    return conn->source_bud->index;
-}
+bl00mbox_array_t * bl00mbox_channel_collect_plugins(bl00mbox_channel_t * chan){
+    unsigned int len = chan->plugins.len;
+    bl00mbox_array_t * ret;
+    ret = malloc(sizeof(bl00mbox_array_t) + len * sizeof(void *));
+    if(!ret) return NULL;
+    ret->len = len;
 
-uint16_t bl00mbox_channel_get_source_signal(int32_t channel, uint64_t bud_index, uint16_t signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return 0;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return 0;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, signal_index);
-    if(sig == NULL) return 0;
-    bl00mbox_connection_t * conn = (bl00mbox_connection_t *) sig->buffer; // buffer sits on top of struct
-    if(conn == NULL) return 0;
-    return conn->signal_index;
+    bl00mbox_set_iter_t iter;
+    bl00mbox_set_iter_start(&iter, &chan->plugins);
+    bl00mbox_plugin_t * plugin;
+    size_t i = 0;
+    while((plugin = bl00mbox_set_iter_next(&iter))) ret->elems[i++] = plugin;
+    return ret;
 }
 
-static bl00mbox_connection_t * create_connection(int32_t channel){
-    bl00mbox_connection_t * ret = malloc(sizeof(bl00mbox_connection_t));
-    if(ret == NULL) return NULL;
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    ret->chan_next = NULL;
-    ret->subs = NULL;
-    ret->channel = channel;
-
-    if(chan->connections != NULL){
-        bl00mbox_connection_t * last = chan->connections;
-        while(last->chan_next != NULL){
-            last = last->chan_next;
-        }
-        last->chan_next = ret;
-    } else {
-        chan->connections = ret;
-    }
-    return ret;
+uint16_t bl00mbox_channel_conns_num(bl00mbox_channel_t * chan){
+    return chan->connections.len;
 }
 
-static bool weak_delete_connection(bl00mbox_connection_t * conn){
-    if(conn->subs != NULL) return false;
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(conn->channel);
-
-    // nullify source bud connection;
-    bl00mbox_bud_t * bud = conn->source_bud;
-    if(bud != NULL){
-        radspa_signal_t * tx = bl00mbox_signal_get_by_index(bud->plugin, conn->signal_index);
-        if(tx != NULL){
-            bl00mbox_take_lock(&chan->render_lock);
-            tx->buffer = NULL;
-            bl00mbox_give_lock(&chan->render_lock);
-        }
-    }
+uint16_t bl00mbox_channel_mixer_num(bl00mbox_channel_t * chan){
+    return chan->roots.len;
+}
 
-    // pop from channel list
-    if(chan->connections != NULL){
-        if(chan->connections != conn){
-            bl00mbox_connection_t * prev = chan->connections;
-            while(prev->chan_next != conn){
-                prev = prev->chan_next;
-                if(prev->chan_next == NULL){
-                    break;
-                }
-            }
-            if(prev->chan_next != NULL){
-                bl00mbox_take_lock(&chan->render_lock);
-                prev->chan_next = conn->chan_next;
-                bl00mbox_give_lock(&chan->render_lock);
-            }
-        } else {
-            bl00mbox_take_lock(&chan->render_lock);
-            chan->connections = conn->chan_next;
-            bl00mbox_give_lock(&chan->render_lock);
-        }
-    }
+bl00mbox_array_t * bl00mbox_channel_collect_connections_mx(bl00mbox_channel_t * chan){
+    bl00mbox_array_t * ret;
+    ret = malloc(sizeof(bl00mbox_array_t) + chan->roots.len * sizeof(void *));
+    if(!ret) return NULL;
 
-    free(conn);
-    return true;
-}
+    ret->len = chan->roots.len;
 
-bl00mbox_bud_t * bl00mbox_channel_get_bud_by_index(int32_t channel, uint32_t index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return NULL;
-    if(chan->buds == NULL) return NULL;
-    bl00mbox_bud_t * bud = chan->buds;
-    while(true){
-        if(bud->index == index) break;
-        bud = bud->chan_next;
-        if(bud == NULL) break;
+    bl00mbox_set_iter_t iter;
+    bl00mbox_set_iter_start(&iter, &chan->roots);
+    bl00mbox_connection_t * conn;
+    size_t i = 0;
+    while((conn = bl00mbox_set_iter_next(&iter))){
+        ret->elems[i++] = &conn->source;
     }
-    return bud;
+    return ret;
 }
 
-bl00mbox_bud_t * bl00mbox_channel_new_bud(int32_t channel, uint32_t id, uint32_t init_var){
-    /// creates a new bud instance of the plugin with descriptor id "id" and the initialization variable
-    /// "init_var" and appends it to the plugin list of the corresponding channel. returns pointer to
-    /// the bud if successfull, else NULL.
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return NULL;
-    radspa_descriptor_t * desc = bl00mbox_plugin_registry_get_descriptor_from_id(id);
-    if(desc == NULL) return NULL;
-    bl00mbox_bud_t * bud = malloc(sizeof(bl00mbox_bud_t));
-    if(bud == NULL) return NULL;
-    radspa_t * plugin = desc->create_plugin_instance(init_var);
-    if(plugin == NULL){ free(bud); return NULL; }
-
-    bud->init_var = init_var;
-    bud->plugin = plugin;
-    bud->channel = channel;
-    bud->is_being_rendered = false;
-    //TODO: look for empty indices? maybe?
-    bud->index = bl00mbox_bud_index;
-    bud->always_render = false;
-    bl00mbox_bud_index++;
-    bud->chan_next = NULL;
-
-    // append to channel bud list
-    if(chan->buds == NULL){
-        chan->buds = bud;
+bl00mbox_array_t * bl00mbox_signal_collect_connections(bl00mbox_signal_t * signal){
+    // caller has to free memory
+    // return NULL -> OOM error
+    bl00mbox_array_t * ret = NULL;
+    bl00mbox_connection_t * conn = conn_from_signal(signal);
+    if(!conn){
+        ret = malloc(sizeof(bl00mbox_array_t));
+        if(!ret) return NULL;
+        ret->len = 0;
+    } else if(signal->rignal->hints & RADSPA_SIGNAL_HINT_INPUT){
+        ret = malloc(sizeof(bl00mbox_array_t) + sizeof(void *));
+        if(!ret) return NULL;
+        ret->len = 1;
+        ret->elems[0] = &conn->source;
     } else {
-        bl00mbox_bud_t * last = chan->buds;
-        while(last->chan_next != NULL){ last = last->chan_next; } 
-        last->chan_next = bud;
+        // TODO: add sentinel for channel mixer connection
+        ret = malloc(sizeof(bl00mbox_array_t) + conn->subscribers.len * sizeof(void *));
+        if(!ret) return NULL;
+        ret->len = conn->subscribers.len;
+
+        bl00mbox_set_iter_t iter;
+        bl00mbox_set_iter_start(&iter, &conn->subscribers);
+        bl00mbox_signal_t * sub;
+        size_t i = 0;
+        while((sub = bl00mbox_set_iter_next(&iter))){
+            ret->elems[i++] = sub;
+        }
     }
-    bl00mbox_channel_event(channel);
-    return bud;
+    return ret;
 }
 
-bool bl00mbox_channel_delete_bud(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-
-    // disconnect all signals
-    uint16_t num_signals = bl00mbox_channel_bud_get_num_signals(channel, bud_index);
-    for(uint16_t i = 0; i < num_signals; i++){
-        bl00mbox_channel_disconnect_signal(channel, bud_index, i);
-    }
-
-    // pop from always list
-    bl00mbox_channel_bud_set_always_render(channel, bud_index, false);
-
-    // pop from channel bud list
-    bl00mbox_bud_t * seek = chan->buds;
-    bool free_later = false;
-    if(chan->buds != NULL){
-        bl00mbox_bud_t * prev = NULL;
-        while(seek != NULL){
-            if(seek->index == bud_index){
+static void update_roots(bl00mbox_channel_t * chan){
+    // create list of plugins to be rendered, i.e. all roots and always_actives
+    bl00mbox_set_t * roots = &chan->roots; // content: bl00mbox_connection_t
+    bl00mbox_set_t * always_render = &chan->always_render; // content: bl00mbox_plugin_t
+    radspa_signal_t * output_rignal = &chan->channel_plugin->rugin->signals[1];
+    
+    // render_buffers are simple, just copy the content of the set and add the output plugin manually
+    int num_render_buffers = roots->len + (output_rignal->buffer ? 1 : 0);
+    bl00mbox_array_t * render_buffers = malloc(sizeof(bl00mbox_array_t) + sizeof(void *) * num_render_buffers);
+    // for render_plugins there may be duplicates. we still allocate full memory for now
+    int num_render_plugins = roots->len + always_render->len + (output_rignal->buffer ? 1 : 0);
+    bl00mbox_array_t * render_plugins = malloc(sizeof(bl00mbox_array_t) + sizeof(void *) * num_render_plugins);
+    if(!(render_buffers && render_plugins)) goto defer;
+
+    size_t index = 0;
+
+    bl00mbox_set_iter_t iter;
+    // filling up mixer roots
+    bl00mbox_set_iter_start(&iter, roots);
+    bl00mbox_connection_t * conn;
+    while((conn = bl00mbox_set_iter_next(&iter))){
+        render_buffers->elems[index] = conn->buffer;
+        render_plugins->elems[index] = conn->source.plugin;
+        index++;
+    }
+    render_buffers->len = index;
+
+    // if someone is connected to the channel_plugin output:
+    // add to render_buffers/render_plugin
+    if(output_rignal->buffer){
+        render_buffers->elems[index] = output_rignal->buffer;
+        render_buffers->len++;
+
+        conn = output_rignal->buffer;
+        bl00mbox_plugin_t * plugin = conn->source.plugin;
+        bool is_duplicate = false;
+        for(size_t rindex = 0; rindex < index; rindex++){
+            if(render_plugins->elems[rindex] == plugin){
+                is_duplicate = true;
                 break;
             }
-            prev = seek;
-            seek = seek->chan_next;
         }
-        if(seek != NULL){
-            bl00mbox_take_lock(&chan->render_lock);
-            if(prev != NULL){
-                prev->chan_next = seek->chan_next;
-            } else {
-                chan->buds = seek->chan_next;
+        if(!is_duplicate){
+            render_plugins->elems[index++] = conn->source.plugin;
+        }
+    }
+
+    // adding always_render to the plugin list. those should be after the regular roots
+    // because we might mess with natural render order otherwise
+    size_t duplicate_index = index;
+    bl00mbox_set_iter_start(&iter, always_render);
+    bl00mbox_plugin_t * plugin;
+    while((plugin = bl00mbox_set_iter_next(&iter))){
+        // check for duplicates
+        bool is_duplicate = false;
+        for(size_t rindex = 0; rindex < duplicate_index; rindex++){
+            if(render_plugins->elems[rindex] == plugin){
+                is_duplicate = true;
+                break;
             }
-            bl00mbox_give_lock(&chan->render_lock);
-            free_later = true;
         }
+        if(!is_duplicate) render_plugins->elems[index++] = plugin;
     }
+    render_plugins->len = index;
 
-    bud->plugin->descriptor->destroy_plugin_instance(bud->plugin);
-    if(free_later) free(seek);
-    return true;
-}
+    // if we overshot let's trim excess memory.
+    if(index != num_render_plugins){
+        bl00mbox_array_t * tmp = realloc(render_plugins, sizeof(bl00mbox_array_t) + sizeof(void *) * index);
+        if(tmp) render_plugins = tmp;
+    }
 
-bool bl00mbox_channel_clear(int32_t channel){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = chan->buds;
-    while(bud != NULL){
-        bl00mbox_bud_t * bud_next = bud->chan_next;
-        bl00mbox_channel_delete_bud(channel, bud->index);
-        bud = bud_next;
+defer:
+    if(!(render_plugins && render_buffers)){
+        free(render_plugins);
+        free(render_buffers);
+        render_plugins = NULL;
+        render_buffers = NULL;
+        bl00mbox_log_error("out of memory, render list cleared")
     }
-    return true;
+#ifdef BL00MBOX_DEBUG
+    else {
+        bl00mbox_log_info("new render data, %d plugins, %d buffers",
+        (int) render_plugins->len, (int) render_buffers->len);
+    }
+#endif
+    bl00mbox_array_t * render_plugins_prev = chan->render_plugins;
+    bl00mbox_array_t * render_buffers_prev = chan->render_buffers;
+    bl00mbox_take_lock(&chan->render_lock);
+    chan->render_plugins = render_plugins;
+    chan->render_buffers = render_buffers;
+    bl00mbox_give_lock(&chan->render_lock);
+    free(render_plugins_prev);
+    free(render_buffers_prev);
 }
 
-bool bl00mbox_channel_connect_signal_to_output_mixer(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    radspa_signal_t * tx = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(tx == NULL) return false;
-    if(!(tx->hints & RADSPA_SIGNAL_HINT_OUTPUT)) return false;
+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
+    bl00mbox_plugin_t * plugin = calloc(1, sizeof(bl00mbox_plugin_t));
+    if(plugin == NULL) return NULL;
+    radspa_t * rugin = desc->create_plugin_instance(init_var);
+    if(rugin == NULL){ free(plugin); return NULL; }
 
-    bl00mbox_channel_root_t * root = malloc(sizeof(bl00mbox_channel_root_t));
-    if(root == NULL) return false;
-    bl00mbox_connection_subscriber_t * sub = malloc(sizeof(bl00mbox_connection_subscriber_t));
-    if(sub == NULL){ free(root); return false; }
-
-    bl00mbox_connection_t * conn;
-    if(tx->buffer == NULL){ // doesn't feed a buffer yet
-        conn = create_connection(channel);
-        if(conn == NULL){
-            free(sub);
-            free(root);
-            return false;
-        }
-        // set up new connection
-        conn->signal_index = bud_signal_index;
-        conn->source_bud = bud;
-        tx->buffer = conn->buffer;
-    } else {
-        conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct
-        if(conn->subs != NULL){
-            bl00mbox_connection_subscriber_t * seek = conn->subs;
-            while(seek != NULL){
-                if(seek->type == 1){
-                    free(root);
-                    free(sub);
-                    return false;  // already connected
-                }
-                seek = seek->next;
-            }
-        }
-    }
+    plugin->init_var = init_var;
+    plugin->rugin = rugin;
+    plugin->channel = chan;
+    plugin->id = chan->plugin_id++;
+    return plugin;
+}
 
-    sub->type = 1;
-    sub->bud_index = bud_index;
-    sub->signal_index = bud_signal_index;
-    sub->next = NULL;
-    if(conn->subs == NULL){
-        conn->subs = sub;
-    } else {
-        bl00mbox_connection_subscriber_t * seek = conn->subs;
-        while(seek->next != NULL){ seek = seek->next; }
-        seek->next = sub;
-    }
+bl00mbox_plugin_t * bl00mbox_plugin_create(bl00mbox_channel_t * chan, 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;
 
-    root->con = conn;
-    root->next = NULL;
+    bl00mbox_plugin_t * plugin = bl00mbox_plugin_create_unlisted(chan, desc, init_var);
 
-    if(chan->root_list == NULL){
-        chan->root_list = root;
-    } else {
-        bl00mbox_channel_root_t * last_root = chan->root_list;
-        while(last_root->next != NULL){ last_root = last_root->next; }
-        last_root->next = root;
+    if(!bl00mbox_set_add_skip_unique_check(&chan->plugins, plugin)){
+        plugin->rugin->descriptor->destroy_plugin_instance(plugin->rugin);
+        free(plugin);
+        return NULL;
     }
 
-    bl00mbox_channel_event(channel);
-    return true;
+    bl00mbox_channel_event(chan);
+    return plugin;
 }
 
-bool bl00mbox_channel_disconnect_signal_from_output_mixer(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index){
-    //TODO
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    radspa_signal_t * tx = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(tx == NULL) return false;
-    if(tx->buffer == NULL) return false;
-    if(!(tx->hints & RADSPA_SIGNAL_HINT_OUTPUT)) return false;
-
-    bl00mbox_connection_t * conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct
-    if(conn == NULL) return false; //not connected
-
-    bl00mbox_channel_root_t * rt = chan->root_list;
-    bl00mbox_channel_root_t * rt_prev = NULL;
-
-    while(rt != NULL){
-        if(rt->con == conn) break;
-        rt_prev = rt;
-        rt = rt->next;
-    }
-    if(rt == NULL) return false; // root doesn't exist
+void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin){
+    bl00mbox_channel_t * chan = plugin->channel;
 
-    bl00mbox_take_lock(&chan->render_lock);
-    if(rt_prev == NULL){
-        chan->root_list = rt->next;
-    } else {
-        rt_prev->next = rt->next;
+    // remove from parent
+    if(* (plugin->parent_self_ref) != plugin){
+        bl00mbox_log_error("plugin: parent_self_ref improper");
     }
-    bl00mbox_give_lock(&chan->render_lock);
-    free(rt);
+    * (plugin->parent_self_ref) = NULL;
 
-    if(conn->subs != NULL){
-        bl00mbox_connection_subscriber_t * seek = conn->subs;
-        bl00mbox_connection_subscriber_t * prev = NULL;
-        while(seek != NULL){
-            if(seek->type == 1){
-                break;
-            }
-            prev = seek;
-            seek = seek->next;
-        }
-        if(seek != NULL){
-            bl00mbox_take_lock(&chan->render_lock);
-            if(prev != NULL){
-                prev->next = seek->next;
-            } else {
-                conn->subs = seek->next;
-            }
-            bl00mbox_give_lock(&chan->render_lock);
-            free(seek);
-        }
+    // disconnect all signals
+    int num_signals = plugin->rugin->len_signals;
+    for(int i = 0; i < num_signals; i++){
+        bl00mbox_signal_t sig = {
+            .index = i,
+            .plugin = plugin,
+            .rignal = &(plugin->rugin->signals[i])
+        };
+        bl00mbox_signal_disconnect(&sig);
     }
 
-    weak_delete_connection(conn);
-    bl00mbox_channel_event(channel);
-    return true;
-}
+    // pop from sets
+    bl00mbox_plugin_set_always_render(plugin, false);
+    bl00mbox_set_remove(&chan->plugins, plugin);
 
-bool bl00mbox_channel_disconnect_signal_rx(int32_t channel, uint32_t bud_rx_index, uint32_t bud_rx_signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    bl00mbox_bud_t * bud_rx = bl00mbox_channel_get_bud_by_index(channel, bud_rx_index);
-    if(bud_rx == NULL) return false; // bud index doesn't exist
+    plugin->rugin->descriptor->destroy_plugin_instance(plugin->rugin);
+    free(plugin);
+}
 
-    radspa_signal_t * rx = bl00mbox_signal_get_by_index(bud_rx->plugin, bud_rx_signal_index);
-    if(rx == NULL) return false; // signal index doesn't exist
-    if(rx->buffer ==  NULL) return false;
-    if(!(rx->hints & RADSPA_SIGNAL_HINT_INPUT)) return false;
 
-    bl00mbox_connection_t * conn = (bl00mbox_connection_t *) rx->buffer; // buffer sits on top of struct
-    if(conn == NULL) return false; //not connected
+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->subscribers.key = signal_equals;
+    memcpy(&ret->source, source, sizeof(bl00mbox_signal_t));
 
-    bl00mbox_bud_t * bud_tx = conn->source_bud;
-    if(bud_tx == NULL) return false; // bud index doesn't exist
-    radspa_signal_t * tx = bl00mbox_signal_get_by_index(bud_tx->plugin, conn->signal_index);
-    if(tx == NULL) return false; // signal index doesn't exist
+    if(!bl00mbox_set_add_skip_unique_check(&source->plugin->channel->connections, ret)){
+        free(ret);
+        return NULL;
+    }
 
+    bl00mbox_channel_t * chan = source->plugin->channel;
     bl00mbox_take_lock(&chan->render_lock);
-    rx->buffer = NULL;
+    source->rignal->buffer = &ret->buffer;
     bl00mbox_give_lock(&chan->render_lock);
+    return ret;
+}
 
-    if(conn->subs != NULL){
-        bl00mbox_connection_subscriber_t * seek = conn->subs;
-        bl00mbox_connection_subscriber_t * prev = NULL;
-        while(seek != NULL){
-            if( (seek->signal_index == bud_rx_signal_index) &&
-                (seek->bud_index == bud_rx_index) &&
-                (seek->type == 0)){
-                break;
-            }
-            prev = seek;
-            seek = seek->next;
-        }
-        if(seek != NULL){
-            bl00mbox_take_lock(&chan->render_lock);
-            if(prev != NULL){
-                prev->next = seek->next;
-            } else {
-                conn->subs = seek->next;
-            }
-            bl00mbox_give_lock(&chan->render_lock);
-            free(seek);
-        }
-    }
-
-    weak_delete_connection(conn);
-    bl00mbox_channel_event(channel);
-    return true;
+static bl00mbox_connection_t * weak_create_connection(bl00mbox_signal_t * source){
+    bl00mbox_connection_t * conn = conn_from_signal(source);
+    if(conn) return conn;
+    conn = create_connection(source);
+    return conn;
 }
 
-bool bl00mbox_channel_disconnect_signal_tx(int32_t channel, uint32_t bud_tx_index, uint32_t bud_tx_signal_index){
-    bl00mbox_bud_t * bud_tx = bl00mbox_channel_get_bud_by_index(channel, bud_tx_index);
-    if(bud_tx == NULL) return false; // bud index doesn't exist
+static bool weak_delete_connection(bl00mbox_connection_t * conn){
+    // are there still active connections?
+    if(conn->subscribers.len || conn->connected_to_mixer) return false;
 
-    radspa_signal_t * tx = bl00mbox_signal_get_by_index(bud_tx->plugin, bud_tx_signal_index);
-    if(tx == NULL) return false; // signal index doesn't exist
-    if(tx->buffer ==  NULL) return false;
-    if(!(tx->hints & RADSPA_SIGNAL_HINT_OUTPUT)) return false;
+    bl00mbox_channel_t * chan = conn->source.plugin->channel;
 
-    bl00mbox_connection_t * conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct
-    if(conn == NULL) return false; //not connected
+    // disconnect buffer from plugin
+    bl00mbox_take_lock(&chan->render_lock);
+    conn->source.rignal->buffer = NULL;
+    bl00mbox_give_lock(&chan->render_lock);
 
-    while(conn->subs != NULL){
-        switch(conn->subs->type){
-            case 0:
-                bl00mbox_channel_disconnect_signal_rx(channel, conn->subs->bud_index, conn->subs->signal_index);
-                break;
-            case 1:
-                bl00mbox_channel_disconnect_signal_from_output_mixer(channel, conn->subs->bud_index, conn->subs->signal_index);
-                break;
-        }
-    }
-    bl00mbox_channel_event(channel);
-    return true;
-}
+    // remove from connections list
+    if(!bl00mbox_set_remove(&chan->connections, conn)) bl00mbox_log_error("connection list corruption");
 
-bool bl00mbox_channel_disconnect_signal(int32_t channel, uint32_t bud_index, uint32_t signal_index){
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false; // bud index doesn't exist
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, signal_index);
-    if(sig == NULL) return false; // signal index doesn't exist
-    if(sig->buffer ==  NULL) return false;
-
-    bl00mbox_channel_disconnect_signal_rx(channel, bud_index, signal_index);
-    bl00mbox_channel_disconnect_signal_tx(channel, bud_index, signal_index);
-    bl00mbox_channel_disconnect_signal_from_output_mixer(channel, bud_index, signal_index);
-    if(sig->buffer ==  NULL) return true;
-    return false;
+#ifdef BL00MBOX_DEBUG
+    if(conn->subscribers.len) bl00mbox_log_error("subscribers nonempty");
+#endif
+
+    free(conn);
+    return true;
 }
 
-bool bl00mbox_channel_connect_signal(int32_t channel, uint32_t bud_rx_index, uint32_t bud_rx_signal_index,
-                                               uint32_t bud_tx_index, uint32_t bud_tx_signal_index){
-    bl00mbox_bud_t * bud_rx = bl00mbox_channel_get_bud_by_index(channel, bud_rx_index);
-    bl00mbox_bud_t * bud_tx = bl00mbox_channel_get_bud_by_index(channel, bud_tx_index);
-    if(bud_tx == NULL || bud_rx == NULL) return false; // bud index doesn't exist
+bl00mbox_error_t bl00mbox_signal_connect_mx(bl00mbox_signal_t * signal){
+    if(!signal_is_output(signal)) return BL00MBOX_ERROR_INVALID_CONNECTION;
+    bl00mbox_channel_t * chan = signal->plugin->channel;
 
-    radspa_signal_t * rx = bl00mbox_signal_get_by_index(bud_rx->plugin, bud_rx_signal_index);
-    radspa_signal_t * tx = bl00mbox_signal_get_by_index(bud_tx->plugin, bud_tx_signal_index);
-    if(tx == NULL || rx == NULL) return false; // signal index doesn't exist
-    if(!(rx->hints & RADSPA_SIGNAL_HINT_INPUT)) return false;
-    if(!(tx->hints & RADSPA_SIGNAL_HINT_OUTPUT)) return false;
+    bl00mbox_connection_t * conn = weak_create_connection(signal);
+    if(!conn) goto failed;
 
-    bl00mbox_connection_t * conn;
-    bl00mbox_connection_subscriber_t * sub;
-    if(tx->buffer == NULL){ // doesn't feed a buffer yet
-        conn = create_connection(channel);
-        if(conn == NULL) return false; // no ram for connection
-        // set up new connection
-        conn->signal_index = bud_tx_signal_index;
-        conn->source_bud = bud_tx;
-        tx->buffer = conn->buffer;
-    } else {
-        if(rx->buffer == tx->buffer) return false; // already connected
-        conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct
-    }
+    // check if already connected
+    if(conn->connected_to_mixer) return BL00MBOX_ERROR_OK;
+    conn->connected_to_mixer = true;
+    if(!bl00mbox_set_add(&chan->roots, conn)) goto failed;
 
-    bl00mbox_channel_disconnect_signal_rx(channel, bud_rx_index, bud_rx_signal_index);
+    update_roots(chan);
+    bl00mbox_channel_event(chan);
+    return BL00MBOX_ERROR_OK;
 
-    sub = malloc(sizeof(bl00mbox_connection_subscriber_t));
-    if(sub == NULL){
+failed:
+    if(conn){
+        conn->connected_to_mixer = false;
         weak_delete_connection(conn);
-        return false;
-    }
-    sub->type = 0;
-    sub->bud_index = bud_rx_index;
-    sub->signal_index = bud_rx_signal_index;
-    sub->next = NULL;
-    if(conn->subs == NULL){
-        conn->subs = sub;
-    } else {
-        bl00mbox_connection_subscriber_t * seek = conn->subs;
-        while(seek->next != NULL){
-            seek = seek->next;
-        }
-        seek->next = sub;
     }
-
-    rx->buffer = tx->buffer;
-    bl00mbox_channel_event(channel);
-    return true;
+    bl00mbox_log_error("couldn't connect to mixer");
+    return BL00MBOX_ERROR_OOM;
 }
 
-bool bl00mbox_channel_bud_exists(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL){
-        return false;
-    } else {
-        return true;
-    }
-}
+static void bl00mbox_signal_disconnect_mx(bl00mbox_signal_t * signal){
+    if(!signal_is_output(signal)) return;
+    bl00mbox_connection_t * conn = conn_from_signal(signal);
+    if(conn == NULL) return; //not connected
 
-char * bl00mbox_channel_bud_get_name(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    return bud->plugin->descriptor->name;
-}
+    conn->connected_to_mixer = false;
+    bl00mbox_channel_t * chan = signal->plugin->channel;
+    bl00mbox_set_remove(&chan->roots, conn);
+    update_roots(chan);
 
-char * bl00mbox_channel_bud_get_description(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    return bud->plugin->descriptor->description;
+    weak_delete_connection(conn);
+    bl00mbox_channel_event(chan);
 }
 
-uint32_t bl00mbox_channel_bud_get_plugin_id(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    return bud->plugin->descriptor->id;
-}
+static void bl00mbox_signal_disconnect_rx(bl00mbox_signal_t * signal){
+    if(!signal_is_input(signal)) return;
 
-uint32_t bl00mbox_channel_bud_get_init_var(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    return bud->init_var;
-}
+    // try to get connection and return if not connected
+    bl00mbox_connection_t * conn = conn_from_signal(signal);
+    if(!conn) return;
 
-uint16_t bl00mbox_channel_bud_get_num_signals(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    return bud->plugin->len_signals;
-}
+    bl00mbox_channel_t * chan = signal->plugin->channel;
+    bl00mbox_take_lock(&chan->render_lock);
+    signal->rignal->buffer = NULL;
+    bl00mbox_give_lock(&chan->render_lock);
 
-char * bl00mbox_channel_bud_get_signal_name(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(sig == NULL) return false;
-    return sig->name;
-}
+    if(signal->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) update_roots(chan);
 
-int8_t bl00mbox_channel_bud_get_signal_name_multiplex(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(sig == NULL) return false;
-    return sig->name_multiplex;
+    bl00mbox_set_remove(&conn->subscribers, signal);
+    weak_delete_connection(conn);
+    bl00mbox_channel_event(signal->plugin->channel);
 }
 
-char * bl00mbox_channel_bud_get_signal_description(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(sig == NULL) return false;
-    return sig->description;
-}
+void bl00mbox_signal_disconnect_tx(bl00mbox_signal_t * signal){
+    if(!signal_is_output(signal)) return;
+    bl00mbox_connection_t * conn = conn_from_signal(signal);
+    if(!conn) return;
+
+    // disconnect from mixer
+    if(conn->connected_to_mixer) bl00mbox_signal_disconnect_mx(signal);
+
+    // disconnect all subscribers
+    bl00mbox_set_iter_t iter;
+    bl00mbox_set_iter_start(&iter, &conn->subscribers);
+    bl00mbox_signal_t * sub;
+    while((sub = bl00mbox_set_iter_next(&iter))){
+        bl00mbox_signal_disconnect_rx(sub);
+    }
 
-char * bl00mbox_channel_bud_get_signal_unit(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(sig == NULL) return false;
-    return sig->unit;
+    bl00mbox_channel_event(signal->plugin->channel);
+    return;
 }
 
-bool bl00mbox_channel_bud_get_always_render(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    return bud->always_render;
+void bl00mbox_signal_disconnect(bl00mbox_signal_t * signal){
+    if(!conn_from_signal(signal)) return;
+    if(signal_is_input(signal)){
+        bl00mbox_signal_disconnect_rx(signal);
+    }
+    if(signal_is_output(signal)){
+        bl00mbox_signal_disconnect_tx(signal);
+        bl00mbox_signal_disconnect_mx(signal);
+    }
+    if(conn_from_signal(signal)) bl00mbox_log_error("connection persists after disconnect");
 }
 
-bool bl00mbox_channel_bud_set_always_render(int32_t channel, uint32_t bud_index, bool value){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    if(bud->always_render == value) return false;
+bl00mbox_error_t bl00mbox_signal_connect(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
+    // are signals on the same channel?
+    if(some->plugin->channel != other->plugin->channel) return BL00MBOX_ERROR_INVALID_CONNECTION;
+    bl00mbox_channel_t * chan = some->plugin->channel;
 
-    if(value){
-        bl00mbox_bud_list_t * new = malloc(sizeof(bl00mbox_bud_list_t));
-        new->bud = bud;
-        new->next = NULL;
-        if(chan->always_render == NULL){
-            bl00mbox_take_lock(&chan->render_lock);
-            chan->always_render = new;
-            bl00mbox_give_lock(&chan->render_lock);
-        } else{
-            bl00mbox_bud_list_t * end = chan->always_render;
-            while(end->next != NULL){
-                end = end->next;
-            }
-            bl00mbox_take_lock(&chan->render_lock);
-            end->next = new;
-            bl00mbox_give_lock(&chan->render_lock);
-        }
+    // which one is input, which one is output?
+    bl00mbox_signal_t * signal_rx;
+    bl00mbox_signal_t * signal_tx;
+    if(signal_is_input(some) && signal_is_output(other)){
+        signal_rx = some;
+        signal_tx = other;
+    } else if(signal_is_input(other) && signal_is_output(some)){
+        signal_rx = other;
+        signal_tx = some;
     } else {
-        if(chan->always_render != NULL){
-            bl00mbox_bud_list_t * seek = chan->always_render;
-            bl00mbox_bud_list_t * prev = NULL;
-            while(seek != NULL){
-                if(seek->bud->index == bud_index){
-                    break;
-                }
-                prev = seek;
-                seek = seek->next;
-            }
-            if(seek != NULL){
-                bl00mbox_take_lock(&chan->render_lock);
-                if(prev != NULL){
-                    prev->next = seek->next;
-                } else {
-                    chan->always_render = seek->next;
-                }
-                bl00mbox_give_lock(&chan->render_lock);
-                free(seek);
-            }
-        }
+        return BL00MBOX_ERROR_INVALID_CONNECTION;
     }
 
-    bud->always_render = value;
-    bl00mbox_channel_event(channel);
-    return true;
-}
-
-bool bl00mbox_channel_bud_set_signal_value(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index, int16_t value){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(sig == NULL) return false;
-    while(bud->is_being_rendered) {};
-
-    if(value == -32678){
-        sig->value = -32767;
+    bl00mbox_connection_t * conn;
+    if(!(conn = conn_from_signal(signal_tx))){
+        if(!(conn = create_connection(signal_tx))) return BL00MBOX_ERROR_OOM;
     } else {
-        sig->value = value;
+        if(signals_are_connected(signal_rx, signal_tx)) return BL00MBOX_ERROR_OK; // already connected
     }
-    bl00mbox_channel_event(channel);
-    return true;
-}
 
-int16_t bl00mbox_channel_bud_get_signal_value(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return -32768;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return -32768;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(sig == NULL) return -32768;
-    while(bud->is_being_rendered) {};
-
-    if(sig->buffer != NULL){
-        return sig->buffer[0];
-    } else {
-        return sig->value;
+    bl00mbox_signal_disconnect_rx(signal_rx);
+
+    bl00mbox_signal_t * sub;
+
+    sub = malloc(sizeof(bl00mbox_signal_t));
+    if(!sub){
+        weak_delete_connection(conn);
+        return BL00MBOX_ERROR_OOM;
     }
-}
 
-uint32_t bl00mbox_channel_bud_get_signal_hints(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index);
-    if(sig == NULL) return false;
+    memcpy(sub, signal_rx, sizeof(bl00mbox_signal_t));
 
-    return sig->hints;
-}
+    bl00mbox_set_add(&conn->subscribers, sub);
 
-bool bl00mbox_channel_bud_set_table_value(int32_t channel, uint32_t bud_index, uint32_t table_index, int16_t value){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    if(bud->plugin->plugin_table == NULL) return false;
-    if(table_index >= bud->plugin->plugin_table_len) return false;
-    while(bud->is_being_rendered) {};
-    bud->plugin->plugin_table[table_index] = value;
-    bl00mbox_channel_event(channel);
-    return true;
+    // we must block here else we could access the buffer of a constant signal
+    // and think it's nonconst, which is bad
+    bl00mbox_take_lock(&chan->render_lock);
+    signal_rx->rignal->buffer = signal_tx->rignal->buffer;
+    bl00mbox_give_lock(&chan->render_lock);
+
+    if(signal_rx->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) update_roots(chan);
+
+    bl00mbox_channel_event(chan);
+    return BL00MBOX_ERROR_OK;
 }
 
-int16_t bl00mbox_channel_bud_get_table_value(int32_t channel, uint32_t bud_index, uint32_t table_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    if(bud->plugin->plugin_table == NULL) return false;
-    if(table_index >= bud->plugin->plugin_table_len) return false;
-    while(bud->is_being_rendered) {};
-    return bud->plugin->plugin_table[table_index];
+void bl00mbox_plugin_set_always_render(bl00mbox_plugin_t * plugin, bool value){
+    bl00mbox_channel_t * chan = plugin->channel;
+    if(plugin->always_render == value) return;
+    plugin->always_render = value;
+
+    if(value){
+        bl00mbox_set_add(&chan->always_render, plugin);
+    } else {
+        bl00mbox_set_remove(&chan->always_render, plugin);
+    }
+    update_roots(chan);
+
+    bl00mbox_channel_event(chan);
 }
 
-int16_t * bl00mbox_channel_bud_get_table_pointer(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return false;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return false;
-    if(bud->plugin->plugin_table == NULL) return false;
-    return bud->plugin->plugin_table;
+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);
+    return BL00MBOX_ERROR_OK;
 }
 
-uint32_t bl00mbox_channel_bud_get_table_len(int32_t channel, uint32_t bud_index){
-    bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
-    if(chan == NULL) return 0;
-    bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
-    if(bud == NULL) return 0;
-    return bud->plugin->plugin_table_len;
+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;
 }
diff --git a/components/bl00mbox/config/bl00mbox_config.h b/components/bl00mbox/config/bl00mbox_config.h
index 38171d8dc5..37a2ddeefb 100644
--- a/components/bl00mbox/config/bl00mbox_config.h
+++ b/components/bl00mbox/config/bl00mbox_config.h
@@ -1,6 +1,8 @@
 #pragma once
 #define BL00MBOX_FLOW3R
 
+//#define BL00MBOX_DEBUG
+
 #ifdef BL00MBOX_FLOW3R
 //#include "flow3r_bsp.h"
 //#define BL00MBOX_MAX_BUFFER_LEN FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE
@@ -10,4 +12,18 @@
 #define BL00MBOX_LOOPS_ENABLE
 #define BL00MBOX_FREERTOS
 #define BL00MBOX_ESPIDF
+// note: this option relies on implementation details of the garbage
+// collector, specifically the fact that it is stop-the-world and
+// collects all unreachable objects it can find before it gives control
+// back to micropython. changing it from stop-the-world to anything
+// else is to our knowledge not possible without rewriting all mutators,
+// so we consider this aspect future proof. also we see no reason to
+// not collect all of them since sweeping is very fast, unless it is
+// outsourced to a separate sweeping task, in which case this option
+// may result in use-after-free, but we don't really see that happening
+// anytime soon.
+// if you want to use this option in such an environment however,
+// setting a ChannelCore's callback to None before dropping all
+// references to it solves the issue.
+#define BL00MBOX_CALLBACKS_FUNSAFE
 #endif
diff --git a/components/bl00mbox/include/bl00mbox_audio.h b/components/bl00mbox/include/bl00mbox_audio.h
index 0a4490c54c..b23af0aaa9 100644
--- a/components/bl00mbox/include/bl00mbox_audio.h
+++ b/components/bl00mbox/include/bl00mbox_audio.h
@@ -2,6 +2,7 @@
 #pragma once
 #include "bl00mbox_config.h"
 #include "bl00mbox_os.h"
+#include "bl00mbox_containers.h"
 
 #include <stdio.h>
 #include <math.h>
@@ -9,98 +10,95 @@
 #include "radspa.h"
 #include "radspa_helpers.h"
 
-struct _bl00mbox_bud_t;
+struct _bl00mbox_plugin_t;
 struct _bl00mbox_connection_source_t;
 struct _bl00mbox_channel_root_t;
 struct _bl00mbox_channel_t;
 
 extern int16_t * bl00mbox_line_in_interlaced;
 
-typedef struct _bl00mbox_bud_t{
-    radspa_t * plugin; // plugin
+// pointer is unique identifier, no memcpy of this!
+typedef struct _bl00mbox_plugin_t{
+    radspa_t * rugin; // radspa plugin
     char * name;
-    uint64_t index; // unique index number for bud
+    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
-    int32_t channel; // index of channel that owns the plugin
     volatile bool is_being_rendered; // true if rendering the plugin is in progress, else false.
     bool always_render;
-    struct _bl00mbox_bud_t * chan_next; //for linked list in bl00mbox_channel_t
-} bl00mbox_bud_t;
-
-typedef struct _bl00mbox_bud_list_t{
-    struct _bl00mbox_bud_t * bud;
-    struct _bl00mbox_bud_list_t * next;
-} bl00mbox_bud_list_t;
-
-typedef struct _bl00mbox_connection_subscriber_t{
-    uint8_t type; // 0: standard signal input, 1: output mixer
-    int32_t channel;
-    uint64_t bud_index;
-    uint32_t signal_index;
-    struct _bl00mbox_connection_subscriber_t * next;
-} bl00mbox_connection_subscriber_t;
+    struct _bl00mbox_channel_t * channel; // channel that owns the plugin
+
+    void * parent;
+    struct _bl00mbox_plugin_t ** parent_self_ref;
+} bl00mbox_plugin_t;
+
+// pointer is NOT unique identifier, memcpy allowed
+typedef struct {
+    bl00mbox_plugin_t * plugin;
+    radspa_signal_t * rignal;
+    int index;
+} 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!
-    struct _bl00mbox_bud_t * source_bud;
-    uint32_t signal_index; // signal of source_bud that renders to buffer
-    struct _bl00mbox_connection_subscriber_t * subs;
-    int32_t channel;
-    struct _bl00mbox_connection_t * chan_next; //for linked list in bl00mbox_channel_t;
+    int16_t buffer[BL00MBOX_MAX_BUFFER_LEN]; // 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
 } bl00mbox_connection_t;
 
-typedef struct _bl00mbox_channel_root_t{
-    struct _bl00mbox_connection_t * con;
-    struct _bl00mbox_channel_root_t * next;
-} bl00mbox_channel_root_t;
-
-typedef struct{
-    int32_t index;
-    bl00mbox_lock_t render_lock;
-    bool is_active; // rendering can be skipped if false
-    bool compute_mean_square;
-    uint32_t mean_square;
+// pointer is unique identifier, no memcpy of this!
+typedef struct _bl00mbox_channel_t{
     char * name;
+
     int32_t volume;
+    bool background_mute_override;
+
+    // secondary gain that the channel user is supposed to leave untouched so that the OS
+    // can change gain too, for example for a system mixer. we still want to apply both in the
+    // same pass, so we keep it here.
     int32_t sys_gain;
+
+    // whether to keep track of the rms volume of the channel. adds a bit of cpu load, best
+    // keep it off if not used.
+    bool compute_rms;
+    // we are storing the un-rooted value. this means that if you want the value in decibels
+    // 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;
-    struct _bl00mbox_channel_root_t * root_list; // list of all roots associated with channels
+
     uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary
-    struct _bl00mbox_bud_t * buds; // linked list with all channel buds
-    struct _bl00mbox_bud_list_t * always_render; // linked list of buds that should always render
-    struct _bl00mbox_connection_t * connections; // linked list with all channel connections
+
+    uint32_t plugin_id; // used to give each plugin in channel a unique identifier
+
+    // 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
+
+    bl00mbox_plugin_t * channel_plugin;
+
+    bl00mbox_lock_t render_lock;
+    // take .render_lock before changing these
+    bl00mbox_array_t * render_plugins;
+    bl00mbox_array_t * render_buffers;
+
+    void * parent;
+    struct _bl00mbox_channel_t ** parent_self_ref;
 } bl00mbox_channel_t;
 
 void bl00mbox_audio_init();
 
-bl00mbox_channel_t * bl00mbox_get_channel(int32_t chan);
-void bl00mbox_channel_enable(int32_t chan);
-
-void bl00mbox_channel_enable(int32_t chan);
-void bl00mbox_channel_disable(int32_t chan);
-void bl00mbox_channel_set_volume(int32_t chan, uint16_t volume);
-int16_t bl00mbox_channel_get_volume(int32_t chan);
-void bl00mbox_channel_set_sys_gain(int32_t chan, int16_t volume);
-int16_t bl00mbox_channel_get_sys_gain(int32_t chan);
-void bl00mbox_channel_set_compute_mean_square(int32_t chan, bool compute);
-bool bl00mbox_channel_get_compute_mean_square(int32_t chan);
-uint32_t bl00mbox_channel_get_mean_square(int32_t chan);
-void bl00mbox_channel_event(int32_t chan);
-bool bl00mbox_channel_get_free(int32_t channel_index);
-bool bl00mbox_channel_set_free(int32_t channel_index, bool free);
-
-bool bl00mbox_channel_get_background_mute_override(int32_t channel_index);
-bool bl00mbox_channel_set_background_mute_override(int32_t channel_index, bool enable);
-
-char * bl00mbox_channel_get_name(int32_t channel_index);
-void bl00mbox_channel_set_name(int32_t channel_index, char * new_name);
-
-void bl00mbox_audio_bud_render(bl00mbox_bud_t * bud);
-
-bool bl00mbox_get_channel_exists(int32_t channel_index);
-int32_t bl00mbox_channel_get_free_index();
-int32_t bl00mbox_get_channel_index_positional_all_chans(int32_t position);
-int32_t bl00mbox_get_channel_index_positional_background_mute_override_chans(int32_t position);
-int32_t bl00mbox_channel_get_foreground_index();
-void bl00mbox_channel_set_foreground_index(int32_t channel_index);
+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);
+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);
diff --git a/components/bl00mbox/include/bl00mbox_containers.h b/components/bl00mbox/include/bl00mbox_containers.h
new file mode 100644
index 0000000000..71dfb6ea39
--- /dev/null
+++ b/components/bl00mbox/include/bl00mbox_containers.h
@@ -0,0 +1,36 @@
+#pragma once
+#include "bl00mbox_os.h"
+
+typedef struct _bl00mbox_ll_t {
+    struct _bl00mbox_ll_t * next;
+    void * content;
+} bl00mbox_ll_t;
+
+typedef struct {
+    size_t len;
+    void * elems[];
+} bl00mbox_array_t;
+
+typedef bool (* bl00mbox_set_key_t)(void *, void *);
+
+// initialize all zeroed out except for content type
+typedef struct {
+    bl00mbox_ll_t * start;
+    bl00mbox_set_key_t key;
+    size_t len;
+} bl00mbox_set_t;
+
+typedef struct {
+    bl00mbox_ll_t * next;
+}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);
+bool bl00mbox_set_add(bl00mbox_set_t * set, void * content);
+bool bl00mbox_set_remove(bl00mbox_set_t * set, void * content);
+
+// removing from set during iteration is safe, adding is NOT
+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);
diff --git a/components/bl00mbox/include/bl00mbox_ll.h b/components/bl00mbox/include/bl00mbox_ll.h
deleted file mode 100644
index 0d3a315805..0000000000
--- a/components/bl00mbox/include/bl00mbox_ll.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#pragma once
-#include "bl00mbox_os.h"
-
-typedef struct _bl00mbox_ll_t {
-    struct _bl00mbox_ll_t * next;
-    void * content;
-} bl00mbox_ll_t;
-
-bool bl00mbox_ll_contains(bl00mbox_ll_t ** startref, void * content){
-    if(!startref) return false; // list doesn't exist
-    bl00mbox_ll_t * seek = * startref;
-    if(!seek) return false; // list is empty
-    while(seek){
-        if(seek->content == content) break;
-        seek = seek->next;
-    }
-    return seek;
-}
-
-bool bl00mbox_ll_prepend(bl00mbox_ll_t ** startref, void * content, bl00mbox_lock_t * write_lock){
-    if(!startref) return false;
-    if(bl00mbox_ll_contains(startref, content)) return false;
-    bl00mbox_ll_t * ll = malloc(sizeof(bl00mbox_ll_t));
-    if(!ll) return false;
-    ll->content = content;
-    ll->next = *startref;
-    bl00mbox_take_lock(write_lock);
-    *startref = ll;
-    bl00mbox_give_lock(write_lock);
-    return true;
-}
-
-bool bl00mbox_ll_pop(bl00mbox_ll_t ** startref, void * content, bl00mbox_lock_t * write_lock){
-    if(!startref) return false; // list doesn't exist
-    bl00mbox_ll_t * seek = * startref;
-    if(!seek) return false; // list is empty
-    bl00mbox_ll_t * prev = NULL;
-    while(seek){
-        if(seek->content == content) break;
-        prev = seek;
-        seek = seek->next;
-    }
-    if(!seek) return false; // not found
-    bl00mbox_take_lock(write_lock);
-    if(prev){ // normal
-        prev->next = seek->next;
-    } else { // first element
-        * startref = seek->next;
-    }
-    bl00mbox_give_lock(write_lock);
-    free(seek);
-    return true;
-}
-
diff --git a/components/bl00mbox/include/bl00mbox_os.h b/components/bl00mbox/include/bl00mbox_os.h
index fbb422fa42..59964962a9 100644
--- a/components/bl00mbox/include/bl00mbox_os.h
+++ b/components/bl00mbox/include/bl00mbox_os.h
@@ -1,5 +1,13 @@
 #pragma once
 #include "bl00mbox_config.h"
+#include <stdbool.h>
+
+typedef enum {
+    BL00MBOX_ERROR_OK = false,
+    BL00MBOX_ERROR_OOM,
+    BL00MBOX_ERROR_INVALID_CONNECTION,
+    BL00MBOX_ERROR_INVALID_IDENTIFIER,
+} bl00mbox_error_t;
 
 #ifdef BL00MBOX_FREERTOS
 #include "freertos/FreeRTOS.h"
@@ -7,16 +15,25 @@
 typedef SemaphoreHandle_t bl00mbox_lock_t;
 #endif
 
-void bl00mbox_create_lock(bl00mbox_lock_t * lock);
+bool bl00mbox_create_lock(bl00mbox_lock_t * lock);
 void bl00mbox_delete_lock(bl00mbox_lock_t * lock);
 void bl00mbox_take_lock(bl00mbox_lock_t * lock);
 void bl00mbox_give_lock(bl00mbox_lock_t * lock);
 
+#ifdef BL00MBOX_DEBUG
+void bl00mbox_wait();
+#endif
+
 #ifdef BL00MBOX_ESPIDF
 #include "esp_log.h"
 #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__);
+#else
 #define bl00mbox_log_info(txt, ...) ESP_LOGI("bl00mbox", txt, ##__VA_ARGS__);
+#endif
 #else
 void bl00mbox_log_error(char * txt, ...);
 void bl00mbox_log_info(char * txt, ...);
 #endif
+
diff --git a/components/bl00mbox/include/bl00mbox_plugin_registry.h b/components/bl00mbox/include/bl00mbox_plugin_registry.h
index 077af67857..65e7293f41 100644
--- a/components/bl00mbox/include/bl00mbox_plugin_registry.h
+++ b/components/bl00mbox/include/bl00mbox_plugin_registry.h
@@ -2,6 +2,8 @@
 #pragma once
 #include "stdio.h"
 #include "radspa.h"
+#include "bl00mbox_os.h"
+#include "bl00mbox_channel_plugin.h"
 
 typedef struct _bl00mbox_plugin_registry_t{
     radspa_descriptor_t * descriptor;
diff --git a/components/bl00mbox/include/bl00mbox_user.h b/components/bl00mbox/include/bl00mbox_user.h
index 58bdd59f9b..254720aef1 100644
--- a/components/bl00mbox/include/bl00mbox_user.h
+++ b/components/bl00mbox/include/bl00mbox_user.h
@@ -8,54 +8,33 @@
 #include "bl00mbox_plugin_registry.h"
 #include "bl00mbox_audio.h"
 #include "bl00mbox_os.h"
+#include "bl00mbox_containers.h"
 
 #include <stdint.h>
 #include "bl00mbox_audio.h"
 #include "radspa_helpers.h"
 
-uint16_t bl00mbox_channel_buds_num(int32_t channel);
-uint64_t bl00mbox_channel_get_bud_by_list_pos(int32_t channel, uint32_t pos);
-uint16_t bl00mbox_channel_conns_num(int32_t channel);
-uint16_t bl00mbox_channel_mixer_num(int32_t channel);
-uint64_t bl00mbox_channel_get_bud_by_mixer_list_pos(int32_t channel, uint32_t pos);
-uint32_t bl00mbox_channel_get_signal_by_mixer_list_pos(int32_t channel, uint32_t pos);
-bool bl00mbox_channel_clear(int32_t channel);
-
-bool bl00mbox_channel_connect_signal_to_output_mixer(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index);
-bool bl00mbox_channel_connect_signal(int32_t channel, uint32_t bud_rx_index, uint32_t bud_rx_signal_index,
-                                               uint32_t bud_tx_index, uint32_t bud_tx_signal_index);
-bool bl00mbox_channel_disconnect_signal_rx(int32_t channel, uint32_t bud_rx_index, uint32_t bud_rx_signal_index);
-bool bl00mbox_channel_disconnect_signal_tx(int32_t channel, uint32_t bud_tx_index, uint32_t bud_tx_signal_index);
-bool bl00mbox_channel_disconnect_signal(int32_t channel, uint32_t bud_tx_index, uint32_t bud_tx_signal_index);
-bool bl00mbox_channel_disconnect_signal_from_output_mixer(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index);
-
-bl00mbox_bud_t * bl00mbox_channel_new_bud(int32_t channel, uint32_t id, uint32_t init_var);
-bool bl00mbox_channel_delete_bud(int32_t channel, uint32_t bud_index);
-bool bl00mbox_channel_bud_exists(int32_t channel, uint32_t bud_index);
-char * bl00mbox_channel_bud_get_name(int32_t channel, uint32_t bud_index);
-char * bl00mbox_channel_bud_get_description(int32_t channel, uint32_t bud_index);
-uint32_t bl00mbox_channel_bud_get_plugin_id(int32_t channel, uint32_t bud_index);
-uint32_t bl00mbox_channel_bud_get_init_var(int32_t channel, uint32_t bud_index);
-uint16_t bl00mbox_channel_bud_get_num_signals(int32_t channel, uint32_t bud_index);
-
-char * bl00mbox_channel_bud_get_signal_name(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index);
-int8_t bl00mbox_channel_bud_get_signal_name_multiplex(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index);
-char * bl00mbox_channel_bud_get_signal_description(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index);
-char * bl00mbox_channel_bud_get_signal_unit(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index);
-bool bl00mbox_channel_bud_get_always_render(int32_t channel, uint32_t bud_index);
-bool bl00mbox_channel_bud_set_always_render(int32_t channel, uint32_t bud_index, bool value);
-bool bl00mbox_channel_bud_set_signal_value(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index, int16_t value);
-int16_t bl00mbox_channel_bud_get_signal_value(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index);
-uint32_t bl00mbox_channel_bud_get_signal_hints(int32_t channel, uint32_t bud_index, uint32_t bud_signal_index);
-uint16_t bl00mbox_channel_subscriber_num(int32_t channel, uint64_t bud_index, uint16_t signal_index);
-uint64_t bl00mbox_channel_get_bud_by_subscriber_list_pos(int32_t channel, uint64_t bud_index,
-                uint16_t signal_index, uint8_t pos);
-int32_t bl00mbox_channel_get_signal_by_subscriber_list_pos(int32_t channel, uint64_t bud_index,
-                uint16_t signal_index, uint8_t pos);
-uint64_t bl00mbox_channel_get_source_bud(int32_t channel, uint64_t bud_index, uint16_t signal_index);
-uint16_t bl00mbox_channel_get_source_signal(int32_t channel, uint64_t bud_index, uint16_t signal_index);
-
-bool bl00mbox_channel_bud_set_table_value(int32_t channel, uint32_t bud_index, uint32_t table_index, int16_t value);
-int16_t bl00mbox_channel_bud_get_table_value(int32_t channel, uint32_t bud_index, uint32_t table_index);
-uint32_t bl00mbox_channel_bud_get_table_len(int32_t channel, uint32_t bud_index);
-int16_t * bl00mbox_channel_bud_get_table_pointer(int32_t channel, uint32_t bud_index);
+// lazy 2nd error channel: pointer return types resulting in NULL means OOM
+
+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);
+void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin);
+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);
+int16_t bl00mbox_signal_get_value(bl00mbox_signal_t * signal);
+bl00mbox_error_t bl00mbox_signal_connect(bl00mbox_signal_t * some, bl00mbox_signal_t * other);
+bl00mbox_error_t bl00mbox_signal_connect_mx(bl00mbox_signal_t * some);
+void bl00mbox_signal_disconnect(bl00mbox_signal_t * signal);
+bl00mbox_array_t * bl00mbox_signal_collect_connections(bl00mbox_signal_t * signal);
+
+bl00mbox_connection_t * bl00mbox_connection_from_signal(bl00mbox_signal_t * signal);
+
+bool bl00mbox_signal_is_input(bl00mbox_signal_t * signal);
+bool bl00mbox_signal_is_output(bl00mbox_signal_t * signal);
diff --git a/components/bl00mbox/micropython/bl00mbox/_patches.py b/components/bl00mbox/micropython/bl00mbox/_patches.py
index 3b2723a69d..b63552ea22 100644
--- a/components/bl00mbox/micropython/bl00mbox/_patches.py
+++ b/components/bl00mbox/micropython/bl00mbox/_patches.py
@@ -7,30 +7,48 @@ import cpython.wave as wave
 
 class _Patch:
     def __init__(self, chan):
-        self.plugins = _PatchPluginList()
         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
 
     def __repr__(self):
         ret = "[patch] " + type(self).__name__
         ret += "\n  [signals:]    " + "\n    ".join(repr(self.signals).split("\n"))
-        ret += "\n  [plugins:]    " + "\n    ".join(repr(self.plugins).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 ret
 
     def delete(self):
+        # new style
+        plugins = self._plugins[:]
+        # old style
         for plugin_name in self.plugins.__dict__:
             if not plugin_name.startswith("_"):
-                plugin = self.plugins.__dict__[plugin_name]
-                if type(plugin) == list:
-                    for p in plugin:
-                        p.delete()
-                else:
-                    plugin.delete()
+                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 = []
+        # workaround
 
     def __iter__(self):
         return iter(self._items)
@@ -44,11 +62,10 @@ class _PatchItemList:
         return ret
 
     def __setattr__(self, key, value):
+        # old style
         current_value = getattr(self, key, None)
-
         if current_value is None and not key.startswith("_"):
             self._items.append(key)
-
         super().__setattr__(key, value)
 
 
@@ -57,8 +74,8 @@ class _PatchSignalList(_PatchItemList):
         current_value = getattr(self, key, None)
         if isinstance(current_value, bl00mbox.Signal):
             current_value.value = value
-            return
-        super().__setattr__(key, value)
+        else:
+            super().__setattr__(key, value)
 
 
 class _PatchPluginList(_PatchItemList):
diff --git a/components/bl00mbox/micropython/bl00mbox/_plugins.py b/components/bl00mbox/micropython/bl00mbox/_plugins.py
index 0c0d6f3cd4..0a7372fcf4 100644
--- a/components/bl00mbox/micropython/bl00mbox/_plugins.py
+++ b/components/bl00mbox/micropython/bl00mbox/_plugins.py
@@ -51,80 +51,54 @@ _fill()
 
 
 class _Plugin:
-    def __init__(self, channel, plugin_id, bud_num=None, init_var=0):
-        self._channel_num = channel.channel_num
-        if bud_num == None:
-            self._plugin_id = plugin_id
-            self._bud_num = sys_bl00mbox.channel_new_bud(
-                self.channel_num, self.plugin_id, init_var
-            )
-            if self._bud_num == None:
-                raise bl00mbox.Bl00mboxError("plugin init failed")
+    _core_keys = (
+        "always_render",
+        "delete",
+        "init_var",
+        "table_len",
+        "name",
+        "plugin_id",
+    )
+
+    def __setattr__(self, key, value):
+        if key in self._core_keys:
+            setattr(self._core, key, value)
         else:
-            self._bud_num = bud_num
-            self._check_existence()
-            self._plugin_id = sys_bl00mbox.channel_bud_get_plugin_id(
-                self.channel_num, self.bud_num
-            )
-        self._name = sys_bl00mbox.channel_bud_get_name(self.channel_num, self.bud_num)
+            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}")
+
+    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)
+        else:
+            raise ValueError("must supply core or plugin id")
         self._signals = bl00mbox.SignalList(self)
+        self._channel = channel
+
+    def _repr_no_signals(self):
+        id_num = self._core.id
+        # id_num 0: special meaning, channel plugin, there can only be one
+        if id_num:
+            return f"[{self._core.name} (id={id_num})]"
+        else:
+            return f"[{self._core.name}]"
 
     def __repr__(self):
-        self._check_existence()
-        ret = "[plugin " + str(self.bud_num) + "] " + self.name
+        ret = self._repr_no_signals()
         for sig in self.signals._list:
             ret += "\n  " + "\n  ".join(sig._no_desc().split("\n"))
         return ret
 
-    def __del__(self):
-        self._check_existence()
-        sys_bl00mbox.channel_delete_bud(self.channel_num, self.bud_num)
-
     def _check_existence(self):
-        if not sys_bl00mbox.channel_bud_exists(self.channel_num, self.bud_num):
-            raise bl00mbox.Bl00mboxError("plugin has been deleted")
-
-    @property
-    def always_render(self):
-        self._check_existence()
-        return sys_bl00mbox.channel_bud_get_always_render(
-            self.channel_num, self.bud_num
-        )
-
-    @always_render.setter
-    def always_render(self, value):
-        self._check_existence()
-        sys_bl00mbox.channel_bud_set_always_render(
-            self.channel_num, self.bud_num, value
-        )
-
-    def delete(self):
-        self._check_existence()
-        sys_bl00mbox.channel_delete_bud(self.channel_num, self.bud_num)
-
-    @property
-    def init_var(self):
-        return sys_bl00mbox.channel_bud_get_init_var(self.channel_num, self.bud_num)
-
-    @property
-    def table_len(self):
-        return sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num)
-
-    @property
-    def channel_num(self):
-        return self._channel_num
-
-    @property
-    def plugin_id(self):
-        return self._plugin_id
-
-    @property
-    def name(self):
-        return self._name
-
-    @property
-    def bud_num(self):
-        return self._bud_num
+        # will fail if plugin was deleted
+        _ = self._core.plugin_id
 
     @property
     def signals(self):
@@ -132,33 +106,22 @@ class _Plugin:
 
     @property
     def table(self):
-        ret = []
-        for x in range(
-            sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num)
-        ):
-            ret += [
-                sys_bl00mbox.channel_bud_get_table_value(
-                    self.channel_num, self.bud_num, x
-                )
-            ]
+        _table = self.table_int16_array
+        ret = [None] * self.table_len
+        for x in range(self.table_len):
+            ret[x] = _table[x]
         return ret
 
     @table.setter
-    def table(self, stuff):
-        max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num)
-        if len(stuff) > max_len:
-            stuff = stuff[:max_len]
-        for x, y in enumerate(stuff):
-            sys_bl00mbox.channel_bud_set_table_value(
-                self.channel_num, self.bud_num, x, y
-            )
+    def table(self, data):
+        _table = self.table_int16_array
+        for x in range(min(self.table_len, len(data))):
+            _table[x] = data[x]
 
     @property
     def table_pointer(self):
-        pointer = sys_bl00mbox.channel_bud_get_table_pointer(
-            self.channel_num, self.bud_num
-        )
-        max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num)
+        pointer = self._core.table_pointer
+        max_len = self._core.table_len
         return (pointer, max_len)
 
     @property
@@ -192,22 +155,20 @@ class _Plugin:
 _plugin_subclasses = {}
 
 
-def _make_new_plugin(channel, plugin_id, bud_num, *args, **kwargs):
-    if bud_num is not None:
-        plugin_id = sys_bl00mbox.channel_bud_get_plugin_id(channel.channel_num, bud_num)
+def _make_plugin(channel, core, plugin_id, *args, **kwargs):
     if plugin_id in _plugin_subclasses:
-        return _plugin_subclasses[plugin_id](
-            channel, plugin_id, bud_num, *args, **kwargs
-        )
+        return _plugin_subclasses[plugin_id](channel, core, plugin_id, *args, **kwargs)
     else:
-        init_var = kwargs.get("init_var", None)
-        if init_var is None and len(args) == 1:
-            init_var = args[0]
-        try:
-            init_var = int(init_var)
-        except:
-            return _Plugin(channel, plugin_id, bud_num)
-        return _Plugin(channel, plugin_id, bud_num, init_var)
+        init_var = 0
+        if not core:
+            _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, core, plugin_id, init_var)
 
 
 def _plugin_set_subclass(plugin_id):
@@ -229,19 +190,19 @@ class _Sampler(_Plugin):
     _STATUS = 10
     _BUFFER = 11
 
-    def __init__(self, channel, plugin_id, bud_num, init_var=1000):
+    def __init__(self, channel, core, plugin_id, init_var=1000):
         self._filename = ""
-        if bud_num is not None:
-            super().__init__(channel, plugin_id, bud_num=bud_num)
+        if core is not None:
+            super().__init__(channel, core, None)
             self._memory_len = self.init_var
         elif type(init_var) is str:
             with wave.open(init_var, "r") as f:
                 self._memory_len = f.getnframes()
-                super().__init__(channel, plugin_id, init_var=self._memory_len)
+                super().__init__(channel, None, plugin_id, init_var=self._memory_len)
                 self.load(init_var)
         else:
             self._memory_len = int(48 * init_var)
-            super().__init__(channel, plugin_id, init_var=self._memory_len)
+            super().__init__(channel, None, plugin_id, init_var=self._memory_len)
 
     def __repr__(self):
         ret = super().__repr__()
@@ -532,14 +493,71 @@ class _Distortion(_Plugin):
 
 @_plugin_set_subclass(172)
 class _PolySqueeze(_Plugin):
-    def __init__(self, channel, plugin_id, bud_num, num_outputs=3, num_inputs=10):
-        if bud_num is None:
+    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, plugin_id, init_var=init_var)
+            super().__init__(channel, core, plugin_id, init_var=init_var)
+        else:
+            super().__init__(channel, core, None)
+
+
+@_plugin_set_subclass(0)
+class _Noise(_Plugin):
+    @property
+    def speed(self):
+        if self.signals.speed.value < 0:
+            return "lfo"
+        else:
+            return "audio"
+
+    @speed.setter
+    def speed(self, val):
+        if val == "lfo":
+            self.signals.speed.switch.LFO = True
+        elif val == "audio":
+            self.signals.speed.switch.AUDIO = True
+        else:
+            raise ValueError('speed must be "lfo" or "audio"')
+
+
+@_plugin_set_subclass(69)
+class _RangeShifter(_Plugin):
+    @property
+    def speed(self):
+        val = self.signals.speed.value
+        if val <= -10922:
+            return "slow"
+        elif val < 10922:
+            return "slow_range"
         else:
-            super().__init__(channel, plugin_id, bud_num=bud_num)
+            return "fast"
+
+    @speed.setter
+    def speed(self, val):
+        if val == "slow":
+            self.signals.speed.switch.SLOW = True
+        elif val == "slow_range":
+            self.signals.speed.switch.SLOW_RANGE = True
+        elif val == "fast":
+            self.signals.speed.switch.AUDIO = True
+        else:
+            raise ValueError('speed must be "slow", "slow_range" or "fast"')
+
+
+@_plugin_set_subclass(21)
+class _Mixer(_Plugin):
+    @property
+    def block_dc(self):
+        return self.signals.block_dc.value > 0
+
+    @block_dc.setter
+    def block_dc(self, val):
+        if val:
+            self.signals.block_dc.switch.ON = True
+        else:
+            self.signals.block_dc.switch.OFF = True
 
 
 @_plugin_set_subclass(420)
@@ -548,6 +566,35 @@ class _Osc(_Plugin):
     def wave(self):
         return tuple([self.table_int8_array[i] for i in range(64)])
 
+    @property
+    def speed(self):
+        val = self.signals.speed.value
+        if val < -10922:
+            return "lfo"
+        elif val < 10922:
+            return "auto"
+        else:
+            return "audio"
+
+    @speed.setter
+    def speed(self, val):
+        if val == "lfo":
+            self.signals.speed.switch.LFO = True
+        elif val == "auto":
+            self.signals.speed.switch.AUTO = True
+        elif val == "audio":
+            self.signals.speed.switch.AUDIO = True
+        else:
+            raise ValueError('speed must be "lfo", "auto" or "audio"')
+
+    @property
+    def antialiasing(self):
+        return bool(self.table_int8_array[64])
+
+    @antialiasing.setter
+    def antialiasing(self, val):
+        self.table_int8_array[64] = bool(val)
+
     def __repr__(self):
         ret = super().__repr__()
         wave = self.wave
@@ -580,18 +627,18 @@ class _Osc(_Plugin):
 
 @_plugin_set_subclass(56709)
 class _Sequencer(_Plugin):
-    def __init__(self, channel, plugin_id, bud_num, num_tracks=4, num_steps=16):
-        if bud_num is None:
+    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)
 
-            super().__init__(channel, plugin_id, init_var=init_var)
+            super().__init__(channel, core, plugin_id, init_var=init_var)
 
             tracktable = [-32767] + ([0] * self.num_steps)
             self.table = tracktable * self.num_tracks
         else:
-            super().__init__(channel, plugin_id, bud_num=bud_num)
+            super().__init__(channel, core, None)
             self.num_tracks = self.init_var % 256
             self.num_steps = (self.init_var // 256) % 256
 
diff --git a/components/bl00mbox/micropython/bl00mbox/_user.py b/components/bl00mbox/micropython/bl00mbox/_user.py
index 56ca658ebd..45858eef3d 100644
--- a/components/bl00mbox/micropython/bl00mbox/_user.py
+++ b/components/bl00mbox/micropython/bl00mbox/_user.py
@@ -15,62 +15,48 @@ class Bl00mboxError(Exception):
     pass
 
 
-def _makeSignal(plugin, signal_num):
-    hints = sys_bl00mbox.channel_bud_get_signal_hints(
-        plugin.channel_num, plugin.bud_num, signal_num
-    )
-    if hints & 2:
-        signal = SignalOutput(plugin, signal_num)
-        signal._hints = "output"
-    elif hints & 1:
-        if hints & 4:
-            signal = SignalInputTrigger(plugin, signal_num)
-            signal._hints = "input/trigger"
-        elif hints & 32:
-            signal = SignalInputPitch(plugin, signal_num)
-            signal._hints = "input/pitch"
-        else:
-            signal = SignalInput(plugin, signal_num)
-            signal._hints = "input"
+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
 
 
 class ChannelMixer:
     def __init__(self, channel):
         self._channel = channel
+        self._core = channel._core
         pass
 
     def __repr__(self):
-        ret = "[channel mixer]"
-        ret += " (" + str(len(self.connections)) + " connections)"
+        ret = f"[channel mixer] ({len(self.connections)} connections)"
         for con in self.connections:
-            ret += "\n  " + con.name
-            ret += " in [plugin " + str(con._plugin.bud_num) + "] " + con._plugin.name
+            ret += f"\n  {con.name} in {con._plugin._repr_no_signals()}"
         return ret
 
-    def _unplug_all(self):
-        # TODO
-        pass
-
     @property
     def connections(self):
-        ret = []
-        for i in range(sys_bl00mbox.channel_mixer_num(self._channel.channel_num)):
-            b = sys_bl00mbox.channel_get_bud_by_mixer_list_pos(
-                self._channel.channel_num, i
-            )
-            s = sys_bl00mbox.channel_get_signal_by_mixer_list_pos(
-                self._channel.channel_num, i
+        cons = []
+        signal_cores = self._core.get_connected_mx()
+        for core in signal_cores:
+            plugin = bl00mbox._plugins._make_plugin(
+                self._channel, core.plugin_core, None
             )
-            sig = Signal(bl00mbox._plugins._make_new_plugin(self._channel, 0, b), s)
-            ret += [sig]
-        return ret
+            cons.append(_make_signal(plugin, core=core))
+        return cons
 
 
 class ValueSwitch:
-    def __init__(self, plugin, signal_num):
+    def __init__(self, plugin, core):
         self._plugin = plugin
-        self._signal_num = signal_num
+        self._core = core
 
     def __setattr__(self, key, value):
         if getattr(self, "_locked", False):
@@ -79,12 +65,7 @@ class ValueSwitch:
             val = getattr(self, key, None)
             if val is None:
                 return
-            sys_bl00mbox.channel_bud_set_signal_value(
-                self._plugin.channel_num,
-                self._plugin.bud_num,
-                self._signal_num,
-                int(val),
-            )
+            self._core.value = val
         else:
             super().__setattr__(key, value)
 
@@ -96,22 +77,14 @@ class ValueSwitch:
 
 
 class Signal:
-    def __init__(self, plugin, signal_num):
+    def __init__(self, plugin, core):
+        self._core = core
         self._plugin = plugin
-        self._signal_num = signal_num
-        self._name = sys_bl00mbox.channel_bud_get_signal_name(
-            plugin.channel_num, plugin.bud_num, signal_num
-        )
-        self._mpx = sys_bl00mbox.channel_bud_get_signal_name_multiplex(
-            plugin.channel_num, plugin.bud_num, signal_num
-        )
-        self._description = sys_bl00mbox.channel_bud_get_signal_description(
-            plugin.channel_num, plugin.bud_num, signal_num
-        )
-        self._unit = sys_bl00mbox.channel_bud_get_signal_unit(
-            plugin.channel_num, plugin.bud_num, signal_num
-        )
+        self._mpx = core.mpx
+        self._unit = core.unit
+        self._description = core.description
         constants = {}
+
         another_round = True
         while another_round:
             another_round = False
@@ -125,13 +98,41 @@ class Signal:
                             break
                     another_round = True
                     break
+
         if constants:
             self._unit = self._unit.strip()
-            self.switch = ValueSwitch(plugin, signal_num)
+            self.switch = ValueSwitch(plugin, self._core)
             for key in constants:
                 setattr(self.switch, key, constants[key])
             self.switch._locked = True
-        self._hints = ""
+
+        hint_list = []
+        if core.hints & sys_bl00mbox.SIGNAL_HINT_INPUT:
+            hint_list.append("input")
+        if core.hints & sys_bl00mbox.SIGNAL_HINT_OUTPUT:
+            hint_list.append("output")
+        if core.hints & sys_bl00mbox.SIGNAL_HINT_TRIGGER:
+            hint_list.append("trigger")
+        if core.hints & sys_bl00mbox.SIGNAL_HINT_SCT:
+            hint_list.append("pitch")
+        if core.hints & sys_bl00mbox.SIGNAL_HINT_GAIN:
+            hint_list.append("gain")
+        if core.hints & sys_bl00mbox.SIGNAL_HINT_DEPRECATED:
+            hint_list.append("deprecated")
+        self._hints = "/".join(hint_list)
+
+    @property
+    def connections(self):
+        cons = []
+        signal_cores = self._core.get_connected()
+        for core in signal_cores:
+            plugin = bl00mbox._plugins._make_plugin(
+                self._plugin._channel, core.plugin_core, None
+            )
+            cons.append(_make_signal(plugin, core=core))
+        if self._core.connected_mx:
+            cons.append(self._plugin._channel.mixer)
+        return cons
 
     def __repr__(self):
         ret = self._no_desc()
@@ -151,7 +152,6 @@ class Signal:
 
         ret += " [" + self.hints + "]: "
 
-        conret = []
         direction = " <?> "
         val = self.value
         if isinstance(self, SignalInput):
@@ -168,68 +168,54 @@ class Signal:
             if value_name is not None:
                 ret += " (" + value_name.lower() + ")"
 
-        if isinstance(self, SignalPitchMixin):
+        if self._core.hints & sys_bl00mbox.SIGNAL_HINT_SCT:
             ret += " / " + str(self.tone) + " semitones / " + str(self.freq) + "Hz"
 
-        if isinstance(self, SignalGainMixin):
+        if self._core.hints & sys_bl00mbox.SIGNAL_HINT_GAIN:
             if self.mult == 0:
                 ret += " / (mute)"
             else:
                 ret += " / " + str(self.dB) + "dB / x" + str(self.mult)
 
+        conret = []
         for con in self.connections:
             if isinstance(con, Signal):
-                conret += [
-                    direction
-                    + con.name
-                    + " in [plugin "
-                    + str(con._plugin.bud_num)
-                    + "] "
-                    + con._plugin.name
-                ]
+                conret += [f"{direction}{con.name} in {con._plugin._repr_no_signals()}"]
             if isinstance(con, ChannelMixer):
-                conret += [" ==> [channel mixer]"]
-        nl = "\n"
+                conret += [f"{direction}[channel mixer]"]
         if len(conret) > 1:
-            ret += "\n"
-            nl += "  "
-        ret += nl.join(conret)
-
+            nl = "\n "
+            ret += nl + nl.join(conret)
+        elif conret:
+            ret += conret[0]
         return ret
 
     @property
     def name(self):
-        return self._name
+        return self._core.name
 
     @property
     def description(self):
-        return self._description
+        return self._core.description
 
     @property
     def unit(self):
-        return self._unit
+        return self._core.unit
 
     @property
     def hints(self):
         return self._hints
 
-    @property
-    def connections(self):
-        return []
-
     @property
     def value(self):
-        self._plugin._check_existence()
-        return sys_bl00mbox.channel_bud_get_signal_value(
-            self._plugin.channel_num, self._plugin.bud_num, self._signal_num
-        )
+        return self._core.value
 
     @property
-    def _tone(self):
+    def tone(self):
         return (self.value - (32767 - 2400 * 6)) / 200
 
-    @_tone.setter
-    def _tone(self, val):
+    @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
@@ -239,12 +225,12 @@ class Signal:
             raise AttributeError("can't set output signal")
 
     @property
-    def _freq(self):
+    def freq(self):
         tone = (self.value - (32767 - 2400 * 6)) / 200
         return 440 * (2 ** (tone / 12))
 
-    @_freq.setter
-    def _freq(self, val):
+    @freq.setter
+    def freq(self, val):
         if isinstance(self, SignalInput):
             tone = 12 * math.log(val / 440, 2)
             self.value = (32767 - 2400 * 6) + 200 * tone
@@ -252,194 +238,87 @@ class Signal:
             raise AttributeError("can't set output signal")
 
     @property
-    def _dB(self):
+    def dB(self):
         if self.value == 0:
             return -9999
         return 20 * math.log((abs(self.value) / 4096), 10)
 
-    @_dB.setter
-    def _dB(self, val):
+    @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")
 
     @property
-    def _mult(self):
+    def mult(self):
         return self.value / 4096
 
-    @_mult.setter
-    def _mult(self, val):
+    @mult.setter
+    def mult(self, val):
         if isinstance(self, SignalInput):
             self.value = int(4096 * val)
         else:
             raise AttributeError("can't set output signal")
 
+    def start(self, velocity=32767):
+        if self.value > 0:
+            self.value = -abs(velocity)
+        else:
+            self.value = abs(velocity)
+
+    def stop(self):
+        self.value = 0
+
 
 class SignalOutput(Signal):
     @Signal.value.setter
     def value(self, val):
-        if val == None:
-            sys_bl00mbox.channel_disconnect_signal_tx(
-                self._plugin.channel_num, self._plugin.bud_num, self._signal_num
-            )
+        if val is None:
+            self._core.disconnect()
         elif isinstance(val, SignalInput):
             val.value = self
         elif isinstance(val, ChannelMixer):
-            if val._channel.channel_num == self._plugin.channel_num:
-                sys_bl00mbox.channel_connect_signal_to_output_mixer(
-                    self._plugin.channel_num, self._plugin.bud_num, self._signal_num
-                )
+            self._core.connect_mx()
+        # fails silently bc of backwards compatibility :/
+        # we'll deprecate this setter entirely someday, use rshift instead
+
+    def __rshift__(self, other):
+        if not (
+            isinstance(other, SignalInput)
+            or isinstance(other, ChannelMixer)
+            or other is None
+        ):
+            raise TypeError(f"can't connect SignalOutput to {type(other)}")
+        self.value = other
 
-    @property
-    def connections(self):
-        cons = []
-        chan = self._plugin.channel_num
-        bud_num = self._plugin.bud_num
-        sig = self._signal_num
-        for i in range(sys_bl00mbox.channel_subscriber_num(chan, bud_num, sig)):
-            b = sys_bl00mbox.channel_get_bud_by_subscriber_list_pos(
-                chan, bud_num, sig, i
-            )
-            s = sys_bl00mbox.channel_get_signal_by_subscriber_list_pos(
-                chan, bud_num, sig, i
-            )
-            if (s >= 0) and (b > 0):
-                cons += [
-                    _makeSignal(
-                        bl00mbox._plugins._make_new_plugin(SysChannel(chan), 0, b), s
-                    )
-                ]
-            elif s == -1:
-                cons += [ChannelMixer(SysChannel(chan))]
-        return cons
+    def __lshift__(self, other):
+        raise TypeError("output signals can't receive data from other signals")
 
 
 class SignalInput(Signal):
     @Signal.value.setter
     def value(self, val):
-        self._plugin._check_existence()
-        if isinstance(val, SignalOutput):
-            if len(self.connections):
-                if not sys_bl00mbox.channel_disconnect_signal_rx(
-                    self._plugin.channel_num, self._plugin.bud_num, self._signal_num
-                ):
-                    return
-            sys_bl00mbox.channel_connect_signal(
-                self._plugin.channel_num,
-                self._plugin.bud_num,
-                self._signal_num,
-                val._plugin.bud_num,
-                val._signal_num,
-            )
-        elif isinstance(val, SignalInput):
-            # TODO
-            pass
+        if val is None:
+            self._core.disconnect()
+        elif isinstance(val, SignalOutput):
+            self._core.connect(val._core)
         elif (type(val) == int) or (type(val) == float):
-            if len(self.connections):
-                if not sys_bl00mbox.channel_disconnect_signal_rx(
-                    self._plugin.channel_num, self._plugin.bud_num, self._signal_num
-                ):
-                    return
-            sys_bl00mbox.channel_bud_set_signal_value(
-                self._plugin.channel_num,
-                self._plugin.bud_num,
-                self._signal_num,
-                int(val),
-            )
-
-    @property
-    def connections(self):
-        cons = []
-        chan = self._plugin.channel_num
-        bud = self._plugin.bud_num
-        sig = self._signal_num
-        b = sys_bl00mbox.channel_get_source_bud(chan, bud, sig)
-        s = sys_bl00mbox.channel_get_source_signal(chan, bud, sig)
-        if (s >= 0) and (b > 0):
-            cons += [
-                _makeSignal(
-                    bl00mbox._plugins._make_new_plugin(SysChannel(chan), 0, b), s
-                )
-            ]
-        return cons
-
-    def _start(self, velocity=32767):
-        if self.value > 0:
-            self.value = -abs(velocity)
-        else:
-            self.value = abs(velocity)
-
-    def _stop(self):
-        self.value = 0
-
-
-class SignalInputTriggerMixin:
-    def start(self, velocity=32767):
-        self._start(velocity)
-
-    def stop(self):
-        self._stop()
-
-
-class SignalPitchMixin:
-    @property
-    def tone(self):
-        return self._tone
-
-    @tone.setter
-    def tone(self, val):
-        self._tone = val
-
-    @property
-    def freq(self):
-        return self._freq
-
-    @freq.setter
-    def freq(self, val):
-        self._freq = val
-
-
-class SignalGainMixin:
-    @property
-    def dB(self):
-        return self._dB
-
-    @dB.setter
-    def dB(self, val):
-        self._dB = val
-
-    @property
-    def mult(self):
-        return self._mult
-
-    @mult.setter
-    def mult(self, val):
-        self._mult = val
-
-
-class SignalOutputTrigger(SignalOutput):
-    pass
-
-
-class SignalOutputPitch(SignalOutput, SignalPitchMixin):
-    pass
-
-
-class SignalOutputGain(SignalOutput, SignalGainMixin):
-    pass
-
-
-class SignalInputTrigger(SignalInput, SignalInputTriggerMixin):
-    pass
-
-
-class SignalInputPitch(SignalInput, SignalPitchMixin):
-    pass
-
+            self._core.value = int(val)
+        # fails silently bc of backwards compatibility :/
+
+    def __lshift__(self, other):
+        if not (
+            isinstance(other, SignalOutput)
+            or type(other) == int
+            or type(other) == float
+            or other is None
+        ):
+            raise TypeError(f"can't connect SignalInput to {type(other)}")
+        self.value = other
 
-class SignalInputGain(SignalInput, SignalGainMixin):
-    pass
+    def __rshift__(self, other):
+        raise TypeError("input signals can't send data to other signals")
 
 
 class SignalMpxList:
@@ -498,41 +377,9 @@ class SignalMpxList:
 class SignalList:
     def __init__(self, plugin):
         self._list = []
-        for signal_num in range(
-            sys_bl00mbox.channel_bud_get_num_signals(plugin.channel_num, plugin.bud_num)
-        ):
-            hints = sys_bl00mbox.channel_bud_get_signal_hints(
-                plugin.channel_num, plugin.bud_num, signal_num
-            )
-            if hints & 4:
-                if hints & 1:
-                    signal = SignalInputTrigger(plugin, signal_num)
-                    signal._hints = "input/trigger"
-                elif hints & 2:
-                    signal = SignalOutputTrigger(plugin, signal_num)
-                    signal._hints = "output/trigger"
-            elif hints & 32:
-                if hints & 1:
-                    signal = SignalInputPitch(plugin, signal_num)
-                    signal._hints = "input/pitch"
-                elif hints & 2:
-                    signal = SignalOutputPitch(plugin, signal_num)
-                    signal._hints = "output/pitch"
-            elif hints & 8:
-                if hints & 1:
-                    signal = SignalInputGain(plugin, signal_num)
-                    signal._hints = "input/gain"
-                elif hints & 2:
-                    signal = SignalOutputGain(plugin, signal_num)
-                    signal._hints = "input/gain"
-            else:
-                if hints & 1:
-                    signal = SignalInput(plugin, signal_num)
-                    signal._hints = "input"
-                elif hints & 2:
-                    signal = SignalOutput(plugin, signal_num)
-                    signal._hints = "output"
-            self._list += [signal]
+        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)
@@ -567,27 +414,54 @@ def set_channel_init_callback(callback):
 
 
 class Channel:
-    def __init__(self, name=None):
-        if name == None:
-            self._channel_num = sys_bl00mbox.channel_get_free_index()
-            self.name = "repl"
-        elif type(name) == str:
-            self._channel_num = sys_bl00mbox.channel_get_free_index()
-            self.name = name
+    _core_keys = (
+        "name",
+        "num_plugins",
+        "clear",
+        "volume",
+        "foreground",
+        "background_mute_override",
+        "callback",
+        "compute_rms",
+    )
+
+    # 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.
+
+    def __setattr__(self, key, value):
+        if key in self._core_keys:
+            setattr(self._core, key, value)
         else:
-            raise TypeError("must be None or str")
-        if self._channel_num < 0:
-            raise Bl00mboxError("Channel could not be initialized")
-        global _channel_init_callback
+            super().__setattr__(key, value)
+
+    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 __init__(self, name="repl"):
+        self._core = sys_bl00mbox.ChannelCore(name)
         if _channel_init_callback is not None:
             _channel_init_callback(self)
-        self._tripwire = sys_bl00mbox.channel_wire(self._channel_num)
+        self._channel_plugin = bl00mbox._plugins._make_plugin(
+            self, self._core.channel_plugin, sys_bl00mbox.BL00MBOX_CHANNEL_PLUGIN_ID
+        )
+
+    @property
+    def signals(self):
+        return self._channel_plugin._signals
 
     def __repr__(self):
-        ret = "[channel " + str(self.channel_num)
-        if self.name != "":
-            ret += ": " + self.name
-        ret += "]"
+        ret = f'[channel "{self.name}"]'
         if self.foreground:
             ret += " (foreground)"
         if self.background_mute_override:
@@ -595,92 +469,53 @@ class Channel:
         ret += "\n  gain: " + str(self.gain_dB) + "dB"
         b = self.num_plugins
         ret += "\n  plugins: " + str(b)
-        # actual_len = len(list(self.plugins))
-        # if actual_len != b:
-        #     ret += " (desync" + actual_len + ")"
         ret += "\n  " + "\n  ".join(repr(self.mixer).split("\n"))
         return ret
 
-    def clear(self):
-        sys_bl00mbox.channel_clear(self.channel_num)
-
-    @property
-    def num_plugins(self):
-        return sys_bl00mbox.channel_buds_num(self.channel_num)
-
-    @property
-    def name(self):
-        name = sys_bl00mbox.channel_get_name(self.channel_num)
-        if name is None:
-            return ""
-        return name
-
-    @name.setter
-    def name(self, newname: str):
-        sys_bl00mbox.channel_set_name(self._channel_num, newname)
-
     @property
     def free(self):
-        return sys_bl00mbox.channel_get_free(self.channel_num)
+        # legacy oof
+        return self._core is None
 
     @free.setter
     def free(self, val):
+        # bigger legacy oof
         if val:
             self.clear()
-        sys_bl00mbox.channel_set_free(self.channel_num, val)
+            self._core = None
 
     def _new_plugin(self, thing, *args, **kwargs):
         if isinstance(thing, bl00mbox._plugins._PluginDescriptor):
-            return bl00mbox._plugins._make_new_plugin(
-                self, thing.plugin_id, None, *args, **kwargs
+            return bl00mbox._plugins._make_plugin(
+                self, None, thing.plugin_id, *args, **kwargs
             )
         else:
             raise TypeError("not a plugin")
 
-    def print_plugins(self):
-        for plugin in self.plugins:
-            print(repr(plugin))
-
     def new(self, thing, *args, **kwargs):
         self.free = False
         if type(thing) == type:
             if issubclass(thing, bl00mbox.patches._Patch):
                 return thing(self, *args, **kwargs)
-        if isinstance(thing, bl00mbox._plugins._PluginDescriptor) or (
+        elif isinstance(thing, bl00mbox._plugins._PluginDescriptor) or (
             type(thing) == int
         ):
             return self._new_plugin(thing, *args, **kwargs)
+        else:
+            raise TypeError("invalid plugin/patch")
 
     @property
     def plugins(self):
-        for i in range(sys_bl00mbox.channel_buds_num(self.channel_num)):
-            b = sys_bl00mbox.channel_get_bud_by_list_pos(self.channel_num, i)
-            yield [bl00mbox._plugins._make_new_plugin(self, 0, b)]
-
-    @property
-    def channel_num(self):
-        return self._channel_num
-
-    @property
-    def connections(self):
-        return sys_bl00mbox.channel_conns_num(self.channel_num)
-
-    @property
-    def volume(self):
-        return sys_bl00mbox.channel_get_volume(self.channel_num)
-
-    @volume.setter
-    def volume(self, value):
-        value = min(32767, max(-32767, value))
-        sys_bl00mbox.channel_set_volume(self.channel_num, value)
+        for core in self._core.get_plugins():
+            yield bl00mbox._plugins._make_plugin(self, core, None)
 
     @property
     def gain_dB(self):
-        ret = sys_bl00mbox.channel_get_volume(self.channel_num)
+        ret = self._core.volume
         if ret == 0:
             return -math.inf
         else:
-            return 20 * math.log(abs(ret) / 32768, 10)
+            return 20 * math.log(ret / 32768, 10)
 
     @gain_dB.setter
     def gain_dB(self, value):
@@ -689,26 +524,23 @@ class Channel:
         else:
             value = int(32768 * (10 ** (value / 20)))
             value = min(32767, max(0, value))
-        sys_bl00mbox.channel_set_volume(self.channel_num, value)
-
-    @property
-    def compute_rms(self):
-        return sys_bl00mbox.channel_get_compute_mean_square(self.channel_num)
-
-    @compute_rms.setter
-    def compute_rms(self, value):
-        sys_bl00mbox.channel_set_compute_mean_square(self.channel_num, value)
+        self._core.volume = value
 
     @property
     def rms_dB(self):
         if self.compute_rms:
-            ret = sys_bl00mbox.channel_get_mean_square(self.channel_num)
+            ret = self._core.mean_square
             if ret == 0:
                 return -math.inf
             else:
-                mult = abs(sys_bl00mbox.channel_get_volume(self.channel_num)) / 32768
+                # 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:
@@ -722,50 +554,25 @@ class Channel:
     def mixer(self, val):
         if isinstance(val, SignalOutput):
             val.value = self.mixer
-        elif val is None:
-            ChannelMixer(self)._unplug_all
-        else:
+        elif val is not None:
+            # val is None: backwards compatibility, not allowed to throw exception ;w;
             raise Bl00mboxError("can't connect this")
 
-    @property
-    def background_mute_override(self):
-        return sys_bl00mbox.channel_get_background_mute_override(self.channel_num)
-
-    @background_mute_override.setter
-    def background_mute_override(self, val):
-        sys_bl00mbox.channel_set_background_mute_override(self.channel_num, val)
-
-    @property
-    def foreground(self):
-        return sys_bl00mbox.channel_get_foreground() == self.channel_num
-
-    @foreground.setter
-    def foreground(self, val):
-        if val:
-            sys_bl00mbox.channel_set_foreground(self.channel_num)
-        elif sys_bl00mbox.channel_get_foreground() == self.channel_num:
-            sys_bl00mbox.channel_set_foreground(-1)
-
 
 class SysChannel(Channel):
-    def __init__(self, identifier):
-        if isinstance(identifier, int):
-            if not sys_bl00mbox.channel_get_exists(identifier):
-                raise Bl00mboxError(f"channel index {identifier} not found")
-            self._channel_num = identifier
-        elif isinstance(identifier, Channel):
-            self._channel_num = identifier.channel_num
-        else:
+    def __init__(self, core):
+        if not isinstance(core, sys_bl00mbox.ChannelCore):
             raise Bl00mboxError("improper identifier")
+        self._core = core
 
     @property
     def sys_gain_dB(self):
-        ret = sys_bl00mbox.channel_get_sys_gain(self.channel_num)
+        ret = self._core.sys_gain
         if ret == 0:
             return -math.inf
         else:
             try:
-                return 20 * math.log(abs(ret) / 4096, 10)
+                return 20 * math.log(ret / 4096, 10)
             except:
                 return -math.inf
 
@@ -776,17 +583,17 @@ class SysChannel(Channel):
         else:
             value = int(4096 * (10 ** (value / 20)))
             value = min(32767, max(0, value))
-        sys_bl00mbox.channel_set_sys_gain(self.channel_num, value)
+        self._core.sys_gain = value
 
     @property
     def sys_rms_dB(self):
         if self.compute_rms:
-            ret = sys_bl00mbox.channel_get_mean_square(self.channel_num)
+            ret = self._core.mean_square
             if ret == 0:
                 return -math.inf
             else:
-                mult = abs(sys_bl00mbox.channel_get_volume(self.channel_num)) / 32768
-                mult *= abs(sys_bl00mbox.channel_get_sys_gain(self.channel_num)) / 4096
+                mult = self._core.volume / 32768
+                mult *= self._core.sys_gain / 4096
                 if mult == 0:
                     return -math.inf
                 ret *= mult * mult
@@ -795,27 +602,40 @@ class SysChannel(Channel):
             return None
 
 
-def all_channels():
-    index = 0
-    while True:
-        ret = sys_bl00mbox.channel_get_index_positional_all_chans(index)
-        if ret < 0:
-            break
-        yield SysChannel(ret)
-        index += 1
-
-
-def active_channels():
-    index = 0
-    fore = sys_bl00mbox.channel_get_foreground()
-    if fore >= 0:
-        yield SysChannel(fore)
-    while True:
-        ret = sys_bl00mbox.channel_get_index_positional_background_mute_override_chans(
-            index
-        )
-        if ret < 0:
-            break
-        if ret != fore:
-            yield SysChannel(ret)
-        index += 1
+class Sys:
+    @staticmethod
+    def collect_channels(active):
+        cores = sys_bl00mbox.collect_channels(active)
+        for core in cores:
+            yield SysChannel(core)
+
+    @staticmethod
+    def collect_active_callbacks():
+        # Channel callbacks have a strange side effect on garbage collection:
+        #
+        # If a Channel has become unreachable but has not been collected yet, this will still fetch the
+        # ChannelCore object and the attached callback, meaning that from a gc perspective the channel
+        # "flickers" into reachability on the stack while the callback is fetched. if the callback
+        # contains the last reference to the channel (as it typically does), this flickering is extended
+        # for the duration of the callback execution.
+        #
+        # If garbage collection is triggered always in that exact moment, the channel will never
+        # be collected. In practice, this is very unlikely, so it should be fine.
+        #
+        # It is still good practice to call .clear() on the Channel before dropping the last
+        # reference as this will also remove the callback and set defined endpoint to Channel
+        # audio rendering.
+        #
+        # We are relying on implementation details of the garbage collector to ensure memory safety.
+        # micropython uses primitive conservative tracing gc triggered by some or the other threshold.
+        # It always sweeps everything it can before it returns control. See channel_core_obj_t for details.
+        #
+        # We never heard of this technique before, probably because it's risky. On the off chance we
+        # invented something here we'll dub this one "gc necromancy". Think of the flickering objects
+        # as "tiny little ghosts" that get summoned during the midnight hour and can only be gc'd at
+        # daytime :D. Necromancy is allowed to be risky business ^w^.
+        #
+        # hooray programming!
+        for core in sys_bl00mbox.collect_channels(True):
+            if core.callback and core.sys_gain:
+                yield core.callback
diff --git a/components/bl00mbox/micropython/mp_sys_bl00mbox.c b/components/bl00mbox/micropython/mp_sys_bl00mbox.c
index e5e85360ac..bc78467c48 100644
--- a/components/bl00mbox/micropython/mp_sys_bl00mbox.c
+++ b/components/bl00mbox/micropython/mp_sys_bl00mbox.c
@@ -14,11 +14,141 @@
 #error "bl00mbox needs finaliser"
 #endif
 
+MP_DEFINE_EXCEPTION(ReferenceError, Exception)
+
+typedef struct _channel_core_obj_t {
+    mp_obj_base_t base;
+    // might be NULL if channel has been manually deleted, use
+    // channel_core_verify() before accessing
+    bl00mbox_channel_t *chan;
+    mp_obj_t channel_plugin;
+#ifdef BL00MBOX_CALLBACKS_FUNSAFE
+    // the channel may be become reachable after having been unreachable.
+    // if the callback was collected during this phase it results in a
+    // use-after-free, we're relying on the fact that if the callback
+    // was collected the channel must have been collected as well since
+    // the garbage collector sweeps everything directly after marking.
+    // conservative false positives for the channel are ok since this
+    // means the callback can't be collected either.
+    // see bl00mbox_config.h for more notes on this.
+    mp_obj_t callback;
+#endif
+} channel_core_obj_t;
+
+STATIC const mp_obj_type_t channel_core_type;
+
+typedef struct _plugin_core_obj_t {
+    mp_obj_base_t base;
+    // 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;
+} plugin_core_obj_t;
+
+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)
+    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);
+
+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) {
+    if (!plugin_core->plugin)
+        mp_raise_msg(&mp_type_ReferenceError,
+                     MP_ERROR_TEXT("plugin was deleted"));
+}
+
+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:;
+    }
+}
+
+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];
+        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;
+            // 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);
+            output_index++;
+        }
+        return mp_obj_new_list(output_index, elems);
+    } else {
+        free(array);
+        return mp_obj_new_list(0, NULL);
+    }
+}
+
+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];
+        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;
+            // 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);
+            output_index++;
+        }
+        return mp_obj_new_list(output_index, elems);
+    } else {
+        free(array);
+        return mp_obj_new_list(0, NULL);
+    }
+}
+
 // ========================
-//    PLUGIN OPERATIONS
+//    PLUGIN REGISTRY
 // ========================
+
+// TODO: clean this up
+
 STATIC mp_obj_t mp_plugin_index_get_id(mp_obj_t index) {
-    /// prints name
     radspa_descriptor_t *desc =
         bl00mbox_plugin_registry_get_descriptor_from_index(
             mp_obj_get_int(index));
@@ -57,552 +187,450 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_plugin_registry_num_plugins_obj,
                                  mp_plugin_registry_num_plugins);
 
 // ========================
-//    CHANNEL OPERATIONS
+//        CHANNELS
 // ========================
 
-STATIC const mp_obj_type_t channel_wire_type;
-
-typedef struct _channel_wire_obj_t {
-    mp_obj_base_t base;
-    int32_t index;
-} channel_wire_obj_t;
-
-STATIC mp_obj_t channel_wire_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, true);
-    channel_wire_obj_t *self = m_new_obj_with_finaliser(channel_wire_obj_t);
-    self->base.type = &channel_wire_type;
-    self->index = mp_obj_get_int(args[0]);
-    bl00mbox_log_info("making channel %d", (int)self->index);
+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;
     return MP_OBJ_FROM_PTR(self);
 }
 
-mp_obj_t mp_channel_wire_trip(mp_obj_t self_in) {
-    channel_wire_obj_t *self = MP_OBJ_TO_PTR(self_in);
-    bl00mbox_log_info("freeing channel %d", (int)self->index);
-    bl00mbox_channel_clear(self->index);
-    bl00mbox_channel_set_free(self->index, true);
-    return mp_const_none;
-}
-MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_wire_trip_obj, mp_channel_wire_trip);
-
-STATIC const mp_rom_map_elem_t channel_wire_locals_dict_table[] = {
-    { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_channel_wire_trip_obj) },
-};
-STATIC MP_DEFINE_CONST_DICT(channel_wire_locals_dict,
-                            channel_wire_locals_dict_table);
-
-STATIC MP_DEFINE_CONST_OBJ_TYPE(channel_wire_type, MP_QSTR_channel_wire,
-                                MP_TYPE_FLAG_NONE, make_new,
-                                channel_wire_make_new, locals_dict,
-                                &channel_wire_locals_dict);
-
-static mp_obj_t mp_channel_get_exists(mp_obj_t pos) {
-    return mp_obj_new_bool(bl00mbox_get_channel_exists(mp_obj_get_int(pos)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_exists_obj,
-                                 mp_channel_get_exists);
-
-static mp_obj_t mp_channel_get_index_positional_all_chans(mp_obj_t pos) {
-    return mp_obj_new_int(
-        bl00mbox_get_channel_index_positional_all_chans(mp_obj_get_int(pos)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_index_positional_all_chans_obj,
-                                 mp_channel_get_index_positional_all_chans);
-
-static mp_obj_t mp_channel_get_index_positional_background_mute_override_chans(
-    mp_obj_t pos) {
-    return mp_obj_new_int(
-        bl00mbox_get_channel_index_positional_background_mute_override_chans(
-            mp_obj_get_int(pos)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(
-    mp_channel_get_index_positional_background_mute_override_chans_obj,
-    mp_channel_get_index_positional_background_mute_override_chans);
-
-STATIC mp_obj_t mp_channel_clear(mp_obj_t index) {
-    return mp_obj_new_bool(bl00mbox_channel_clear(mp_obj_get_int(index)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_clear_obj, mp_channel_clear);
-
-static mp_obj_t mp_channel_get_free(mp_obj_t index) {
-    return mp_obj_new_int(bl00mbox_channel_get_free(mp_obj_get_int(index)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_free_obj, mp_channel_get_free);
-
-static mp_obj_t mp_channel_set_free(mp_obj_t index, mp_obj_t free) {
-    return mp_obj_new_int(
-        bl00mbox_channel_set_free(mp_obj_get_int(index), mp_obj_is_true(free)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_set_free_obj, mp_channel_set_free);
-
-static mp_obj_t mp_channel_get_free_index() {
-    return mp_obj_new_int(bl00mbox_channel_get_free_index());
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_channel_get_free_index_obj,
-                                 mp_channel_get_free_index);
-
-STATIC mp_obj_t mp_channel_get_foreground() {
-    return mp_obj_new_int(bl00mbox_channel_get_foreground_index());
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_channel_get_foreground_obj,
-                                 mp_channel_get_foreground);
-
-STATIC mp_obj_t mp_channel_set_foreground(mp_obj_t index) {
-    bl00mbox_channel_set_foreground_index(mp_obj_get_int(index));
-    return mp_const_none;
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_set_foreground_obj,
-                                 mp_channel_set_foreground);
-
-STATIC mp_obj_t mp_channel_get_background_mute_override(mp_obj_t index) {
-    bool ret =
-        bl00mbox_channel_get_background_mute_override(mp_obj_get_int(index));
-    return mp_obj_new_bool(ret);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_background_mute_override_obj,
-                                 mp_channel_get_background_mute_override);
-
-STATIC mp_obj_t mp_channel_set_background_mute_override(mp_obj_t index,
-                                                        mp_obj_t enable) {
-    bool ret = bl00mbox_channel_set_background_mute_override(
-        mp_obj_get_int(index), mp_obj_is_true(enable));
-    return mp_obj_new_bool(ret);
+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->base.type = &channel_core_type;
+    const char *name = strdup(mp_obj_str_get_str(args[0]));
+    if (!name) 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->parent = self;
+    self->chan = chan;
+    self->channel_plugin = create_channel_plugin(chan);
+#ifdef BL00MBOX_CALLBACKS_FUNSAFE
+    self->callback = mp_const_none;
+#endif
+    bl00mbox_log_info("created channel %s", chan->name);
+    return MP_OBJ_FROM_PTR(self);
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_set_background_mute_override_obj,
-                                 mp_channel_set_background_mute_override);
 
-STATIC mp_obj_t mp_channel_enable(mp_obj_t chan) {
-    bl00mbox_channel_enable(mp_obj_get_int(chan));
+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) {
+        bl00mbox_log_info("destroyed channel %s", self->chan->name);
+        bl00mbox_channel_destroy(self->chan);
+    }
     return mp_const_none;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_enable_obj, mp_channel_enable);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_del_obj, mp_channel_core_del);
 
-STATIC mp_obj_t mp_channel_disable(mp_obj_t chan) {
-    bl00mbox_channel_disable(mp_obj_get_int(chan));
+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_verify(self);
+    bl00mbox_log_info("destroyed channel %s", self->chan->name);
+    bl00mbox_channel_destroy(self->chan);
     return mp_const_none;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_disable_obj, mp_channel_disable);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_delete_obj, mp_channel_core_delete);
 
-STATIC mp_obj_t mp_channel_set_volume(mp_obj_t chan, mp_obj_t vol) {
-    bl00mbox_channel_set_volume(mp_obj_get_int(chan), mp_obj_get_int(vol));
+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);
     return mp_const_none;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_set_volume_obj,
-                                 mp_channel_set_volume);
+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);
+    channel_core_verify(self);
+    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);
+
+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);
+    channel_core_verify(self);
+    bl00mbox_array_t *array = bl00mbox_channel_collect_plugins(self->chan);
+    return get_plugins_from_array(array);
+}
+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;
+
+    // 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;
+        if (attr == MP_QSTR_volume) {
+            int gain = abs(mp_obj_get_int(dest[1]));
+            chan->volume = gain > 32767 ? 32767 : gain;
+        } 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_rms) {
+            chan->compute_rms = mp_obj_is_true(dest[1]);
+            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]));
+        } 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) {
+            self->callback = dest[1];
+#endif
+        } else {
+            attr_exists = false;
+        }
+        if (attr_exists) dest[0] = MP_OBJ_NULL;
+    } 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);
+        } else if (attr == MP_QSTR_mean_square) {
+            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_name) {
+            dest[0] = mp_obj_new_str(chan->name, strlen(chan->name));
+        } else if (attr == MP_QSTR_channel_plugin) {
+            dest[0] = self->channel_plugin;
+        } else if (attr == MP_QSTR_callback) {
+#ifdef BL00MBOX_CALLBACKS_FUNSAFE
+            dest[0] = self->callback;
+#else
+            dest[0] = mp_const_none;
+#endif
+        } else if (attr == MP_QSTR_background_mute_override) {
+            dest[0] = mp_obj_new_bool(chan->background_mute_override);
+        } else if (attr == MP_QSTR_foreground) {
+            dest[0] = mp_obj_new_bool(bl00mbox_channel_get_foreground(chan));
+        } else if (attr == MP_QSTR_num_plugins) {
+            dest[0] = mp_obj_new_int(bl00mbox_channel_plugins_num(chan));
+        } else if (attr == MP_QSTR_num_conns) {
+            dest[0] = mp_obj_new_int(bl00mbox_channel_conns_num(chan));
+        } else if (attr == MP_QSTR_num_mixer) {
+            dest[0] = mp_obj_new_int(bl00mbox_channel_mixer_num(chan));
+        } else {
+            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_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) },
+};
+STATIC MP_DEFINE_CONST_DICT(channel_core_locals_dict,
+                            channel_core_locals_dict_table);
 
-STATIC mp_obj_t mp_channel_get_volume(mp_obj_t chan) {
-    return mp_obj_new_int(bl00mbox_channel_get_volume(mp_obj_get_int(chan)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_volume_obj,
-                                 mp_channel_get_volume);
+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) {
+        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) {
+                bl00mbox_log_error("channel core corrupted");
+                continue;
+            }
+            elems[output_index] = MP_OBJ_FROM_PTR(core);
+            output_index++;
+        }
+        ret = mp_obj_new_list(output_index, elems);
+    } else {
+        ret = mp_obj_new_list(0, NULL);
+    }
+    free(chans);
+    return ret;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_collect_channels_obj, mp_collect_channels);
 
-STATIC mp_obj_t mp_channel_set_sys_gain(mp_obj_t chan, mp_obj_t vol) {
-    bl00mbox_channel_set_sys_gain(mp_obj_get_int(chan), mp_obj_get_int(vol));
-    return mp_const_none;
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_set_sys_gain_obj,
-                                 mp_channel_set_sys_gain);
+// ========================
+//         PLUGINS
+// ========================
 
-STATIC mp_obj_t mp_channel_get_sys_gain(mp_obj_t chan) {
-    return mp_obj_new_int(bl00mbox_channel_get_sys_gain(mp_obj_get_int(chan)));
+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);
+    self->base.type = &plugin_core_type;
+
+    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"));
+    }
+    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]);
+
+    bl00mbox_plugin_t *plugin =
+        bl00mbox_plugin_create(chan, plugin_kind, init_var);
+
+    if (!plugin) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
+
+    self->plugin = plugin;
+    plugin->parent = self;
+    plugin->parent_self_ref = &self->plugin;
+    bl00mbox_log_info("created plugin");
+    return MP_OBJ_FROM_PTR(self);
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_sys_gain_obj,
-                                 mp_channel_get_sys_gain);
 
-STATIC mp_obj_t mp_channel_set_compute_mean_square(mp_obj_t chan,
-                                                   mp_obj_t vol) {
-    bl00mbox_channel_set_compute_mean_square(mp_obj_get_int(chan),
-                                             mp_obj_get_int(vol));
+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
+    if (self->plugin) bl00mbox_plugin_destroy(self->plugin);
     return mp_const_none;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_set_compute_mean_square_obj,
-                                 mp_channel_set_compute_mean_square);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_plugin_core_del_obj, mp_plugin_core_del);
 
-STATIC mp_obj_t mp_channel_get_compute_mean_square(mp_obj_t chan) {
-    return mp_obj_new_bool(
-        bl00mbox_channel_get_compute_mean_square(mp_obj_get_int(chan)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_compute_mean_square_obj,
-                                 mp_channel_get_compute_mean_square);
-
-STATIC mp_obj_t mp_channel_get_mean_square(mp_obj_t chan) {
-    return mp_obj_new_int(
-        bl00mbox_channel_get_mean_square(mp_obj_get_int(chan)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_mean_square_obj,
-                                 mp_channel_get_mean_square);
-
-STATIC mp_obj_t mp_channel_set_name(mp_obj_t chan, mp_obj_t name) {
-    char *tmp = strdup(mp_obj_str_get_str(name));
-    bl00mbox_channel_set_name(mp_obj_get_int(chan), tmp);
-    free(tmp);
+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_verify(self);
+    if (self->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) {
+        mp_raise_TypeError(
+            MP_ERROR_TEXT("cannot manually delete channel plugin"));
+    }
+    bl00mbox_plugin_destroy(self->plugin);
     return mp_const_none;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_set_name_obj, mp_channel_set_name);
-
-STATIC mp_obj_t mp_channel_get_name(mp_obj_t chan) {
-    char *name = bl00mbox_channel_get_name(mp_obj_get_int(chan));
-    if (name == NULL) return mp_const_none;
-    return mp_obj_new_str(name, strlen(name));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_name_obj, mp_channel_get_name);
-
-STATIC mp_obj_t mp_channel_buds_num(mp_obj_t chan) {
-    return mp_obj_new_int(bl00mbox_channel_buds_num(mp_obj_get_int(chan)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_buds_num_obj, mp_channel_buds_num);
-
-STATIC mp_obj_t mp_channel_get_bud_by_list_pos(mp_obj_t chan, mp_obj_t pos) {
-    return mp_obj_new_int(bl00mbox_channel_get_bud_by_list_pos(
-        mp_obj_get_int(chan), mp_obj_get_int(pos)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_get_bud_by_list_pos_obj,
-                                 mp_channel_get_bud_by_list_pos);
-
-STATIC mp_obj_t mp_channel_conns_num(mp_obj_t chan) {
-    return mp_obj_new_int(bl00mbox_channel_conns_num(mp_obj_get_int(chan)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_conns_num_obj,
-                                 mp_channel_conns_num);
-
-STATIC mp_obj_t mp_channel_mixer_num(mp_obj_t chan) {
-    return mp_obj_new_int(bl00mbox_channel_mixer_num(mp_obj_get_int(chan)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_mixer_num_obj,
-                                 mp_channel_mixer_num);
-
-STATIC mp_obj_t mp_channel_get_bud_by_mixer_list_pos(mp_obj_t chan,
-                                                     mp_obj_t pos) {
-    return mp_obj_new_int(bl00mbox_channel_get_bud_by_mixer_list_pos(
-        mp_obj_get_int(chan), mp_obj_get_int(pos)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_get_bud_by_mixer_list_pos_obj,
-                                 mp_channel_get_bud_by_mixer_list_pos);
+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;
+
+    // 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) {
+            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_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_num_signals) {
+            dest[0] = mp_obj_new_int(plugin->rugin->len_signals);
+        } else if (attr == MP_QSTR_always_render) {
+            dest[0] = mp_obj_new_bool(plugin->always_render);
+        } else if (attr == MP_QSTR_table_len) {
+            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);
+        } 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) },
+};
+STATIC MP_DEFINE_CONST_DICT(plugin_core_locals_dict,
+                            plugin_core_locals_dict_table);
 
-STATIC mp_obj_t mp_channel_get_signal_by_mixer_list_pos(mp_obj_t chan,
-                                                        mp_obj_t pos) {
-    return mp_obj_new_int(bl00mbox_channel_get_signal_by_mixer_list_pos(
-        mp_obj_get_int(chan), mp_obj_get_int(pos)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_get_signal_by_mixer_list_pos_obj,
-                                 mp_channel_get_signal_by_mixer_list_pos);
+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);
 
 // ========================
-//      BUD OPERATIONS
+//         SIGNALS
 // ========================
 
-STATIC mp_obj_t mp_channel_new_bud(mp_obj_t chan, mp_obj_t id,
-                                   mp_obj_t init_var) {
-    bl00mbox_bud_t *bud = bl00mbox_channel_new_bud(
-        mp_obj_get_int(chan), mp_obj_get_int(id), mp_obj_get_int(init_var));
-    if (bud == NULL) return mp_const_none;
-    return mp_obj_new_int(bud->index);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_new_bud_obj, mp_channel_new_bud);
-
-STATIC mp_obj_t mp_channel_delete_bud(mp_obj_t chan, mp_obj_t bud) {
-    bool ret =
-        bl00mbox_channel_delete_bud(mp_obj_get_int(chan), mp_obj_get_int(bud));
-    return mp_obj_new_bool(ret);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_delete_bud_obj,
-                                 mp_channel_delete_bud);
-
-STATIC mp_obj_t mp_channel_bud_exists(mp_obj_t chan, mp_obj_t bud) {
-    bool ret =
-        bl00mbox_channel_bud_exists(mp_obj_get_int(chan), mp_obj_get_int(bud));
-    return mp_obj_new_bool(ret);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_exists_obj,
-                                 mp_channel_bud_exists);
-
-STATIC mp_obj_t mp_channel_bud_get_name(mp_obj_t chan, mp_obj_t bud) {
-    char *name = bl00mbox_channel_bud_get_name(mp_obj_get_int(chan),
-                                               mp_obj_get_int(bud));
-    return mp_obj_new_str(name, strlen(name));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_name_obj,
-                                 mp_channel_bud_get_name);
-
-STATIC mp_obj_t mp_channel_bud_get_description(mp_obj_t chan, mp_obj_t bud) {
-    char *description = bl00mbox_channel_bud_get_description(
-        mp_obj_get_int(chan), mp_obj_get_int(bud));
-    return mp_obj_new_str(description, strlen(description));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_description_obj,
-                                 mp_channel_bud_get_description);
-
-STATIC mp_obj_t mp_channel_bud_get_plugin_id(mp_obj_t chan, mp_obj_t bud) {
-    uint32_t plugin_id = bl00mbox_channel_bud_get_plugin_id(
-        mp_obj_get_int(chan), mp_obj_get_int(bud));
-    return mp_obj_new_int(plugin_id);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_plugin_id_obj,
-                                 mp_channel_bud_get_plugin_id);
-
-STATIC mp_obj_t mp_channel_bud_get_init_var(mp_obj_t chan, mp_obj_t bud) {
-    uint32_t init_var = bl00mbox_channel_bud_get_init_var(mp_obj_get_int(chan),
-                                                          mp_obj_get_int(bud));
-    return mp_obj_new_int(init_var);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_init_var_obj,
-                                 mp_channel_bud_get_init_var);
-
-STATIC mp_obj_t mp_channel_bud_get_num_signals(mp_obj_t chan, mp_obj_t bud) {
-    uint16_t ret = bl00mbox_channel_bud_get_num_signals(mp_obj_get_int(chan),
-                                                        mp_obj_get_int(bud));
-    return mp_obj_new_int(ret);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_num_signals_obj,
-                                 mp_channel_bud_get_num_signals);
-
-STATIC mp_obj_t mp_channel_bud_get_always_render(mp_obj_t chan, mp_obj_t bud) {
-    bool ret = bl00mbox_channel_bud_get_always_render(mp_obj_get_int(chan),
-                                                      mp_obj_get_int(bud));
-    return mp_obj_new_bool(ret);
+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"));
+    }
+    // do this before verification
+    signal_core_obj_t *self = m_new_obj(signal_core_obj_t);
+    int index = mp_obj_get_int(args[1]);
+
+    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"));
+    }
+    self->base.type = &signal_core_type;
+    self->plugin_core = plugin_core;
+    self->signal.rignal = &rugin->signals[index];
+    self->signal.plugin = plugin_core->plugin;
+    self->signal.index = index;
+    return MP_OBJ_FROM_PTR(self);
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_always_render_obj,
-                                 mp_channel_bud_get_always_render);
 
-STATIC mp_obj_t mp_channel_bud_set_always_render(mp_obj_t chan, mp_obj_t bud,
-                                                 mp_obj_t value) {
-    bl00mbox_channel_bud_set_always_render(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_is_true(value));
+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);
+    plugin_core_verify(MP_OBJ_TO_PTR(other->plugin_core));
+    bl00mbox_error_unwrap(
+        bl00mbox_signal_connect(&self->signal, &other->signal));
     return mp_const_none;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_set_always_render_obj,
-                                 mp_channel_bud_set_always_render);
-
-// ========================
-//      SIGNAL OPERATIONS
-// ========================
+MP_DEFINE_CONST_FUN_OBJ_2(mp_signal_core_connect_obj, mp_signal_core_connect);
 
-STATIC mp_obj_t mp_channel_bud_get_signal_name(mp_obj_t chan, mp_obj_t bud,
-                                               mp_obj_t signal) {
-    char *name = bl00mbox_channel_bud_get_signal_name(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_str(name, strlen(name));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_name_obj,
-                                 mp_channel_bud_get_signal_name);
-
-STATIC mp_obj_t mp_channel_bud_get_signal_name_multiplex(mp_obj_t chan,
-                                                         mp_obj_t bud,
-                                                         mp_obj_t signal) {
-    int8_t ret = bl00mbox_channel_bud_get_signal_name_multiplex(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_int(ret);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_name_multiplex_obj,
-                                 mp_channel_bud_get_signal_name_multiplex);
-
-STATIC mp_obj_t mp_channel_bud_get_signal_description(mp_obj_t chan,
-                                                      mp_obj_t bud,
-                                                      mp_obj_t signal) {
-    char *description = bl00mbox_channel_bud_get_signal_description(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_str(description, strlen(description));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_description_obj,
-                                 mp_channel_bud_get_signal_description);
-
-STATIC mp_obj_t mp_channel_bud_get_signal_unit(mp_obj_t chan, mp_obj_t bud,
-                                               mp_obj_t signal) {
-    char *unit = bl00mbox_channel_bud_get_signal_unit(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_str(unit, strlen(unit));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_unit_obj,
-                                 mp_channel_bud_get_signal_unit);
-
-STATIC mp_obj_t mp_channel_bud_get_signal_hints(mp_obj_t chan, mp_obj_t bud,
-                                                mp_obj_t signal) {
-    uint32_t val = bl00mbox_channel_bud_get_signal_hints(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_int(val);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_hints_obj,
-                                 mp_channel_bud_get_signal_hints);
-
-STATIC mp_obj_t mp_channel_bud_set_signal_value(size_t n_args,
-                                                const mp_obj_t *args) {
-    int32_t value = mp_obj_get_int(args[3]);
-    if (value > 32767) value = 32767;
-    if (value < -32767) value = -32767;
-    bool success = bl00mbox_channel_bud_set_signal_value(
-        mp_obj_get_int(args[0]),  // chan
-        mp_obj_get_int(args[1]),  // bud_index
-        mp_obj_get_int(args[2]),  // bud_signal_index
-        value);
-    return mp_obj_new_bool(success);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_channel_bud_set_signal_value_obj,
-                                           4, 4,
-                                           mp_channel_bud_set_signal_value);
-
-STATIC mp_obj_t mp_channel_bud_get_signal_value(mp_obj_t chan, mp_obj_t bud,
-                                                mp_obj_t signal) {
-    int16_t val = bl00mbox_channel_bud_get_signal_value(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_int(val);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_value_obj,
-                                 mp_channel_bud_get_signal_value);
-
-STATIC mp_obj_t mp_channel_subscriber_num(mp_obj_t chan, mp_obj_t bud,
-                                          mp_obj_t signal) {
-    return mp_obj_new_int(bl00mbox_channel_subscriber_num(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal)));
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_subscriber_num_obj,
-                                 mp_channel_subscriber_num);
-
-STATIC mp_obj_t
-mp_channel_get_bud_by_subscriber_list_pos(size_t n_args, const mp_obj_t *args) {
-    return mp_obj_new_int(bl00mbox_channel_get_bud_by_subscriber_list_pos(
-        mp_obj_get_int(args[0]),  // chan
-        mp_obj_get_int(args[1]),  // bud_index
-        mp_obj_get_int(args[2]),  // bud_signal_index
-        mp_obj_get_int(args[3]))  // pos
-    );
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
-    mp_channel_get_bud_by_subscriber_list_pos_obj, 4, 4,
-    mp_channel_get_bud_by_subscriber_list_pos);
-
-STATIC mp_obj_t mp_channel_get_signal_by_subscriber_list_pos(
-    size_t n_args, const mp_obj_t *args) {
-    return mp_obj_new_int(bl00mbox_channel_get_signal_by_subscriber_list_pos(
-        mp_obj_get_int(args[0]),  // chan
-        mp_obj_get_int(args[1]),  // bud_index
-        mp_obj_get_int(args[2]),  // bud_signal_index
-        mp_obj_get_int(args[3]))  // pos
-    );
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
-    mp_channel_get_signal_by_subscriber_list_pos_obj, 4, 4,
-    mp_channel_get_signal_by_subscriber_list_pos);
-
-STATIC mp_obj_t mp_channel_get_source_bud(mp_obj_t chan, mp_obj_t bud,
-                                          mp_obj_t signal) {
-    uint64_t val = bl00mbox_channel_get_source_bud(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_int(val);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_get_source_bud_obj,
-                                 mp_channel_get_source_bud);
-
-STATIC mp_obj_t mp_channel_get_source_signal(mp_obj_t chan, mp_obj_t bud,
-                                             mp_obj_t signal) {
-    uint64_t val = bl00mbox_channel_get_source_signal(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_int(val);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_get_source_signal_obj,
-                                 mp_channel_get_source_signal);
-
-// ========================
-//      TABLE OPERATIONS
-// ========================
-
-STATIC mp_obj_t mp_channel_bud_set_table_value(size_t n_args,
-                                               const mp_obj_t *args) {
-    bool success = bl00mbox_channel_bud_set_table_value(
-        mp_obj_get_int(args[0]),   // chan
-        mp_obj_get_int(args[1]),   // bud_index
-        mp_obj_get_int(args[2]),   // table_index
-        mp_obj_get_int(args[3]));  // value
-    return mp_obj_new_bool(success);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_channel_bud_set_table_value_obj,
-                                           4, 4,
-                                           mp_channel_bud_set_table_value);
-
-STATIC mp_obj_t mp_channel_bud_get_table_value(mp_obj_t chan, mp_obj_t bud,
-                                               mp_obj_t table_index) {
-    int16_t val = bl00mbox_channel_bud_get_table_value(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(table_index));
-    return mp_obj_new_int(val);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_table_value_obj,
-                                 mp_channel_bud_get_table_value);
-
-STATIC mp_obj_t mp_channel_bud_get_table_pointer(mp_obj_t chan, mp_obj_t bud) {
-    int16_t *val = bl00mbox_channel_bud_get_table_pointer(mp_obj_get_int(chan),
-                                                          mp_obj_get_int(bud));
-    return mp_obj_new_int_from_uint((uint32_t)val);
+// 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);
+    plugin_core_verify(MP_OBJ_TO_PTR(self->plugin_core));
+    bl00mbox_error_unwrap(bl00mbox_signal_connect_mx(&self->signal));
+    return mp_const_none;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_table_pointer_obj,
-                                 mp_channel_bud_get_table_pointer);
+MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_connect_mx_obj,
+                          mp_signal_core_connect_mx);
 
-STATIC mp_obj_t mp_channel_bud_get_table_len(mp_obj_t chan, mp_obj_t bud) {
-    uint32_t val = bl00mbox_channel_bud_get_table_len(mp_obj_get_int(chan),
-                                                      mp_obj_get_int(bud));
-    return mp_obj_new_int_from_uint(val);
+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;
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_table_len_obj,
-                                 mp_channel_bud_get_table_len);
-
-// ========================
-//  CONNECTION OPERATIONS
-// ========================
+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);
+    plugin_core_verify(MP_OBJ_TO_PTR(self->plugin_core));
+    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);
+
+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 (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])));
+                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_name) {
+            char *ret = rignal->name;
+            dest[0] = mp_obj_new_str(ret, strlen(ret));
+        } else if (attr == MP_QSTR_mpx) {
+            dest[0] = mp_obj_new_int(rignal->name_multiplex);
+        } else if (attr == MP_QSTR_description) {
+            char *ret = rignal->description;
+            dest[0] = mp_obj_new_str(ret, strlen(ret));
+        } else if (attr == MP_QSTR_unit) {
+            char *ret = rignal->unit;
+            dest[0] = mp_obj_new_str(ret, strlen(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;
+            }
+            dest[0] = mp_obj_new_bool(ret);
+        } else if (attr == MP_QSTR_hints) {
+            // this should maybe be uint someday :>
+            dest[0] = mp_obj_new_int(rignal->hints);
+        } else {
+            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) },
+};
+STATIC MP_DEFINE_CONST_DICT(signal_core_locals_dict,
+                            signal_core_locals_dict_table);
 
-STATIC mp_obj_t mp_channel_disconnect_signal_rx(mp_obj_t chan, mp_obj_t bud,
-                                                mp_obj_t signal) {
-    bool ret = bl00mbox_channel_disconnect_signal_rx(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_bool(ret);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_disconnect_signal_rx_obj,
-                                 mp_channel_disconnect_signal_rx);
-
-STATIC mp_obj_t mp_channel_disconnect_signal_tx(mp_obj_t chan, mp_obj_t bud,
-                                                mp_obj_t signal) {
-    bool ret = bl00mbox_channel_disconnect_signal_tx(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_bool(ret);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_disconnect_signal_tx_obj,
-                                 mp_channel_disconnect_signal_tx);
-
-STATIC mp_obj_t mp_channel_connect_signal(size_t n_args, const mp_obj_t *args) {
-    bool success = bl00mbox_channel_connect_signal(
-        mp_obj_get_int(args[0]),   // chan
-        mp_obj_get_int(args[1]),   // bud_tx_index
-        mp_obj_get_int(args[2]),   // bud_tx_signal_index
-        mp_obj_get_int(args[3]),   // bud_tx_index
-        mp_obj_get_int(args[4]));  // bud_tx_signal_index
-    return mp_obj_new_bool(success);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_channel_connect_signal_obj, 5, 5,
-                                           mp_channel_connect_signal);
-
-STATIC mp_obj_t mp_channel_connect_signal_to_output_mixer(
-    mp_obj_t chan, mp_obj_t bud_index, mp_obj_t bud_signal_index) {
-    bool success = bl00mbox_channel_connect_signal_to_output_mixer(
-        mp_obj_get_int(chan), mp_obj_get_int(bud_index),
-        mp_obj_get_int(bud_signal_index));
-    return mp_obj_new_bool(success);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_connect_signal_to_output_mixer_obj,
-                                 mp_channel_connect_signal_to_output_mixer);
-
-STATIC mp_obj_t mp_channel_disconnect_signal_from_output_mixer(
-    mp_obj_t chan, mp_obj_t bud, mp_obj_t signal) {
-    bool ret = bl00mbox_channel_disconnect_signal_from_output_mixer(
-        mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal));
-    return mp_obj_new_bool(ret);
-}
-STATIC MP_DEFINE_CONST_FUN_OBJ_3(
-    mp_channel_disconnect_signal_from_output_mixer_obj,
-    mp_channel_disconnect_signal_from_output_mixer);
+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 const mp_rom_map_elem_t bl00mbox_globals_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR___name__),
       MP_OBJ_NEW_QSTR(MP_QSTR_sys_bl00mbox) },
 
-    // PLUGIN OPERATIONS
+    // PLUGIN REGISTRY
     { MP_ROM_QSTR(MP_QSTR_plugin_registry_num_plugins),
       MP_ROM_PTR(&mp_plugin_registry_num_plugins_obj) },
     { MP_ROM_QSTR(MP_QSTR_plugin_index_get_id),
@@ -612,142 +640,32 @@ 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) },
 
-    // CHANNEL OPERATIONS
-    { MP_OBJ_NEW_QSTR(MP_QSTR_channel_wire), (mp_obj_t)&channel_wire_type },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_exists),
-      MP_ROM_PTR(&mp_channel_get_exists_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_index_positional_all_chans),
-      MP_ROM_PTR(&mp_channel_get_index_positional_all_chans_obj) },
-    { MP_ROM_QSTR(
-          MP_QSTR_channel_get_index_positional_background_mute_override_chans),
-      MP_ROM_PTR(
-          &mp_channel_get_index_positional_background_mute_override_chans_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_free),
-      MP_ROM_PTR(&mp_channel_get_free_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_set_free),
-      MP_ROM_PTR(&mp_channel_set_free_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_free_index),
-      MP_ROM_PTR(&mp_channel_get_free_index_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_foreground),
-      MP_ROM_PTR(&mp_channel_get_foreground_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_set_foreground),
-      MP_ROM_PTR(&mp_channel_set_foreground_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_background_mute_override),
-      MP_ROM_PTR(&mp_channel_get_background_mute_override_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_set_background_mute_override),
-      MP_ROM_PTR(&mp_channel_set_background_mute_override_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_enable), MP_ROM_PTR(&mp_channel_enable_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_disable),
-      MP_ROM_PTR(&mp_channel_disable_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_clear), MP_ROM_PTR(&mp_channel_clear_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_set_volume),
-      MP_ROM_PTR(&mp_channel_set_volume_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_volume),
-      MP_ROM_PTR(&mp_channel_get_volume_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_set_sys_gain),
-      MP_ROM_PTR(&mp_channel_set_sys_gain_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_sys_gain),
-      MP_ROM_PTR(&mp_channel_get_sys_gain_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_set_compute_mean_square),
-      MP_ROM_PTR(&mp_channel_set_compute_mean_square_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_compute_mean_square),
-      MP_ROM_PTR(&mp_channel_get_compute_mean_square_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_mean_square),
-      MP_ROM_PTR(&mp_channel_get_mean_square_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_set_name),
-      MP_ROM_PTR(&mp_channel_set_name_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_name),
-      MP_ROM_PTR(&mp_channel_get_name_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_buds_num),
-      MP_ROM_PTR(&mp_channel_buds_num_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_bud_by_list_pos),
-      MP_ROM_PTR(&mp_channel_get_bud_by_list_pos_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_conns_num),
-      MP_ROM_PTR(&mp_channel_conns_num_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_mixer_num),
-      MP_ROM_PTR(&mp_channel_mixer_num_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_bud_by_mixer_list_pos),
-      MP_ROM_PTR(&mp_channel_get_bud_by_mixer_list_pos_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_signal_by_mixer_list_pos),
-      MP_ROM_PTR(&mp_channel_get_signal_by_mixer_list_pos_obj) },
-
-    // BUD OPERATIONS
-    { MP_ROM_QSTR(MP_QSTR_channel_new_bud),
-      MP_ROM_PTR(&mp_channel_new_bud_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_delete_bud),
-      MP_ROM_PTR(&mp_channel_delete_bud_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_exists),
-      MP_ROM_PTR(&mp_channel_bud_exists_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_name),
-      MP_ROM_PTR(&mp_channel_bud_get_name_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_description),
-      MP_ROM_PTR(&mp_channel_bud_get_description_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_plugin_id),
-      MP_ROM_PTR(&mp_channel_bud_get_plugin_id_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_init_var),
-      MP_ROM_PTR(&mp_channel_bud_get_init_var_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_num_signals),
-      MP_ROM_PTR(&mp_channel_bud_get_num_signals_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_always_render),
-      MP_ROM_PTR(&mp_channel_bud_get_always_render_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_set_always_render),
-      MP_ROM_PTR(&mp_channel_bud_set_always_render_obj) },
-
-    // SIGNAL OPERATIONS
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_name),
-      MP_ROM_PTR(&mp_channel_bud_get_signal_name_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_name_multiplex),
-      MP_ROM_PTR(&mp_channel_bud_get_signal_name_multiplex_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_description),
-      MP_ROM_PTR(&mp_channel_bud_get_signal_description_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_unit),
-      MP_ROM_PTR(&mp_channel_bud_get_signal_unit_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_set_signal_value),
-      MP_ROM_PTR(&mp_channel_bud_set_signal_value_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_value),
-      MP_ROM_PTR(&mp_channel_bud_get_signal_value_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_hints),
-      MP_ROM_PTR(&mp_channel_bud_get_signal_hints_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_subscriber_num),
-      MP_ROM_PTR(&mp_channel_subscriber_num_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_bud_by_subscriber_list_pos),
-      MP_ROM_PTR(&mp_channel_get_bud_by_subscriber_list_pos_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_signal_by_subscriber_list_pos),
-      MP_ROM_PTR(&mp_channel_get_signal_by_subscriber_list_pos_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_source_bud),
-      MP_ROM_PTR(&mp_channel_get_source_bud_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_get_source_signal),
-      MP_ROM_PTR(&mp_channel_get_source_signal_obj) },
-
-    // TABLE OPERATIONS
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_set_table_value),
-      MP_ROM_PTR(&mp_channel_bud_set_table_value_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_table_value),
-      MP_ROM_PTR(&mp_channel_bud_get_table_value_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_table_pointer),
-      MP_ROM_PTR(&mp_channel_bud_get_table_pointer_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_bud_get_table_len),
-      MP_ROM_PTR(&mp_channel_bud_get_table_len_obj) },
-
-    // CONNECTION OPERATIONS
-    { MP_ROM_QSTR(MP_QSTR_channel_connect_signal),
-      MP_ROM_PTR(&mp_channel_connect_signal_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_disconnect_signal_rx),
-      MP_ROM_PTR(&mp_channel_disconnect_signal_rx_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_disconnect_signal_tx),
-      MP_ROM_PTR(&mp_channel_disconnect_signal_tx_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_connect_signal_to_output_mixer),
-      MP_ROM_PTR(&mp_channel_connect_signal_to_output_mixer_obj) },
-    { MP_ROM_QSTR(MP_QSTR_channel_disconnect_signal_from_output_mixer),
-      MP_ROM_PTR(&mp_channel_disconnect_signal_from_output_mixer_obj) },
+    // CHANNELS
+    { 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 },
 
     // CONSTANTS
-    { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_SCT),
+    { MP_ROM_QSTR(MP_QSTR_SIGNAL_HINT_OUTPUT),
+      MP_ROM_INT(RADSPA_SIGNAL_HINT_OUTPUT) },
+    { MP_ROM_QSTR(MP_QSTR_SIGNAL_HINT_INPUT),
+      MP_ROM_INT(RADSPA_SIGNAL_HINT_INPUT) },
+    { MP_ROM_QSTR(MP_QSTR_SIGNAL_HINT_SCT),
       MP_ROM_INT(RADSPA_SIGNAL_HINT_SCT) },
-    { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_GAIN),
+    { MP_ROM_QSTR(MP_QSTR_SIGNAL_HINT_GAIN),
       MP_ROM_INT(RADSPA_SIGNAL_HINT_GAIN) },
-    { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_TRIGGER),
+    { MP_ROM_QSTR(MP_QSTR_SIGNAL_HINT_TRIGGER),
       MP_ROM_INT(RADSPA_SIGNAL_HINT_TRIGGER) },
+    { MP_ROM_QSTR(MP_QSTR_SIGNAL_HINT_DEPRECATED),
+      MP_ROM_INT(RADSPA_SIGNAL_HINT_DEPRECATED) },
+    { MP_ROM_QSTR(MP_QSTR_BL00MBOX_CHANNEL_PLUGIN_ID),
+      MP_ROM_INT(BL00MBOX_CHANNEL_PLUGIN_ID) },
 };
 
 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
new file mode 100644
index 0000000000..12887b6680
--- /dev/null
+++ b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.c
@@ -0,0 +1,73 @@
+#include "bl00mbox_channel_plugin.h"
+#include "bl00mbox_config.h"
+
+radspa_descriptor_t bl00mbox_channel_plugin_desc = {
+    .name = "channel",
+    .id = BL00MBOX_CHANNEL_PLUGIN_ID,
+    .description = "bl00mbox channel signals",
+    .create_plugin_instance = bl00mbox_channel_plugin_create,
+    .destroy_plugin_instance = radspa_standard_plugin_destroy
+};
+
+// TODO: at this point we CANNOT just forward the same buffer pointer to every client because the buffer
+// pointer serves as a channel-specific source plugin identifier, so we need to memcpy the line in.
+// 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];
+    uint32_t buffer_render_pass_id;
+    int16_t value;
+    int16_t value_render_pass_id;
+} line_in_singleton_t;
+
+static line_in_singleton_t line_in;
+
+static inline void update_line_in_buffer(uint16_t num_samples, 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++){
+        int32_t val = bl00mbox_line_in_interlaced[2*i];
+        val += bl00mbox_line_in_interlaced[2*i+1];
+        val = radspa_clip(val>>1);
+        line_in.buffer[i] = val;
+    }
+    line_in.value = line_in.buffer[0];
+    line_in.value_render_pass_id = render_pass_id;
+    line_in.buffer_render_pass_id = render_pass_id;
+}
+
+static inline void update_line_in_value(uint32_t render_pass_id){
+    if(line_in.value_render_pass_id == render_pass_id) return;
+    int32_t val = bl00mbox_line_in_interlaced[0];
+    val += bl00mbox_line_in_interlaced[1];
+    val = radspa_clip(val>>1);
+    line_in.value = val;
+    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){
+    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);
+    }
+    // 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
+}
+
+void bl00mbox_channel_plugin_update_values(radspa_t * channel_plugin, uint32_t render_pass_id){
+    update_line_in_value(render_pass_id);
+    radspa_signal_t * input_sig = radspa_signal_get_by_index(channel_plugin, 0);
+    input_sig->value = line_in.value;
+}
+
+radspa_t * bl00mbox_channel_plugin_create(uint32_t init_var){
+    radspa_t * channel_plugin = radspa_standard_plugin_create(&bl00mbox_channel_plugin_desc, 2, 0, 0);
+    if(!channel_plugin) return NULL;
+    channel_plugin->render = bl00mbox_channel_plugin_run;
+    radspa_signal_set(channel_plugin, 0, "input", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(channel_plugin, 1, "output", RADSPA_SIGNAL_HINT_INPUT, 0);
+    return channel_plugin;
+}
diff --git a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.h b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.h
new file mode 100644
index 0000000000..0e5adefaf7
--- /dev/null
+++ b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_channel_plugin.h
@@ -0,0 +1,13 @@
+#pragma once
+#include "radspa.h"
+#include "radspa_helpers.h"
+// SPECIAL REQUIREMENTS
+#include "bl00mbox_audio.h"
+
+#define BL00MBOX_CHANNEL_PLUGIN_ID 4002
+
+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_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 5eeb3a606a..b0de70b2b6 100644
--- a/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.c
+++ b/components/bl00mbox/plugins/bl00mbox_specific/bl00mbox_line_in.c
@@ -8,6 +8,12 @@ radspa_descriptor_t bl00mbox_line_in_desc = {
     .destroy_plugin_instance = radspa_standard_plugin_destroy
 };
 
+// TODO: every channel does this conversion individually. this is okay for now, but we can safe cpu
+// by doing this once globally per render pass id.
+// at this point we CANNOT just forward the same buffer pointer to every client because the buffer
+// 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){
     if(bl00mbox_line_in_interlaced == NULL) return;
     radspa_signal_t * left_sig = radspa_signal_get_by_index(line_in, 0);
diff --git a/components/bl00mbox/radspa/radspa.h b/components/bl00mbox/radspa/radspa.h
index cc17e94fb8..7101da6aae 100644
--- a/components/bl00mbox/radspa/radspa.h
+++ b/components/bl00mbox/radspa/radspa.h
@@ -42,6 +42,7 @@
 #define RADSPA_SIGNAL_HINT_OUTPUT (1<<1)
 #define RADSPA_SIGNAL_HINT_TRIGGER (1<<2)
 #define RADSPA_SIGNAL_HINT_GAIN (1<<3)
+#define RADSPA_SIGNAL_HINT_DEPRECATED (1<<4)
 #define RADSPA_SIGNAL_HINT_SCT (1<<5)
 
 #define RADSPA_SIGNAL_VAL_SCT_A440 (INT16_MAX - 6*2400)
diff --git a/components/bl00mbox/radspa/radspa_helpers.h b/components/bl00mbox/radspa/radspa_helpers.h
index 0e639ce9e7..4a0dd878d6 100644
--- a/components/bl00mbox/radspa/radspa_helpers.h
+++ b/components/bl00mbox/radspa/radspa_helpers.h
@@ -74,11 +74,8 @@ inline radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t
  */
 
 inline int16_t radspa_signal_get_value(radspa_signal_t * sig, int16_t index, 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){
+        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];
     }
@@ -101,6 +98,22 @@ inline void radspa_signal_set_value(radspa_signal_t * sig, int16_t index, int32_
     }
 }
 
+inline void radspa_signal_copy(radspa_signal_t * input, radspa_signal_t * output, uint32_t buffer_len, 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;
+    } 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);
+        } else {
+            memcpy(output->buffer, input->buffer, sizeof(int16_t) * buffer_len);
+        }
+    }
+}
+
 inline void radspa_signal_set_value_check_const(radspa_signal_t * sig, int16_t index, int32_t val){
     // disabled for now, causes issues somewhere, no time to track it down
     radspa_signal_set_value(sig, index, val);
@@ -175,4 +188,3 @@ inline int16_t radspa_trigger_get_const(radspa_signal_t * sig, int16_t * hist, u
     }
     return ret;
 }
-
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/buffer.c b/components/bl00mbox/radspa/standard_plugin_lib/buffer.c
new file mode 100644
index 0000000000..7672bf30bf
--- /dev/null
+++ b/components/bl00mbox/radspa/standard_plugin_lib/buffer.c
@@ -0,0 +1,30 @@
+#include "buffer.h"
+
+radspa_descriptor_t buffer_desc = {
+    .name = "buffer",
+    .id = 22,
+    .description = "forwards input signal to output signal",
+    .create_plugin_instance = buffer_create,
+    .destroy_plugin_instance = radspa_standard_plugin_destroy
+};
+
+void buffer_run(radspa_t * buffer, uint16_t num_samples, 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.
+    // for the future, since this is a common use case, we should add some sort of
+    // buffer forwarding flag. but it's okay. still lighter than the old approach
+    // (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_t * buffer_create(uint32_t init_var){
+    radspa_t * buffer = radspa_standard_plugin_create(&buffer_desc, 2, 0, 0);
+    if(!buffer) return NULL;
+    buffer->render = buffer_run;
+    radspa_signal_set(buffer, 0, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(buffer, 1, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
+    return buffer;
+}
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/buffer.h b/components/bl00mbox/radspa/standard_plugin_lib/buffer.h
new file mode 100644
index 0000000000..18b5344f3a
--- /dev/null
+++ b/components/bl00mbox/radspa/standard_plugin_lib/buffer.h
@@ -0,0 +1,7 @@
+#pragma once
+#include <radspa.h>
+#include <radspa_helpers.h>
+
+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);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/mixer.c b/components/bl00mbox/radspa/standard_plugin_lib/mixer.c
index b909a774be..0eebdcf2e1 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/mixer.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/mixer.c
@@ -181,7 +181,7 @@ radspa_t * mixer_create(uint32_t init_var){
 
     radspa_signal_set(mixer, 0, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
     radspa_signal_set(mixer, 1, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN/init_var);
-    radspa_signal_set(mixer, 2, "block_dc", RADSPA_SIGNAL_HINT_INPUT, -32767);
+    radspa_signal_set(mixer, 2, "block_dc", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_DEPRECATED, -32767);
     radspa_signal_set_group(mixer, init_var, 2, 3, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
     radspa_signal_set_group(mixer, init_var, 2, 4, "input_gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN,
                                 RADSPA_SIGNAL_VAL_UNITY_GAIN);
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/mixer.h b/components/bl00mbox/radspa/standard_plugin_lib/mixer.h
index 7b65c5ad20..14d9f01fbf 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 * osc, uint16_t num_samples, uint32_t render_pass_id);
+void mixer_run(radspa_t * mixer, uint16_t num_samples, 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 3a076cc002..3c9ecfb061 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/noise.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/noise.c
@@ -31,7 +31,7 @@ radspa_t * noise_create(uint32_t init_var){
     if(noise == NULL) return NULL;
     noise->render = noise_run;
     radspa_signal_set(noise, NOISE_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
-    radspa_signal_set(noise, NOISE_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT, 32767);
+    radspa_signal_set(noise, NOISE_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_DEPRECATED, 32767);
     radspa_signal_get_by_index(noise, NOISE_SPEED)->unit = "{LFO:-32767} {AUDIO:32767}";
     return noise;
 }
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc.c b/components/bl00mbox/radspa/standard_plugin_lib/osc.c
index 8761d0e4c3..7212d20cb7 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/osc.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/osc.c
@@ -235,7 +235,7 @@ static inline int16_t fake_sine(int16_t tri){
 // performance. we see some low hanging fruits but we can't justify spending any more time
 // on this until flow3r 1.3 is done.
 
-static inline int16_t saw(int32_t saw, osc_data_t * data){
+static inline int16_t saw(int16_t saw, osc_data_t * data){
     int16_t saw_sgn = saw > 0 ? 1 : -1;
     int16_t saw_abs = saw * saw_sgn;
     if(saw_abs > data->blep_coeffs[0]){
@@ -262,7 +262,8 @@ static inline int16_t square(int16_t saw, osc_data_t * data){
 }
 
 static inline void get_blep_coeffs(osc_data_t * data, int32_t incr){
-    if(incr == data->incr_prev) return;
+    // if antialiasing is false we externally write INT16_MAX to blep_coeffs[0]
+    if(incr == data->incr_prev || (!data->antialiasing)) return;
     int32_t incr_norm = ((int64_t) incr * 65535) >> 32; // max 65534
     incr_norm = incr_norm > 0 ? incr_norm : -incr_norm; 
     data->blep_coeffs[0] = 32767 - incr_norm;
@@ -286,7 +287,7 @@ static inline int16_t apply_morph(osc_data_t * data, uint32_t counter){
 }
 
 static inline int16_t apply_waveform(osc_data_t * data, int16_t input, int32_t incr){
-    int32_t a, b;
+    int16_t a, b;
     if(data->waveform_coeffs[0] < (32767-10922)){
         b = triangle(input);
         a = fake_sine(b);
@@ -353,6 +354,8 @@ static inline int16_t apply_waveform(osc_data_t * data, int16_t input, int32_t i
     radspa_signal_set_value_noclip(sync_out_sig, i, data->counter > (1UL<<31) ? 32767 : -32767); \
 }
 
+#define ANTIALIASING_INDEX 64
+
 void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id){
     osc_data_t * data = osc->plugin_data;
     int8_t * table = (int8_t * ) osc->plugin_table;
@@ -379,6 +382,9 @@ void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id){
     bool out_const = out_sig->buffer == NULL;
     bool sync_out_const = sync_out_sig->buffer == NULL;
 
+    data->antialiasing = table[ANTIALIASING_INDEX];
+    if(!data->antialiasing) data->blep_coeffs[0] = INT16_MAX;
+
     bool lfo = speed < -10922; // manual setting
     lfo = lfo || (out_const && sync_out_const); // unlikely, host should ideally prevent that case
 
@@ -401,6 +407,7 @@ void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id){
         // trigger  signal? would result in "auto-humanize" when attached to any other consumer
         // but that's fine we think.
         radspa_signal_set_const_value(sync_out_sig, data->counter > (1UL<<31) ? 32767 : -32767);
+        // max index 63
         table[(data->counter<<1)>>(32-6)] = out >> 8;
     }
 
@@ -428,7 +435,9 @@ void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id){
 #pragma GCC pop_options
 
 radspa_t * osc_create(uint32_t init_var){
-    radspa_t * osc = radspa_standard_plugin_create(&osc_desc, OSC_NUM_SIGNALS, sizeof(osc_data_t), 32);
+    radspa_t * osc = radspa_standard_plugin_create(&osc_desc, OSC_NUM_SIGNALS, sizeof(osc_data_t), 33);
+    int8_t * table = (int8_t * ) osc->plugin_table;
+    table[ANTIALIASING_INDEX] = true;
     osc->render = osc_run;
     radspa_signal_set(osc, OSC_OUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
     radspa_signal_set(osc, OSC_PITCH, "pitch", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440);
@@ -438,7 +447,7 @@ radspa_t * osc_create(uint32_t init_var){
     radspa_signal_set(osc, OSC_SYNC_IN, "sync_input", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
     radspa_signal_set(osc, OSC_SYNC_IN_PHASE, "sync_input_phase", RADSPA_SIGNAL_HINT_INPUT, 0);
     radspa_signal_set(osc, OSC_SYNC_OUT, "sync_output", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
-    radspa_signal_set(osc, OSC_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT, 0);
+    radspa_signal_set(osc, OSC_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_DEPRECATED, 0);
 
     radspa_signal_get_by_index(osc, OSC_WAVEFORM)->unit = "{SINE:-32767} {TRI:-10922} {SQUARE:10922} {SAW:32767}";
     radspa_signal_get_by_index(osc, OSC_SPEED)->unit = "{LFO:-32767} {AUTO:0} {AUDIO:32767}";
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc.h b/components/bl00mbox/radspa/standard_plugin_lib/osc.h
index 607c9e83e8..e24e21b1a4 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/osc.h
+++ b/components/bl00mbox/radspa/standard_plugin_lib/osc.h
@@ -16,6 +16,7 @@ typedef struct {
     int32_t morph_coeffs[3];
     int16_t morph_gate_prev;
     bool morph_no_pwm_prev;
+    bool antialiasing;
 } osc_data_t;
 
 extern radspa_descriptor_t osc_desc;
diff --git a/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c b/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c
index a0dd0e6b57..ff9c6da3c7 100644
--- a/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c
+++ b/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c
@@ -112,7 +112,7 @@ radspa_t * range_shifter_create(uint32_t init_var){
     radspa_signal_set_group(range_shifter, 2, 1, RANGE_SHIFTER_OUTPUT_A, "output_range", RADSPA_SIGNAL_HINT_INPUT, -32767);
     radspa_signal_set(range_shifter, RANGE_SHIFTER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0);
     radspa_signal_set_group(range_shifter, 2, 1, RANGE_SHIFTER_INPUT_A, "input_range", RADSPA_SIGNAL_HINT_INPUT, -32767);
-    radspa_signal_set(range_shifter, RANGE_SHIFTER_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT, 32767);
+    radspa_signal_set(range_shifter, RANGE_SHIFTER_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_DEPRECATED, 32767);
     radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_SPEED)->unit = "{SLOW:-32767} {SLOW_RANGE:0} {FAST:32767}";
     range_shifter->signals[RANGE_SHIFTER_OUTPUT_B].value = 32767;
     range_shifter->signals[RANGE_SHIFTER_INPUT_B].value = 32767;
diff --git a/python_payload/st3m/reactor.py b/python_payload/st3m/reactor.py
index a96a8a14bd..3f9f3342ac 100644
--- a/python_payload/st3m/reactor.py
+++ b/python_payload/st3m/reactor.py
@@ -9,6 +9,8 @@ import sys_display
 import sys_kernel
 import captouch
 
+import bl00mbox
+
 
 class Responder(ABCBase):
     """
@@ -164,3 +166,7 @@ class Reactor:
         # Share!
         if ftop.run(delta):
             print(ftop.report)
+
+        # Background!
+        for callback in bl00mbox.Sys.collect_active_callbacks():
+            callback(hr, delta)
diff --git a/python_payload/st3m/run.py b/python_payload/st3m/run.py
index b8a17f8708..81565dde08 100644
--- a/python_payload/st3m/run.py
+++ b/python_payload/st3m/run.py
@@ -213,10 +213,6 @@ def run_main() -> None:
     except Exception as e:
         log.error(f"Failed to set hostname {e}")
 
-    for chan in bl00mbox.all_channels():
-        chan.clear()
-        chan.free = True
-
     bundles = BundleManager()
     bundles.update()
     application_settings.synchronize_apps(bundles.bundles.values())
diff --git a/python_payload/st3m/ui/mixer.py b/python_payload/st3m/ui/mixer.py
index fdae6eb58f..26bcf8b704 100644
--- a/python_payload/st3m/ui/mixer.py
+++ b/python_payload/st3m/ui/mixer.py
@@ -5,12 +5,9 @@ import media
 
 
 def recall_blm_channel_stats(blm_chan):
-    print(blm_chan)
     if blm_chan.free:
-        print("wha-!")
         return
     if blm_chan.name in session_channel_vol_mute:
-        print("whee")
         chan = bl00mboxChannel(bl00mbox.SysChannel(blm_chan))
         vol, mute = session_channel_vol_mute[chan.get_name()]
         if mute:
@@ -185,14 +182,11 @@ class AudioMixer(Responder):
 
     def _refresh(self):
         self._chans = [mediaChannel()]
-        for c in bl00mbox.active_channels():
+        for c in bl00mbox.Sys.collect_channels(True):
             chan = bl00mboxChannel(c)
-            if not chan.blm.free and (
-                chan.blm.foreground or chan.blm.background_mute_override
-            ):
-                chan.prev_compute_rms = chan.blm.compute_rms
-                chan.blm.compute_rms = True
-                self._chans += [chan]
+            chan.prev_compute_rms = chan.blm.compute_rms
+            chan.blm.compute_rms = True
+            self._chans += [chan]
         """
         for i in range(5):
             self._chans += [Channel()]
diff --git a/sim/fakes/bl00mbox.py b/sim/fakes/bl00mbox.py
index 334d606493..eed3aba0d2 100644
--- a/sim/fakes/bl00mbox.py
+++ b/sim/fakes/bl00mbox.py
@@ -29,12 +29,16 @@ class _mock(list):
         return _mock()
 
 
-def all_channels():
-    yield Channel(0)
-
-
-def active_channels():
-    yield Channel(0)
+class Sys:
+    @staticmethod
+    def collect_channels(active):
+        if False:
+            yield None
+
+    @staticmethod
+    def collect_active_callbacks():
+        if False:
+            yield None
 
 
 class Channel:
-- 
GitLab