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(&liverter_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