Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 89-apps-should-be-able-to-specify-if-they-want-wifi-to-be-disabled-when-entering-them
  • 9Rmain
  • allow-reloading-sunmenu
  • always-have-a-wifi-instance
  • anon/gpndemo
  • anon/update-sim
  • anon/webflasher
  • app_text_viewer
  • audio_input
  • audio_io
  • blm_dev_chan
  • ch3/bl00mbox_docs
  • ci-1690580595
  • dev_p4
  • dev_p4-iggy
  • dev_p4-iggy-rebased
  • dx/dldldld
  • dx/fb-save-restore
  • dx/hint-hint
  • dx/jacksense-headset-mic-only
  • events
  • fil3s-limit-filesize
  • fil3s-media
  • fpletz/flake
  • gr33nhouse-improvements
  • history-rewrite
  • icon-flower
  • iggy/stemming
  • iggy/stemming_merge
  • led_fix_fix
  • main
  • main+schneider
  • media_has_video_has_audio
  • micropython_api
  • mixer2
  • moon2_demo_temp
  • moon2_migrate_apps
  • more-accurate-battery
  • pippin/ctx_sprite_sheet_support
  • pippin/display-python-errors-on-display
  • pippin/make_empty_drawlists_skip_render_and_blit
  • pippin/more-accurate-battery
  • pippin/tcp_redirect_hack
  • pippin/tune_ctx_config_update_from_upstream
  • pippin/uhm_flash_access_bust
  • pressable_bugfix
  • py_only_update_fps_overlay_when_changing
  • q3k/doom-poc
  • q3k/render-to-texture
  • rahix/flow3rseeds
  • raw_captouch_new
  • raw_captouch_old
  • release/1.0.0
  • release/1.1.0
  • release/1.1.1
  • release/1.2.0
  • release/1.3.0
  • release/1.4.0
  • restore_blit
  • return_of_melodic_demo
  • rev4_micropython
  • schneider/application-remove-name
  • schneider/bhi581
  • schneider/factory_test
  • schneider/recovery
  • scope_hack
  • sdkconfig-spiram-tinyusb
  • sec/auto-nick
  • sec/blinky
  • sector_size_512
  • shoegaze-fps
  • smaller_gradient_lut
  • store_delta_ms_and_ins_as_class_members
  • task_cleanup
  • uctx-wip
  • w1f1-in-sim
  • widgets_draw
  • wifi-json-error-handling
  • wip-docs
  • wip-tinyusb
  • v1.0.0
  • v1.0.0+rc1
  • v1.0.0+rc2
  • v1.0.0+rc3
  • v1.0.0+rc4
  • v1.0.0+rc5
  • v1.0.0+rc6
  • v1.1.0
  • v1.1.0+rc1
  • v1.1.1
  • v1.2.0
  • v1.2.0+rc1
  • v1.3.0
  • v1.4.0
94 results

Target

Select target project
  • flow3r/flow3r-firmware
  • Vespasian/flow3r-firmware
  • alxndr42/flow3r-firmware
  • pl/flow3r-firmware
  • Kari/flow3r-firmware
  • raimue/flow3r-firmware
  • grandchild/flow3r-firmware
  • mu5tach3/flow3r-firmware
  • Nervengift/flow3r-firmware
  • arachnist/flow3r-firmware
  • TheNewCivilian/flow3r-firmware
  • alibi/flow3r-firmware
  • manuel_v/flow3r-firmware
  • xeniter/flow3r-firmware
  • maxbachmann/flow3r-firmware
  • yGifoom/flow3r-firmware
  • istobic/flow3r-firmware
  • EiNSTeiN_/flow3r-firmware
  • gnudalf/flow3r-firmware
  • 999eagle/flow3r-firmware
  • toerb/flow3r-firmware
  • pandark/flow3r-firmware
  • teal/flow3r-firmware
  • x42/flow3r-firmware
  • alufers/flow3r-firmware
  • dos/flow3r-firmware
  • yrlf/flow3r-firmware
  • LuKaRo/flow3r-firmware
  • ThomasElRubio/flow3r-firmware
  • ai/flow3r-firmware
  • T_X/flow3r-firmware
  • highTower/flow3r-firmware
  • beanieboi/flow3r-firmware
  • Woazboat/flow3r-firmware
  • gooniesbro/flow3r-firmware
  • marvino/flow3r-firmware
  • kressnerd/flow3r-firmware
  • quazgar/flow3r-firmware
  • aoid/flow3r-firmware
  • jkj/flow3r-firmware
  • naomi/flow3r-firmware
41 results
Select Git revision
  • 89-apps-should-be-able-to-specify-if-they-want-wifi-to-be-disabled-when-entering-them
  • 9Rmain
  • allow-reloading-sunmenu
  • always-have-a-wifi-instance
  • anon/gpndemo
  • anon/update-sim
  • anon/webflasher
  • app_text_viewer
  • audio_input
  • audio_io
  • blm_dev_chan
  • ch3/bl00mbox_docs
  • ci-1690580595
  • dev_p4
  • dev_p4-iggy
  • dev_p4-iggy-rebased
  • dx/dldldld
  • dx/fb-save-restore
  • dx/hint-hint
  • dx/jacksense-headset-mic-only
  • events
  • fil3s-limit-filesize
  • fil3s-media
  • fpletz/flake
  • gr33nhouse-improvements
  • history-rewrite
  • icon-flower
  • iggy/stemming
  • iggy/stemming_merge
  • led_fix_fix
  • main
  • main+schneider
  • media_has_video_has_audio
  • micropython_api
  • mixer2
  • moon2_demo_temp
  • moon2_migrate_apps
  • more-accurate-battery
  • pippin/ctx_sprite_sheet_support
  • pippin/display-python-errors-on-display
  • pippin/make_empty_drawlists_skip_render_and_blit
  • pippin/more-accurate-battery
  • pippin/tcp_redirect_hack
  • pippin/tune_ctx_config_update_from_upstream
  • pippin/uhm_flash_access_bust
  • pressable_bugfix
  • py_only_update_fps_overlay_when_changing
  • q3k/doom-poc
  • q3k/render-to-texture
  • rahix/flow3rseeds
  • raw_captouch_new
  • raw_captouch_old
  • release/1.0.0
  • release/1.1.0
  • release/1.1.1
  • release/1.2.0
  • release/1.3.0
  • release/1.4.0
  • restore_blit
  • return_of_melodic_demo
  • rev4_micropython
  • schneider/application-remove-name
  • schneider/bhi581
  • schneider/factory_test
  • schneider/recovery
  • scope_hack
  • sdkconfig-spiram-tinyusb
  • sec/auto-nick
  • sec/blinky
  • sector_size_512
  • shoegaze-fps
  • smaller_gradient_lut
  • store_delta_ms_and_ins_as_class_members
  • task_cleanup
  • uctx-wip
  • w1f1-in-sim
  • widgets_draw
  • wifi-json-error-handling
  • wip-docs
  • wip-tinyusb
  • v1.0.0
  • v1.0.0+rc1
  • v1.0.0+rc2
  • v1.0.0+rc3
  • v1.0.0+rc4
  • v1.0.0+rc5
  • v1.0.0+rc6
  • v1.1.0
  • v1.1.0+rc1
  • v1.1.1
  • v1.2.0
  • v1.2.0+rc1
  • v1.3.0
  • v1.4.0
94 results
Show changes
Showing
with 3151 additions and 1085 deletions
//SPDX-License-Identifier: CC0-1.0
#include "bl00mbox_radspa_requirements.h"
bool radspa_host_request_buffer_render(int16_t * buf, uint16_t num_samples){
bl00mbox_bud_t * bud = ((bl00mbox_connection_t *) buf)->source_bud;
bl00mbox_audio_bud_render(bud, num_samples);
bool radspa_host_request_buffer_render(int16_t * buf){
bl00mbox_plugin_t * plugin = ((bl00mbox_connection_t *) buf)->source.plugin;
bl00mbox_audio_plugin_render(plugin);
return 1;
}
......@@ -58,41 +58,4 @@ uint32_t radspa_sct_to_rel_freq(int16_t sct, int16_t undersample_pow){
return ret;
}
int16_t radspa_clip(int32_t a){
if(a > 32767){
return 32767;
} else if(a < -32767){
return -32767;
}
return a;
}
int16_t radspa_add_sat(int32_t a, int32_t b){ return radspa_clip(a+b); }
int32_t radspa_mult_shift(int32_t a, int32_t b){ return radspa_clip((a*b)>>15); }
int32_t radspa_gain(int32_t a, int32_t b){ return radspa_clip((a*b)>>12); }
int16_t radspa_trigger_start(int16_t velocity, int16_t * hist){
int16_t ret = ((* hist) > 0) ? -velocity : velocity;
(* hist) = ret;
return ret;
}
int16_t radspa_trigger_stop(int16_t * hist){
(* hist) = 0;
return 0;
}
int16_t radspa_trigger_get(int16_t trigger_signal, int16_t * hist){
int16_t ret = 0;
if((!trigger_signal) && (* hist)){ //stop
ret = -1;
} else if(trigger_signal > 0 ){
if((* hist) <= 0) ret = trigger_signal;
} else if(trigger_signal < 0 ){
if((* hist) >= 0) ret = -trigger_signal;
}
(* hist) = trigger_signal;
return ret;
}
int16_t radspa_random(){ return xoroshiro64star()>>16; }
//SPDX-License-Identifier: CC0-1.0
#include "bl00mbox_user.h"
static uint64_t bl00mbox_bud_index = 1;
bl00mbox_bud_t * bl00mbox_channel_get_bud_by_index(uint8_t channel, uint32_t index);
extern void bl00mbox_disconnect_rx_callback(void * rx, uint16_t signal_index);
extern void bl00mbox_connect_rx_callback(void * rx, void * tx, uint16_t signal_index);
extern void bl00mbox_disconnect_mx_callback(void * chan, void * tx);
extern void bl00mbox_connect_mx_callback(void * chan, void * tx);
uint16_t bl00mbox_channel_buds_num(uint8_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++;
// get signal struct from a signal index
radspa_signal_t * bl00mbox_signal_get_by_index(radspa_t * plugin, uint16_t signal_index){
return &(plugin->signals[signal_index]);
}
}
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(uint8_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++;
static bool signal_is_input(bl00mbox_signal_t * signal){
return signal->rignal->hints & RADSPA_SIGNAL_HINT_INPUT ? true : false;
}
static bool signal_is_output(bl00mbox_signal_t * signal){
return signal->rignal->hints & RADSPA_SIGNAL_HINT_OUTPUT ? true : false;
}
return 0;
static bool signal_equals(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
return (some->plugin == other->plugin) && (some->index == other->index);
}
uint16_t bl00mbox_channel_conns_num(uint8_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++;
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;
}
bool bl00mbox_signal_is_output(bl00mbox_signal_t * signal){
return signal_is_output(signal);
}
return ret;
bool bl00mbox_signal_is_input(bl00mbox_signal_t * signal){
return signal_is_input(signal);
}
uint16_t bl00mbox_channel_mixer_num(uint8_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++;
bl00mbox_connection_t * bl00mbox_connection_from_signal(bl00mbox_signal_t * signal){
return conn_from_signal(signal);
}
uint16_t bl00mbox_channel_plugins_num(bl00mbox_channel_t * chan){
return chan->plugins.len;
}
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;
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;
}
uint64_t bl00mbox_channel_get_bud_by_mixer_list_pos(uint8_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;
uint16_t bl00mbox_channel_conns_num(bl00mbox_channel_t * chan){
return chan->connections.len;
}
uint32_t bl00mbox_channel_get_signal_by_mixer_list_pos(uint8_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;
uint16_t bl00mbox_channel_mixer_num(bl00mbox_channel_t * chan){
return chan->roots.len;
}
uint16_t bl00mbox_channel_subscriber_num(uint8_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 = radspa_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;
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;
uint16_t ret = 0;
if(conn->subs != NULL){
bl00mbox_connection_subscriber_t * last = conn->subs;
ret++;
while(last->next != NULL){
last = last->next;
ret++;
}
ret->len = chan->roots.len;
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 ret;
}
uint64_t bl00mbox_channel_get_bud_by_subscriber_list_pos(uint8_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 = radspa_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;
}
int32_t bl00mbox_channel_get_signal_by_subscriber_list_pos(uint8_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 = radspa_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;
}
uint64_t bl00mbox_channel_get_source_bud(uint8_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 = radspa_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;
}
uint16_t bl00mbox_channel_get_source_signal(uint8_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 = radspa_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;
}
static bl00mbox_connection_t * create_connection(uint8_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;
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 {
// 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;
if(chan->connections != NULL){
bl00mbox_connection_t * last = chan->connections;
while(last->chan_next != NULL){
last = last->chan_next;
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;
}
last->chan_next = ret;
} else {
chan->connections = ret;
}
return ret;
}
static bool weak_delete_connection(bl00mbox_connection_t * conn){
if(conn->subs != NULL) return false;
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];
// nullify source bud connection;
bl00mbox_bud_t * bud = conn->source_bud;
if(bud != NULL){
radspa_signal_t * tx = radspa_signal_get_by_index(bud->plugin, conn->signal_index);
if(tx != NULL){
bl00mbox_audio_waitfor_pointer_change(&(tx->buffer), NULL);
}
}
// 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;
// pop from channel list
bl00mbox_channel_t * chan = bl00mbox_get_channel(conn->channel);
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){
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;
}
}
if(prev->chan_next != NULL) bl00mbox_audio_waitfor_pointer_change(&(prev->chan_next), conn->chan_next);
} else {
bl00mbox_audio_waitfor_pointer_change(&(chan->connections), conn->chan_next);
if(!is_duplicate){
render_plugins->elems[index++] = conn->source.plugin;
}
}
free(conn);
return true;
// 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_bud_t * bl00mbox_channel_get_bud_by_index(uint8_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;
}
return bud;
if(!is_duplicate) render_plugins->elems[index++] = plugin;
}
render_plugins->len = index;
bl00mbox_bud_t * bl00mbox_channel_new_bud(uint8_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->plugin = plugin;
bud->channel = channel;
//TODO: look for empty indices
bud->index = bl00mbox_bud_index;
bl00mbox_bud_index++;
bud->chan_next = NULL;
// append to channel bud list
if(chan->buds == NULL){
chan->buds = bud;
} else {
bl00mbox_bud_t * last = chan->buds;
while(last->chan_next != NULL){ last = last->chan_next; }
last->chan_next = bud;
}
bl00mbox_channel_event(channel);
return bud;
// 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_delete_bud(uint8_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 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){
break;
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")
}
prev = seek;
seek = seek->chan_next;
#ifdef BL00MBOX_DEBUG
else {
bl00mbox_log_info("new render data, %d plugins, %d buffers",
(int) render_plugins->len, (int) render_buffers->len);
}
if(seek != NULL){
if(prev != NULL){
bl00mbox_audio_waitfor_pointer_change(&(prev->chan_next), seek->chan_next);
} else {
bl00mbox_audio_waitfor_pointer_change(&(chan->buds), seek->chan_next);
#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);
}
free_later = true;
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; }
plugin->init_var = init_var;
plugin->rugin = rugin;
plugin->channel = chan;
plugin->id = chan->plugin_id++;
return plugin;
}
bl00mbox_plugin_t * bl00mbox_plugin_create(bl00mbox_channel_t * chan, uint32_t id, uint32_t init_var){
// doesn't instantiate, don't free
radspa_descriptor_t * desc = bl00mbox_plugin_registry_get_descriptor_from_id(id);
if(desc == NULL) return NULL;
bl00mbox_plugin_t * plugin = bl00mbox_plugin_create_unlisted(chan, desc, init_var);
if(!bl00mbox_set_add_skip_unique_check(&chan->plugins, plugin)){
plugin->rugin->descriptor->destroy_plugin_instance(plugin->rugin);
free(plugin);
return NULL;
}
bud->plugin->descriptor->destroy_plugin_instance(bud->plugin);
if(free_later) free(seek);
return true;
bl00mbox_channel_event(chan);
return plugin;
}
bool bl00mbox_channel_clear(uint8_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;
void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin){
bl00mbox_channel_t * chan = plugin->channel;
// remove from parent
if(* (plugin->parent_self_ref) != plugin){
bl00mbox_log_error("plugin: parent_self_ref improper: plugin %s, %p, ref %p",
plugin->rugin->descriptor->name, plugin, * (plugin->parent_self_ref));
}
return true;
* (plugin->parent_self_ref) = NULL;
// 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);
}
bool bl00mbox_channel_connect_signal_to_output_mixer(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
if(chan == NULL) return false;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
if(bud == NULL) return false;
radspa_signal_t * tx = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(tx == NULL) return false;
if(!(tx->hints & RADSPA_SIGNAL_HINT_OUTPUT)) return false;
// pop from sets
bl00mbox_plugin_set_always_render(plugin, false);
bl00mbox_set_remove(&chan->plugins, plugin);
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; }
if(plugin == chan->channel_plugin) chan->channel_plugin = NULL;
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->rugin->descriptor->destroy_plugin_instance(plugin->rugin);
free(plugin);
}
static bl00mbox_connection_t * create_connection(bl00mbox_signal_t * source){
if(!signal_is_output(source)) return NULL;
bl00mbox_connection_t * ret = calloc(1, sizeof(bl00mbox_connection_t));
if(ret == NULL) return NULL;
ret->subscribers.key = signal_equals;
memcpy(&ret->source, source, sizeof(bl00mbox_signal_t));
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);
source->rignal->buffer = &ret->buffer;
bl00mbox_give_lock(&chan->render_lock);
return ret;
}
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;
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;
}
root->con = conn;
root->next = NULL;
static bool weak_delete_connection(bl00mbox_connection_t * conn){
// are there still active connections?
if(conn->subscribers.len || conn->connected_to_mixer) return false;
bl00mbox_channel_t * chan = conn->source.plugin->channel;
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;
}
// disconnect buffer from plugin
bl00mbox_take_lock(&chan->render_lock);
conn->source.rignal->buffer = NULL;
bl00mbox_give_lock(&chan->render_lock);
// remove from connections list
if(!bl00mbox_set_remove(&chan->connections, conn)) bl00mbox_log_error("connection list corruption");
bl00mbox_channel_event(channel);
#ifdef BL00MBOX_DEBUG
if(conn->subscribers.len) bl00mbox_log_error("subscribers nonempty");
#endif
free(conn);
return true;
}
bool bl00mbox_channel_disconnect_signal_from_output_mixer(uint8_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 = radspa_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_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;
bl00mbox_connection_t * conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct
if(conn == NULL) return false; //not connected
bl00mbox_connection_t * conn = weak_create_connection(signal);
if(!conn) goto failed;
bl00mbox_channel_root_t * rt = chan->root_list;
bl00mbox_channel_root_t * rt_prev = NULL;
// 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;
while(rt != NULL){
if(rt->con == conn) break;
rt_prev = rt;
rt = rt->next;
}
if(rt != NULL){
if(rt_prev == NULL){
bl00mbox_audio_waitfor_pointer_change(&(chan->root_list), rt->next);
} else {
bl00mbox_audio_waitfor_pointer_change(&(rt_prev->next), rt->next);
}
free(rt);
}
update_roots(chan);
bl00mbox_connect_mx_callback(chan->parent, signal->plugin->parent);
bl00mbox_channel_event(chan);
return BL00MBOX_ERROR_OK;
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){
if(prev != NULL){
bl00mbox_audio_waitfor_pointer_change(&(prev->next), seek->next);
} else {
bl00mbox_audio_waitfor_pointer_change(&(conn->subs), seek->next);
}
free(seek);
failed:
if(conn){
conn->connected_to_mixer = false;
weak_delete_connection(conn);
}
bl00mbox_log_error("couldn't connect to mixer");
return BL00MBOX_ERROR_OOM;
}
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
conn->connected_to_mixer = false;
bl00mbox_channel_t * chan = signal->plugin->channel;
bl00mbox_set_remove(&chan->roots, conn);
update_roots(chan);
bl00mbox_disconnect_mx_callback(chan->parent, signal->plugin->parent);
weak_delete_connection(conn);
bl00mbox_channel_event(channel);
return true;
}
bool bl00mbox_channel_disconnect_signal_rx(uint8_t channel, uint32_t bud_rx_index, uint32_t bud_rx_signal_index){
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
static void bl00mbox_signal_disconnect_rx(bl00mbox_signal_t * signal){
if(!signal_is_input(signal)) return;
radspa_signal_t * rx = radspa_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;
// try to get connection and return if not connected
bl00mbox_connection_t * conn = conn_from_signal(signal);
if(!conn) return;
bl00mbox_connection_t * conn = (bl00mbox_connection_t *) rx->buffer; // buffer sits on top of struct
if(conn == NULL) return false; //not connected
bl00mbox_channel_t * chan = signal->plugin->channel;
bl00mbox_take_lock(&chan->render_lock);
signal->rignal->buffer = NULL;
bl00mbox_give_lock(&chan->render_lock);
bl00mbox_bud_t * bud_tx = conn->source_bud;
if(bud_tx == NULL) return false; // bud index doesn't exist
radspa_signal_t * tx = radspa_signal_get_by_index(bud_tx->plugin, conn->signal_index);
if(tx == NULL) return false; // signal index doesn't exist
if(signal->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) update_roots(chan);
bl00mbox_audio_waitfor_pointer_change(&(rx->buffer), NULL);
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){
if(prev != NULL){
bl00mbox_audio_waitfor_pointer_change(&(prev->next), seek->next);
} else {
bl00mbox_audio_waitfor_pointer_change(&(conn->subs), seek->next);
}
free(seek);
}
}
bl00mbox_set_remove(&conn->subscribers, signal);
weak_delete_connection(conn);
bl00mbox_channel_event(channel);
return true;
bl00mbox_disconnect_rx_callback(signal->plugin->parent, signal->index);
}
bool bl00mbox_channel_disconnect_signal_tx(uint8_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
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;
radspa_signal_t * tx = radspa_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;
// disconnect from mixer
if(conn->connected_to_mixer) bl00mbox_signal_disconnect_mx(signal);
bl00mbox_connection_t * conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct
if(conn == NULL) return false; //not connected
// 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);
}
}
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;
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);
}
bl00mbox_channel_event(channel);
return true;
if(conn_from_signal(signal)) bl00mbox_log_error("connection persists after disconnect");
}
bool bl00mbox_channel_disconnect_signal(uint8_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 = radspa_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_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;
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;
// 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 {
return BL00MBOX_ERROR_INVALID_CONNECTION;
}
bool bl00mbox_channel_connect_signal(uint8_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
radspa_signal_t * rx = radspa_signal_get_by_index(bud_rx->plugin, bud_rx_signal_index);
radspa_signal_t * tx = radspa_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;
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;
if(!(conn = conn_from_signal(signal_tx))){
if(!(conn = create_connection(signal_tx))) return BL00MBOX_ERROR_OOM;
} else {
if(rx->buffer == tx->buffer) return false; // already connected
conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct
if(signals_are_connected(signal_rx, signal_tx)) return BL00MBOX_ERROR_OK; // already connected
}
bl00mbox_channel_disconnect_signal_rx(channel, bud_rx_index, bud_rx_signal_index);
bl00mbox_signal_disconnect_rx(signal_rx);
sub = malloc(sizeof(bl00mbox_connection_subscriber_t));
if(sub == NULL){
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;
}
bl00mbox_signal_t * sub;
rx->buffer = tx->buffer;
bl00mbox_channel_event(channel);
return true;
sub = malloc(sizeof(bl00mbox_signal_t));
if(!sub){
weak_delete_connection(conn);
return BL00MBOX_ERROR_OOM;
}
bool bl00mbox_channel_bud_exists(uint8_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;
}
}
memcpy(sub, signal_rx, sizeof(bl00mbox_signal_t));
char * bl00mbox_channel_bud_get_name(uint8_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;
}
char * bl00mbox_channel_bud_get_description(uint8_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;
}
uint32_t bl00mbox_channel_bud_get_plugin_id(uint8_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;
}
uint16_t bl00mbox_channel_bud_get_num_signals(uint8_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;
}
char * bl00mbox_channel_bud_get_signal_name(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
if(chan == NULL) return false;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
if(bud == NULL) return false;
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(sig == NULL) return false;
return sig->name;
}
int8_t bl00mbox_channel_bud_get_signal_name_multiplex(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
if(chan == NULL) return false;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
if(bud == NULL) return false;
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(sig == NULL) return false;
return sig->name_multiplex;
}
char * bl00mbox_channel_bud_get_signal_description(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
if(chan == NULL) return false;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
if(bud == NULL) return false;
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(sig == NULL) return false;
return sig->description;
}
char * bl00mbox_channel_bud_get_signal_unit(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
if(chan == NULL) return false;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
if(bud == NULL) return false;
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(sig == NULL) return false;
return sig->unit;
}
bool bl00mbox_channel_bud_set_signal_value(uint8_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 = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(sig == NULL) return false;
sig->value = value;
bl00mbox_channel_event(channel);
return true;
}
bl00mbox_set_add(&conn->subscribers, sub);
int16_t bl00mbox_channel_bud_get_signal_value(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
if(chan == NULL) return false;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
if(bud == NULL) return false;
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(sig == NULL) return false;
// 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);
return sig->value;
}
if(signal_rx->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) update_roots(chan);
uint32_t bl00mbox_channel_bud_get_signal_hints(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel);
if(chan == NULL) return false;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
if(bud == NULL) return false;
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(sig == NULL) return false;
bl00mbox_connect_rx_callback(signal_rx->plugin->parent, signal_tx->plugin->parent, signal_rx->index);
return sig->hints;
bl00mbox_channel_event(chan);
return BL00MBOX_ERROR_OK;
}
bool bl00mbox_channel_bud_set_table_value(uint8_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;
bud->plugin->plugin_table[table_index] = value;
bl00mbox_channel_event(channel);
return true;
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);
int16_t bl00mbox_channel_bud_get_table_value(uint8_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;
return bud->plugin->plugin_table[table_index];
bl00mbox_channel_event(chan);
}
int16_t * bl00mbox_channel_bud_get_table_pointer(uint8_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(uint8_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;
}
#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
#define BL00MBOX_MAX_BUFFER_LEN 64
#define BL00MBOX_DEFAULT_CHANNEL_VOLUME 8192
#define BL00MBOX_AUTO_FOREGROUNDING
#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
......@@ -8,9 +8,5 @@
uint16_t bl00mbox_sources_count();
uint16_t bl00mbox_source_add(void* render_data, void* render_function);
void bl00mbox_source_remove(uint16_t index);
void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len);
// TEMP
void bl00mbox_player_function(int16_t * rx, int16_t * tx, uint16_t len);
bool bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len);
void bl00mbox_init(void);
//SPDX-License-Identifier: CC0-1.0
#pragma once
//TODO: move this to kconfig someday
#define BL00MBOX_MAX_BUFFER_LEN 256
#define BL00MBOX_DEFAULT_CHANNEL_VOLUME 3000
#define BL00MBOX_CHANNELS 32
#define BL00MBOX_BACKGROUND_MUTE_OVERRIDE_ENABLE
//TODO: remove st3m scope dependency
#include "st3m_audio.h"
#include "st3m_scope.h"
#include "bl00mbox_config.h"
#include "bl00mbox_os.h"
#include "bl00mbox_containers.h"
#include <stdio.h>
#include <math.h>
#include <string.h>
#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;
typedef struct _bl00mbox_bud_t{
radspa_t * plugin; // plugin
extern int16_t * bl00mbox_line_in_interlaced;
// 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
uint8_t channel; // index of channel that owns the plugin
struct _bl00mbox_bud_t * chan_next; //for linked list in bl00mbox_channel_t
} bl00mbox_bud_t;
typedef struct _bl00mbox_connection_subscriber_t{
uint8_t type; // 0: standard signal input, 1: output mixer
uint8_t channel;
uint64_t bud_index;
uint32_t signal_index;
struct _bl00mbox_connection_subscriber_t * next;
} bl00mbox_connection_subscriber_t;
uint32_t init_var; // init var that was used for plugin creation
volatile bool is_being_rendered; // true if rendering the plugin is in progress, else false.
bool always_render;
struct _bl00mbox_channel_t * channel; // channel that owns the plugin
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;
uint8_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{
bool is_active; // rendering can be skipped if false
bool is_free;
// pointer is unique identifier, no memcpy of this!
typedef struct _bl00mbox_channel_t{
char * name;
int32_t volume;
struct _bl00mbox_channel_root_t * root_list; // list of all roots associated with channels
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;
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_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;
struct _bl00mbox_channel_t ** weak_parent_self_ref;
} bl00mbox_channel_t;
bl00mbox_channel_t * bl00mbox_get_channel(uint8_t chan);
void bl00mbox_channel_enable(uint8_t chan);
void bl00mbox_channel_enable(uint8_t chan);
void bl00mbox_channel_disable(uint8_t chan);
void bl00mbox_channel_set_volume(uint8_t chan, uint16_t volume);
int16_t bl00mbox_channel_get_volume(uint8_t chan);
void bl00mbox_channel_event(uint8_t chan);
uint8_t bl00mbox_channel_get_free_index();
void bl00mbox_channels_init();
uint8_t bl00mbox_channel_get_foreground_index();
void bl00mbox_channel_set_foreground_index(uint8_t channel_index);
bool bl00mbox_channel_get_free(uint8_t channel_index);
bool bl00mbox_channel_set_free(uint8_t channel_index, bool free);
bool bl00mbox_channel_get_background_mute_override(uint8_t channel_index);
bool bl00mbox_channel_set_background_mute_override(uint8_t channel_index, bool enable);
char * bl00mbox_channel_get_name(uint8_t channel_index);
void bl00mbox_channel_set_name(uint8_t channel_index, char * new_name);
bool bl00mbox_audio_waitfor_pointer_change(void ** ptr, void * new_val);
void bl00mbox_audio_bud_render(bl00mbox_bud_t * bud, uint16_t num_samples);
void bl00mbox_audio_init();
bl00mbox_channel_t * bl00mbox_channel_create();
void bl00mbox_audio_plugin_render(bl00mbox_plugin_t * plugin);
bool bl00mbox_channel_get_foreground(bl00mbox_channel_t * chan);
void bl00mbox_channel_set_foreground(bl00mbox_channel_t * chan, bool enable);
void bl00mbox_channel_set_background_mute_override(bl00mbox_channel_t * chan, bool enable);
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);
#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);
#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"
#include "freertos/semphr.h"
typedef SemaphoreHandle_t bl00mbox_lock_t;
#endif
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
......@@ -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;
......
......@@ -7,50 +7,34 @@
#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(uint8_t channel);
uint64_t bl00mbox_channel_get_bud_by_list_pos(uint8_t channel, uint32_t pos);
uint16_t bl00mbox_channel_conns_num(uint8_t channel);
uint16_t bl00mbox_channel_mixer_num(uint8_t channel);
uint64_t bl00mbox_channel_get_bud_by_mixer_list_pos(uint8_t channel, uint32_t pos);
uint32_t bl00mbox_channel_get_signal_by_mixer_list_pos(uint8_t channel, uint32_t pos);
bool bl00mbox_channel_clear(uint8_t channel);
bool bl00mbox_channel_connect_signal_to_output_mixer(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
bool bl00mbox_channel_connect_signal(uint8_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(uint8_t channel, uint32_t bud_rx_index, uint32_t bud_rx_signal_index);
bool bl00mbox_channel_disconnect_signal_tx(uint8_t channel, uint32_t bud_tx_index, uint32_t bud_tx_signal_index);
bool bl00mbox_channel_disconnect_signal(uint8_t channel, uint32_t bud_tx_index, uint32_t bud_tx_signal_index);
bool bl00mbox_channel_disconnect_signal_from_output_mixer(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
bl00mbox_bud_t * bl00mbox_channel_new_bud(uint8_t channel, uint32_t id, uint32_t init_var);
bool bl00mbox_channel_delete_bud(uint8_t channel, uint32_t bud_index);
bool bl00mbox_channel_bud_exists(uint8_t channel, uint32_t bud_index);
char * bl00mbox_channel_bud_get_name(uint8_t channel, uint32_t bud_index);
char * bl00mbox_channel_bud_get_description(uint8_t channel, uint32_t bud_index);
uint32_t bl00mbox_channel_bud_get_plugin_id(uint8_t channel, uint32_t bud_index);
uint16_t bl00mbox_channel_bud_get_num_signals(uint8_t channel, uint32_t bud_index);
char * bl00mbox_channel_bud_get_signal_name(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
int8_t bl00mbox_channel_bud_get_signal_name_multiplex(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
char * bl00mbox_channel_bud_get_signal_description(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
char * bl00mbox_channel_bud_get_signal_unit(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
bool bl00mbox_channel_bud_set_signal_value(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index, int16_t value);
int16_t bl00mbox_channel_bud_get_signal_value(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
uint32_t bl00mbox_channel_bud_get_signal_hints(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index);
uint16_t bl00mbox_channel_subscriber_num(uint8_t channel, uint64_t bud_index, uint16_t signal_index);
uint64_t bl00mbox_channel_get_bud_by_subscriber_list_pos(uint8_t channel, uint64_t bud_index,
uint16_t signal_index, uint8_t pos);
int32_t bl00mbox_channel_get_signal_by_subscriber_list_pos(uint8_t channel, uint64_t bud_index,
uint16_t signal_index, uint8_t pos);
uint64_t bl00mbox_channel_get_source_bud(uint8_t channel, uint64_t bud_index, uint16_t signal_index);
uint16_t bl00mbox_channel_get_source_signal(uint8_t channel, uint64_t bud_index, uint16_t signal_index);
bool bl00mbox_channel_bud_set_table_value(uint8_t channel, uint32_t bud_index, uint32_t table_index, int16_t value);
int16_t bl00mbox_channel_bud_get_table_value(uint8_t channel, uint32_t bud_index, uint32_t table_index);
uint32_t bl00mbox_channel_bud_get_table_len(uint8_t channel, uint32_t bud_index);
int16_t * bl00mbox_channel_bud_get_table_pointer(uint8_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);
......@@ -11,7 +11,7 @@ def terminal_scope(
fun=None,
fun_ms=None,
):
"""give it a signal and show it on terminal uwu"""
"""give it a signal and show it on terminal"""
if signal_max <= signal_min:
return
ms_counter = 0
......@@ -24,12 +24,16 @@ def terminal_scope(
if fun_ms <= fun_counter:
fun()
fun_counter = fun_counter % fun_ms
val = int((width * (signal.value - signal_min)) / (signal_max - signal_min))
raw_val = signal.value
ret = f"{ms_counter:06d}"
if raw_val == -32768:
ret += " [" + "?" * width + "] INVALID"
else:
val = int((width * (raw_val - signal_min)) / (signal_max - signal_min))
if val > width:
val = width
if val < 0:
val = 0
ret = f"{ms_counter:06d}"
ret += " [" + "X" * val + "." * (width - val) + "]"
percent = int(100 * val / width)
ret += f" {percent:02d}%"
......
# SPDX-License-Identifier: CC0-1.0
import math
import os
import bl00mbox
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_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)
......@@ -33,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)
......@@ -46,7 +74,7 @@ class _PatchSignalList(_PatchItemList):
current_value = getattr(self, key, None)
if isinstance(current_value, bl00mbox.Signal):
current_value.value = value
return
else:
super().__setattr__(key, value)
......@@ -100,61 +128,158 @@ class tinysynth_fm(tinysynth):
class sampler(_Patch):
"""
requires a wave file. default path: /sys/samples/
requires a wave file (str) or max sample length in milliseconds (int). default path: /sys/samples/
"""
def __init__(self, chan, filename):
# DEPRECATED
_READ_HEAD_POS = 0 // 2
_WRITE_HEAD_POS = 2 // 2
_SAMPLE_START = 4 // 2
_SAMPLE_LEN = 6 // 2
_SAMPLE_RATE = 8 // 2
_STATUS = 10
_BUFFER = 11
def __init__(self, chan, init_var):
# init can be filename to load into ram
super().__init__(chan)
if filename.startswith("/"):
f = wave.open("/flash/" + filename, "r")
self.buffer_offset_i16 = 11
self._filename = ""
if type(init_var) == str:
self.plugins.sampler = chan.new(
bl00mbox.plugins.sampler, self._convert_filename(init_var)
)
else:
f = wave.open("/flash/sys/samples/" + filename, "r")
self.plugins.sampler = chan.new(bl00mbox.plugins.sampler, init_var)
self.len_frames = f.getnframes()
self.plugins.sampler = chan.new(bl00mbox.plugins._sampler_ram, self.len_frames)
self.signals.trigger = self.plugins.sampler.signals.playback_trigger
self.signals.output = self.plugins.sampler.signals.playback_output
self.signals.rec_in = self.plugins.sampler.signals.record_input
self.signals.rec_trigger = self.plugins.sampler.signals.record_trigger
assert f.getsampwidth() == 2
assert f.getnchannels() in (1, 2)
assert f.getcomptype() == "NONE"
if f.getnchannels() == 1:
# fast path for mono
table = self.plugins.sampler.table_bytearray
for i in range(0, self.len_frames * 2, 100):
table[i : i + 100] = f.readframes(50)
# terrible backwards compatibility hack, never do that IRL
del self.plugins.sampler.signals._setattr_allowed
self.plugins.sampler.signals.pitch_shift = (
self.plugins.sampler.signals.playback_speed
)
self.plugins.sampler.signals._setattr_allowed = True
def _convert_filename(self, filename):
# TODO: waht if filename doesn't exist?
if filename.startswith("/flash/") or filename.startswith("/sd/"):
return filename
elif filename.startswith("/"):
return "/flash/" + filename
else:
# somewhat fast path for stereo
table = self.plugins.sampler.table_int16_array
for i in range(self.len_frames):
frame = f.readframes(1)
value = int.from_bytes(frame[0:2], "little")
table[i] = value
f.close()
self._filename = filename
self.signals.trigger = self.plugins.sampler.signals.trigger
self.signals.output = self.plugins.sampler.signals.output
return "/flash/sys/samples/" + filename
def load(self, filename):
filename = self._convert_filename(filename)
try:
self.plugins.sampler.load(filename)
except:
# no proper exception catching bc backwards compat
return False
return True
def save(self, filename, overwrite=False):
filename = self._convert_filename(filename)
try:
self.plugins.sampler.save(filename)
except:
# no proper exception catching bc backwards compat
return False
return True
def _offset_index(self, index):
index += self.sample_start
if index >= self.memory_len:
index -= self.memory_len
index += self.buffer_offset_i16
return index
@property
def memory_len(self):
return self.plugins.sampler.buffer_length
@property
def read_head_position(self):
table = self.plugins.sampler.table_uint32_array
return table[self._READ_HEAD_POS]
@read_head_position.setter
def read_head_position(self, val):
if val >= self.memory_len:
val = self.memory_len - 1
table = self.plugins.sampler.table_uint32_array
table[self._READ_HEAD_POS] = int(val)
@property
def sample_start(self):
table = self.plugins.sampler.table_uint32_array
return table[self._SAMPLE_START]
@sample_start.setter
def sample_start(self, val):
if val >= self.memory_len:
val = self.memory_len - 1
table = self.plugins.sampler.table_uint32_array
table[self._SAMPLE_START] = int(val)
@property
def sample_len(self):
table = self.plugins.sampler.table_uint32_array
return table[self._SAMPLE_LEN]
@sample_len.setter
def sample_len(self, val):
if val > self.memory_len:
val = self.memory_len
table = self.plugins.sampler.table_uint32_array
table[self._SAMPLE_LEN] = int(val)
@property
def sample_rate(self):
table = self.plugins.sampler.table_uint32_array
return table[self._SAMPLE_RATE]
@sample_rate.setter
def sample_rate(self, val):
table = self.plugins.sampler.table_uint32_array
if val < 0:
table[self._SAMPLE_RATE] = int(val)
@property
def filename(self):
return self._filename
@property
def rec_event_autoclear(self):
"""
returns true once after a record cycle has been completed. useful for checking whether a save is necessary if nothing else has modified the table.
"""
a = self.plugins.sampler_table[10]
if a & (1 << 4) & 0xFF:
a = a & ~(1 << 4) & 0xFF
self.plugins.sampler_table[10] = a
return True
return False
class sequencer(_Patch):
def __init__(self, chan, num_tracks, num_steps):
super().__init__(chan)
self.num_steps = num_steps
self.num_tracks = num_tracks
init_var = (self.num_steps * 256) + (self.num_tracks) # magic
self.plugins.seq = chan.new(bl00mbox.plugins.sequencer, num_tracks, num_steps)
self.plugins.seq = chan.new(bl00mbox.plugins._sequencer, init_var)
self.signals.bpm = self.plugins.seq.signals.bpm
self.signals.beat_div = self.plugins.seq.signals.beat_div
self.signals.step = self.plugins.seq.signals.step
self.signals.step_end = self.plugins.seq.signals.step_end
self.signals.step_start = self.plugins.seq.signals.step_start
self.signals.step_start = self.plugins.seq.signals.step_start
self.signals.sync_in = self.plugins.seq.signals.sync_in
self.num_tracks = num_tracks
self.num_steps = num_steps
tracktable = [-32767] + ([0] * self.num_steps)
self.plugins.seq.table = tracktable * self.num_tracks
......@@ -169,11 +294,12 @@ class sequencer(_Patch):
)
ret += (
" step: "
+ str(self.signals.step.value)
+ str(self.signals.step.value - self.signals.step_start.value)
+ "/"
+ str(self.signals.step_len.value)
+ str(self.signals.step_end.value - self.signals.step_start.value)
)
ret += "\n [tracks]"
"""
for x, seq in enumerate(self.seqs):
ret += (
"\n "
......@@ -182,6 +308,7 @@ class sequencer(_Patch):
+ "".join(["X " if x > 0 else ". " for x in seq.table[1:]])
+ "]"
)
"""
ret += "\n" + "\n".join(super().__repr__().split("\n")[1:])
return ret
......@@ -215,6 +342,7 @@ class sequencer(_Patch):
class fuzz(_Patch):
# DEPRECATED
def __init__(self, chan):
super().__init__(chan)
self.plugins.dist = chan.new(bl00mbox.plugins._distortion)
......@@ -278,27 +406,28 @@ class karplus_strong(_Patch):
self.plugins.flanger = chan._new_plugin(bl00mbox.plugins.flanger)
self.plugins.flanger.signals.resonance = 32500
self.plugins.flanger.signals.resonance = 0
self.plugins.flanger.signals.decay = 1000
self.plugins.flanger.signals.manual.tone = "A2"
self.plugins.flanger.signals.input = self.plugins.noise.signals.output
self.signals.trigger = self.plugins.noise.signals.trigger
self.signals.trigger_length = self.plugins.noise.signals.length
self.signals.pitch = self.plugins.flanger.signals.manual
self.signals.output = self.plugins.flanger.signals.output
self.signals.level = self.plugins.flanger.signals.level
self.decay = 1000
self.signals.decay = self.plugins.flanger.signals.decay
self.signals.resonance = self.plugins.flanger.signals.resonance
@property
def decay(self):
return self._decay
# deprecated
return self.plugins.flanger.signals.decay.value
@decay.setter
def decay(self, val):
tone = self.plugins.flanger.signals.manual.tone
loss = (50 * (2 ** (-tone / 12))) // (val / 1000)
if loss < 2:
loss = 2
self.plugins.flanger.signals.resonance = 32767 - loss
self._decay = val
# deprecated
self.plugins.flanger.signals.resonance = -2
self.plugins.flanger.signals.decay = int(val)
# SPDX-License-Identifier: CC0-1.0
import sys_bl00mbox
import bl00mbox
import uctypes
import os
import cpython.wave as wave
class _PluginDescriptor:
def __init__(self, index):
self.index = index
self.plugin_id = sys_bl00mbox.plugin_index_get_id(self.index)
self.name = sys_bl00mbox.plugin_index_get_name(self.index)
self.description = sys_bl00mbox.plugin_index_get_description(self.index)
def __repr__(self):
return (
"[plugin "
+ str(self.plugin_id)
+ "] "
+ self.name
+ ": "
+ self.description
)
class _PluginDescriptors:
pass
plugins = _PluginDescriptors()
def _fill():
plugins_list = {}
for i in range(sys_bl00mbox.plugin_registry_num_plugins()):
plugins_list[sys_bl00mbox.plugin_index_get_name(i).replace(" ", "_")] = i
for name, value in plugins_list.items():
setattr(plugins, name, _PluginDescriptor(value))
# legacy
if name == "sequencer" or name == "distortion":
setattr(plugins, "_" + name, _PluginDescriptor(value))
elif name == "sampler":
setattr(plugins, "_sampler_ram", _PluginDescriptor(value))
elif name == "delay_static":
setattr(plugins, "delay", _PluginDescriptor(value))
_fill()
class _Plugin:
_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:
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)
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):
ret = self._repr_no_signals()
for sig in self.signals._list:
ret += "\n " + "\n ".join(sig._no_desc().split("\n"))
return ret
def _check_existence(self):
# will fail if plugin was deleted
_ = self._core.plugin_id
@property
def signals(self):
return self._signals
@property
def table(self):
_table = self.table_int16_array
ret = [None] * self.table_len
for x in range(self.table_len):
ret[x] = _table[x]
return ret
@table.setter
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 = self._core.table_pointer
max_len = self._core.table_len
return (pointer, max_len)
@property
def table_bytearray(self):
pointer, max_len = self.table_pointer
bytes_len = max_len * 2
return uctypes.bytearray_at(pointer, bytes_len)
@property
def table_int8_array(self):
pointer, max_len = self.table_pointer
descriptor = {"table": (0 | uctypes.ARRAY, max_len * 2 | uctypes.INT8)}
struct = uctypes.struct(pointer, descriptor)
return struct.table
@property
def table_int16_array(self):
pointer, max_len = self.table_pointer
descriptor = {"table": (0 | uctypes.ARRAY, max_len | uctypes.INT16)}
struct = uctypes.struct(pointer, descriptor)
return struct.table
@property
def table_uint32_array(self):
pointer, max_len = self.table_pointer
descriptor = {"table": (0 | uctypes.ARRAY, (max_len // 2) | uctypes.UINT32)}
struct = uctypes.struct(pointer, descriptor)
return struct.table
_plugin_subclasses = {}
def _make_plugin(channel, plugin_id, *args, **kwargs):
if plugin_id in _plugin_subclasses:
return _plugin_subclasses[plugin_id](channel, None, plugin_id, *args, **kwargs)
else:
init_var = 0
_init_var = kwargs.get("init_var", None)
if _init_var is None:
if len(args) == 1:
init_var = int(args[0])
else:
init_var = _init_var
return _Plugin(channel, None, plugin_id, init_var)
def _get_plugin(core):
plugin_id = core.plugin_id
if plugin_id in _plugin_subclasses:
return _plugin_subclasses[plugin_id](None, core, plugin_id, core.init_var)
else:
return _Plugin(None, core, plugin_id, core.init_var)
def _plugin_set_subclass(plugin_id):
def decorator(cls):
_plugin_subclasses[plugin_id] = cls
return cls
return decorator
@_plugin_set_subclass(696969)
class _Sampler(_Plugin):
# divide by two if uint32_t
_READ_HEAD_POS = 0 // 2
_WRITE_HEAD_POS = 2 // 2
_SAMPLE_START = 4 // 2
_SAMPLE_LEN = 6 // 2
_SAMPLE_RATE = 8 // 2
_STATUS = 10
_BUFFER = 11
def __init__(self, channel, core, plugin_id, init_var=1000):
self._filename = ""
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, None, plugin_id, init_var=self._memory_len)
self.load(init_var)
else:
self._memory_len = int(48 * init_var)
super().__init__(channel, None, plugin_id, init_var=self._memory_len)
def __repr__(self):
ret = super().__repr__()
ret += "\n playback "
if self.playback_loop:
ret += "(looped): "
else:
ret += "(single): "
if self.playback_progress is not None:
dots = int(self.playback_progress * 20)
ret += (
"["
+ "#" * dots
+ "-" * (20 - dots)
+ "] "
+ str(int(self.playback_progress * 100))
+ "%"
)
else:
ret += "idle"
ret += "\n record "
if self.record_overflow:
ret += "(overflow): "
else:
ret += "(autostop): "
if self.record_progress is not None:
dots = int(self.record_progress * 20)
ret += (
"["
+ "#" * dots
+ "-" * (20 - dots)
+ "] "
+ str(int(self.record_progress * 100))
+ "%"
)
else:
ret += "idle"
ret += "\n buffer: " + " " * 11
rel_fill = self.sample_length / self.buffer_length
dots = int(rel_fill * 20)
if dots > 19:
dots = 19
ret += (
"[" + "#" * dots + "-" * (19 - dots) + "] " + str(int(rel_fill * 100)) + "%"
)
if self.filename is not "":
ret += "\n file: " + self.filename
ret += "\n sample rate: " + " " * 6 + str(self.sample_rate)
return ret
def load(self, filename):
with wave.open(filename, "r") as f:
try:
assert f.getsampwidth() == 2
assert f.getnchannels() in (1, 2)
assert f.getcomptype() == "NONE"
except AssertionError:
raise Bl00mboxError("incompatible file format")
frames = f.getnframes()
if frames > self._memory_len:
frames = self._memory_len
self._sample_len_frames = frames
self.sample_rate = f.getframerate()
BUFFER_SIZE = int(48000 * 2.5)
if f.getnchannels() == 1:
# fast path for mono
table = self.table_bytearray
for i in range(
2 * self._BUFFER,
(self._sample_len_frames + self._BUFFER) * 2,
BUFFER_SIZE * 2,
):
table[i : i + BUFFER_SIZE * 2] = f.readframes(BUFFER_SIZE)
else:
# somewhat fast path for stereo
table = self.table_int16_array
for i in range(
self._BUFFER,
self._sample_len_frames + self._BUFFER,
BUFFER_SIZE,
):
frame = f.readframes(BUFFER_SIZE)
for j in range(0, len(frame) // 4):
value = int.from_bytes(frame[4 * j : 4 * j + 2], "little")
table[i + j] = value
self._filename = filename
self._set_status_bit(4, 0)
def save(self, filename):
with wave.open(filename, "w") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(self.sample_rate)
start = self._offset_index(0)
end = self._offset_index(self._sample_len_frames)
table = self.table_bytearray
if end > start:
f.writeframes(table[2 * start : 2 * end])
else:
f.writeframes(table[2 * start :])
f.writeframes(table[2 * self._BUFFER : 2 * end])
if self._filename:
self._filename = filename
self._set_status_bit(4, 0)
def _offset_index(self, index):
index += self._sample_start
if index >= self._memory_len:
index -= self._memory_len
index += self._BUFFER
return index
def _set_status_bit(self, bit, val):
table = self.table_int16_array
if val:
table[self._STATUS] = (table[self._STATUS] | (1 << bit)) & 0xFF
else:
table[self._STATUS] = (table[self._STATUS] & ~(1 << bit)) & 0xFF
@property
def filename(self):
if self._get_status_bit(4):
self._filename = ""
self._set_status_bit(4, 0)
return self._filename
def _get_status_bit(self, bit):
table = self.table_int16_array
return bool(table[self._STATUS] & (1 << bit))
@property
def playback_loop(self):
return self._get_status_bit(1)
@playback_loop.setter
def playback_loop(self, val):
self._set_status_bit(1, val)
@property
def record_overflow(self):
return self._get_status_bit(3)
@record_overflow.setter
def record_overflow(self, val):
self._set_status_bit(3, val)
@property
def record_progress(self):
if self._get_status_bit(2):
table = self.table_uint32_array
return table[self._WRITE_HEAD_POS] / self._memory_len
return None
@property
def playback_progress(self):
if self._get_status_bit(0):
table = self.table_uint32_array
return table[0] / table[3]
return None
@property
def _sample_start(self):
table = self.table_uint32_array
return table[2]
@_sample_start.setter
def _sample_start(self, val):
if val >= self._memory_len:
val = self._memory_len - 1
table = self.table_uint32_array
table[2] = int(val)
@property
def _sample_len_frames(self):
table = self.table_uint32_array
return table[self._SAMPLE_LEN]
@_sample_len_frames.setter
def _sample_len_frames(self, val):
val = int(val)
if val > 0:
table = self.table_uint32_array
table[self._SAMPLE_LEN] = val
@property
def sample_length(self):
return self._sample_len_frames
@property
def buffer_length(self):
return self._memory_len
@property
def sample_rate(self):
table = self.table_uint32_array
return table[self._SAMPLE_RATE]
@sample_rate.setter
def sample_rate(self, val):
table = self.table_uint32_array
if int(val) > 0:
table[self._SAMPLE_RATE] = int(val)
@_plugin_set_subclass(9000)
class _Distortion(_Plugin):
def curve_set_power(self, power=2, volume=32767, gate=0):
volume = min(max(volume, -32767), 32767)
table = [0] * 129
for num in range(len(table)):
if num < 64:
ret = num / 64 # scale to [0..1[ range
ret = ret**power
if ret > 1:
ret = 1
table[num] = int(volume * (ret - 1))
else:
ret = (128 - num) / 64 # scale to [0..1] range
ret = ret**power
table[num] = int(volume * (1 - ret))
gate = min(abs(int(gate)), 32767) >> 9
for i in range(64 - gate, 64 + gate):
table[i] = 0
self.table = table
@property
def _secret_sauce(self):
table = self.table_int16_array
return table[129]
@_secret_sauce.setter
def _secret_sauce(self, val):
val = min(max(int(val), 0), 7)
table = self.table_int16_array
table[129] = val
@property
def curve(self):
return self.table[:129]
@curve.setter
def curve(self, points):
# interpolation only implemented for len(points) <= 129,
# for longer lists data may be dropped.
points_size = len(points)
if not points_size:
return
table = [0] * 129
for x, num in enumerate(table):
position = x * (points_size - 1) / 129
lower = int(position)
lerp = position - lower
if position < points_size - 1:
table[x] = int((1 - lerp) * points[lower] + lerp * points[lower + 1])
else:
table[x] = int(points[points_size - 1])
self.table = table
def __repr__(self):
ret = super().__repr__()
wave = self.table[:129]
ret += "\n curve:\n"
ret += " " + "_" * 67 + "\n"
ret += " |" + " " * 67 + "|\n"
symbols = "UW"
symbol_counter = 0
for i in range(15, -1, -1):
line = " | "
for k in range(63):
vals = wave[2 * k : 2 * k + 4]
upper = ((max(vals) >> 8) + 128) >> 4
lower = ((min(vals) >> 8) + 128) >> 4
if (i >= lower) and (i <= upper):
line += symbols[symbol_counter]
symbol_counter = (symbol_counter + 1) % len(symbols)
else:
line += " "
line += " |\n"
ret += line
ret += " |" + "_" * 67 + "|"
return ret
@_plugin_set_subclass(172)
class _PolySqueeze(_Plugin):
def __init__(self, channel, core, plugin_id, num_outputs=3, num_inputs=10):
if core is None:
outs = max(min(num_outputs, 16), 1)
ins = max(min(num_inputs, 32), num_outputs)
init_var = outs + (ins * 256)
super().__init__(channel, core, plugin_id, init_var=init_var)
else:
super().__init__(channel, core, None)
@_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:
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)
class _Osc(_Plugin):
@property
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
ret += "\n wave debug (lazy updates, may get stuck on old data):\n"
ret += " " + "_" * 68 + "\n"
ret += " |" + " " * 68 + "|\n"
symbols = "UW"
symbol_counter = 0
for i in range(15, -1, -1):
line = " | "
for j in range(64):
if j == 0:
upper = wave[63]
else:
upper = wave[j - 1]
upper = (upper + 128) >> 4
lower = (wave[j] + 128) >> 4
if lower > upper:
upper, lower = lower, upper
if (i >= lower) and (i <= upper):
line += symbols[symbol_counter]
symbol_counter = (symbol_counter + 1) % len(symbols)
else:
line += " "
line += " |\n"
ret += line
ret += " |" + "_" * 68 + "|"
return ret
@_plugin_set_subclass(56709)
class _Sequencer(_Plugin):
def __init__(self, channel, core, plugin_id, num_tracks=4, num_steps=16):
if core is None:
self.num_steps = num_steps % 256
self.num_tracks = num_tracks % 256
init_var = (self.num_steps * 256) + (self.num_tracks)
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, core, None)
self.num_tracks = self.init_var % 256
self.num_steps = (self.init_var // 256) % 256
def __repr__(self):
ret = super().__repr__()
ret += (
"\n bpm: "
+ str(self.signals.bpm.value)
+ " @ 1/"
+ str(self.signals.beat_div.value)
)
ret += (
" step: "
+ str(self.signals.step.value - self.signals.step_start.value)
+ "/"
+ str(self.signals.step_end.value - self.signals.step_start.value)
)
ret += "\n [tracks]"
for track in range(self.num_tracks):
ret += (
"\n "
+ str(track)
+ " [ "
+ "".join(
[
"X " if self.trigger_state(track, x) > 0 else ". "
for x in range(self.num_steps)
]
)
+ "]"
)
return ret
def _get_table_index(self, track, step):
return step + 1 + track * (self.num_steps + 1)
def trigger_start(self, track, step, val=32767):
if val > 32767:
val = 32767
elif val < 1:
val = 1
table = self.table_int16_array
table[self._get_table_index(track, step)] = val
def trigger_stop(self, track, step):
table = self.table_int16_array
table[self._get_table_index(track, step)] = -1
def trigger_clear(self, track, step):
table = self.table_int16_array
table[self._get_table_index(track, step)] = 0
def trigger_state(self, track, step):
table = self.table_int16_array
return table[self._get_table_index(track, step)]
def trigger_toggle(self, track, step):
if self.trigger_state(track, step) == 0:
self.trigger_start(track, step)
else:
self.trigger_clear(track, step)
def save_track_pattern(self, track_index):
start = track_index * (self.num_steps + 1)
stop = start + self.num_steps + 1
track = {}
table = self.table
if self.table[start] == -32767:
track["type"] = "trigger"
else:
track["type"] = "value"
track["steps"] = list(table[start + 1 : stop])
return track
def load_track_pattern(self, track, track_index):
start = track_index * (self.num_steps + 1)
table = self.table_int16_array
stop = start + 1 + min(self.num_steps, len(track["steps"]))
for i in range(start + 1, stop):
x = track["steps"][i - start - 1]
assert (x < 32768) and (x > -32768)
table[i] = x
if track["type"] == "trigger":
table[start] = -32767
else:
table[start] = 32767
def save_pattern(self):
beat = {}
beat["tracks"] = [self.save_track_pattern(i) for i in range(self.num_tracks)]
return beat
def load_pattern(self, beat):
num_tracks = min(len(beat["tracks"]), self.num_tracks)
[self.load_track_pattern(beat["tracks"][i], i) for i in range(num_tracks)]
@_plugin_set_subclass(38)
class _TriggerMerge(_Plugin):
@property
def block_stop(self):
table = self.table_int16_array
return bool(table[0])
@block_stop.setter
def block_stop(self, val):
table = self.table_int16_array
table[0] = 1 if val else 0
# SPDX-License-Identifier: CC0-1.0
import sys_bl00mbox
# note: consider the 'sys_bl00mbox' api super unstable for now pls :3
import math
import uctypes
import bl00mbox
from bl00mbox import _helpers as helpers
from bl00mbox._patches import _Patch as Patch
class Bl00mboxError(Exception):
pass
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, core):
self._core = core
def __repr__(self):
ret = f"[channel mixer] ({len(self.connections)} connections)"
for con in self.connections:
ret += f"\n {con.name} in {con._plugin._repr_no_signals()}"
return ret
@property
def connections(self):
cons = []
signal_cores = self._core.get_connected_mx()
for core in signal_cores:
plugin = bl00mbox._plugins._get_plugin(core.plugin_core)
cons.append(_make_signal(plugin, core=core))
return cons
class ValueSwitch:
def __init__(self, plugin, core):
self._plugin = plugin
self._core = core
def __setattr__(self, key, value):
if getattr(self, "_locked", False):
if not value:
return
val = getattr(self, key, None)
if val is None:
return
self._core.value = val
else:
super().__setattr__(key, value)
def get_value_name(self, val):
d = dict((k, v) for k, v in self.__dict__.items() if not k.startswith("_"))
for k, v in d.items():
if v == val:
return k
class Signal:
def __init__(self, plugin, core):
self._core = core
self._plugin = plugin
self._mpx = core.mpx
self._unit = core.unit
self._description = core.description
constants = {}
another_round = True
while another_round:
another_round = False
for k, char in enumerate(self._unit):
if char == "{":
for j, stopchar in enumerate(self._unit[k:]):
if stopchar == "}":
const = self._unit[k + 1 : k + j].split(":")
constants[const[0]] = int(const[1])
self._unit = self._unit[:k] + self._unit[k + j + 1 :]
break
another_round = True
break
if constants:
self._unit = self._unit.strip()
self.switch = ValueSwitch(plugin, self._core)
for key in constants:
setattr(self.switch, key, constants[key])
self.switch._locked = True
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._get_plugin(core.plugin_core)
cons.append(_make_signal(plugin, core=core))
if self._core.connected_mx:
cons.append(ChannelMixer(None))
return cons
def __repr__(self):
ret = self._no_desc()
if len(self._description):
ret += "\n " + "\n ".join(self._description.split("\n"))
return ret
def _no_desc(self):
self._plugin._check_existence()
ret = self.name
if self._mpx != -1:
ret += "[" + str(self._mpx) + "]"
if len(self.unit):
ret += " [" + self.unit + "]"
ret += " [" + self.hints + "]: "
direction = " <?> "
val = self.value
if isinstance(self, SignalInput):
direction = " <= "
if len(self.connections):
val = self.connections[0].value
elif isinstance(self, SignalOutput):
direction = " => "
ret += str(val)
if getattr(self, "switch", None) is not None:
value_name = self.switch.get_value_name(val)
if value_name is not None:
ret += " (" + value_name.lower() + ")"
if self._core.hints & sys_bl00mbox.SIGNAL_HINT_SCT:
ret += " / " + str(self.tone) + " semitones / " + str(self.freq) + "Hz"
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 += [f"{direction}{con.name} in {con._plugin._repr_no_signals()}"]
if isinstance(con, ChannelMixer):
conret += [f"{direction}[channel mixer]"]
if len(conret) > 1:
nl = "\n "
ret += nl + nl.join(conret)
elif conret:
ret += conret[0]
return ret
@property
def name(self):
return self._core.name
@property
def description(self):
return self._core.description
@property
def unit(self):
return self._core.unit
@property
def hints(self):
return self._hints
@property
def value(self):
return self._core.value
@property
def tone(self):
return (self.value - (32767 - 2400 * 6)) / 200
@tone.setter
def tone(self, val):
if isinstance(self, SignalInput):
if (type(val) == int) or (type(val) == float):
self.value = (32767 - 2400 * 6) + 200 * val
if type(val) == str:
self.value = helpers.note_name_to_sct(val)
else:
raise AttributeError("can't set output signal")
@property
def freq(self):
tone = (self.value - (32767 - 2400 * 6)) / 200
return 440 * (2 ** (tone / 12))
@freq.setter
def freq(self, val):
if isinstance(self, SignalInput):
tone = 12 * math.log(val / 440, 2)
self.value = (32767 - 2400 * 6) + 200 * tone
else:
raise AttributeError("can't set output signal")
@property
def dB(self):
if self.value == 0:
return -9999
return 20 * math.log((abs(self.value) / 4096), 10)
@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):
return self.value / 4096
@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 is None:
self._core.disconnect()
elif isinstance(val, SignalInput):
val << self
elif isinstance(val, ChannelMixer):
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
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):
if val is None:
self._core.disconnect()
elif isinstance(val, SignalOutput):
self._core.connect(val._core)
elif (type(val) == int) or (type(val) == float):
self._core.value = int(val)
# fails silently bc of backwards compatibility :/
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
def __rshift__(self, other):
raise TypeError("input signals can't send data to other signals")
class SignalMpxList:
def __init__(self):
self._list = []
self._setattr_allowed = False
def __len__(self):
return len(self._list)
def __getitem__(self, index):
return self._list[index]
def __setitem__(self, index, value):
try:
current_value = self._list[index]
except:
raise AttributeError("index does not exist")
if isinstance(current_value, Signal):
current_value.value = value
else:
raise AttributeError("signal does not exist")
def __iter__(self):
self._iter_index = 0
return self
def __next__(self):
self._iter_index += 1
if self._iter_index >= len(self._list):
raise StopIteration
else:
return self._list[self._iter_index]
def add_new_signal(self, signal, index):
self._setattr_allowed = True
index = int(index)
if len(self._list) <= index:
self._list += [None] * (1 + index - len(self._list))
self._list[index] = signal
self._setattr_allowed = False
def __setattr__(self, key, value):
"""
current_value = getattr(self, key, None)
if isinstance(current_value, Signal):
current_value.value = value
return
"""
if key == "_setattr_allowed" or getattr(self, "_setattr_allowed", True):
super().__setattr__(key, value)
else:
raise AttributeError("signal does not exist")
class SignalList:
def __init__(self, plugin):
self._list = []
for signal_num in range(plugin._core.num_signals):
signal = _make_signal(plugin, signal_num=signal_num)
self._list.append(signal)
name = signal.name.split(" ")[0]
if signal._mpx == -1:
setattr(self, name, signal)
else:
# <LEGACY SUPPORT>
setattr(self, name + str(signal._mpx), signal)
# </LEGACY SUPPORT>
current_list = getattr(self, name, None)
if not isinstance(current_list, SignalMpxList):
setattr(self, name, SignalMpxList())
getattr(self, name, None).add_new_signal(signal, signal._mpx)
self._setattr_allowed = False
def __setattr__(self, key, value):
current_value = getattr(self, key, None)
if isinstance(current_value, Signal):
current_value.value = value
return
elif getattr(self, "_setattr_allowed", True):
super().__setattr__(key, value)
else:
raise AttributeError("signal does not exist")
_channel_init_callback = None
_rms_base = 3 - 10 * math.log(32767 * 32767, 10)
def set_channel_init_callback(callback):
global _channel_init_callback
_channel_init_callback = callback
class Channel:
_core_keys = (
"name",
"num_plugins",
"clear",
"volume",
"foreground",
"background_mute_override",
"callback",
"compute_rms",
"delete",
)
# 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:
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"):
# never ever hand out the deep core to somebody else to make sure channel gets gc'd right
self._deep_core = sys_bl00mbox.ChannelCore(name)
self._core = self._deep_core.get_weak()
# also don't hand out self, use this one instead
self._weak_channel = WeakChannel(self._core)
if _channel_init_callback is not None:
_channel_init_callback(self)
self._channel_plugin = bl00mbox._plugins._get_plugin(self._core.channel_plugin)
@property
def signals(self):
return self._channel_plugin._signals
def __repr__(self):
ret = f'[channel "{self.name}"]'
if self.foreground:
ret += " (foreground)"
if self.background_mute_override:
ret += " (background mute override)"
ret += "\n gain: " + str(self.gain_dB) + "dB"
b = self.num_plugins
ret += "\n plugins: " + str(b)
ret += "\n " + "\n ".join(repr(self.mixer).split("\n"))
return ret
@property
def free(self):
# legacy oof
return self._core is None
@free.setter
def free(self, val):
# bigger legacy oof
if val:
self.clear()
self._core = None
def _new_plugin(self, thing, *args, **kwargs):
if isinstance(thing, bl00mbox._plugins._PluginDescriptor):
return bl00mbox._plugins._make_plugin(
self._weak_channel, thing.plugin_id, *args, **kwargs
)
else:
raise TypeError("not a plugin")
def new(self, thing, *args, **kwargs):
self.free = False
if type(thing) == type:
if issubclass(thing, bl00mbox.patches._Patch):
return thing(self._weak_channel, *args, **kwargs)
elif isinstance(thing, bl00mbox._plugins._PluginDescriptor) or (
type(thing) == int
):
return self._new_plugin(thing, *args, **kwargs)
else:
raise TypeError("invalid plugin/patch")
@property
def plugins(self):
for core in self._core.get_plugins():
yield bl00mbox._plugins._get_plugin(core)
@property
def gain_dB(self):
ret = self._core.volume
if ret == 0:
return -math.inf
else:
return 20 * math.log(ret / 32768, 10)
@gain_dB.setter
def gain_dB(self, value):
if value == -math.inf:
value = 0
else:
value = int(32768 * (10 ** (value / 20)))
value = min(32767, max(0, value))
self._core.volume = value
@property
def rms_dB(self):
if self.compute_rms:
ret = self._core.mean_square
if ret == 0:
return -math.inf
else:
# volume is applied together with sys_volume
# after rms is calculated. we do want the output
# before sys_volume but after regular volume,
# so we're compensating here
mult = abs(self._core.volume) / 32768
if mult == 0:
return -math.inf
# squaring because we're getting root later
ret *= mult * mult
return 10 * math.log(ret, 10) + _rms_base
else:
return None
@property
def mixer(self):
return ChannelMixer(self._core)
@mixer.setter
def mixer(self, val):
if isinstance(val, SignalOutput):
val.value = ChannelMixer(self._core)
elif val is not None:
# val is None: backwards compatibility, not allowed to throw exception ;w;
raise Bl00mboxError("can't connect this")
class WeakChannel(Channel):
def __init__(self, core):
self._core = core.get_weak()
self._channel_plugin = bl00mbox._plugins._get_plugin(self._core.channel_plugin)
self._weak_channel = self
class SysChannel(Channel):
def __init__(self, core):
self._deep_core = core
self._core = core.get_weak()
self._channel_plugin = bl00mbox._plugins._get_plugin(self._core.channel_plugin)
self._weak_channel = WeakChannel(self._core)
@property
def sys_gain_dB(self):
ret = self._core.sys_gain
if ret == 0:
return -math.inf
else:
try:
return 20 * math.log(ret / 4096, 10)
except:
return -math.inf
@sys_gain_dB.setter
def sys_gain_dB(self, value):
if value == -math.inf:
value = 0
else:
value = int(4096 * (10 ** (value / 20)))
value = min(32767, max(0, value))
self._core.sys_gain = value
@property
def sys_rms_dB(self):
if self.compute_rms:
ret = self._core.mean_square
if ret == 0:
return -math.inf
else:
mult = self._core.volume / 32768
mult *= self._core.sys_gain / 4096
if mult == 0:
return -math.inf
ret *= mult * mult
return 10 * math.log(ret, 10) + _rms_base
else:
return None
class Sys:
@staticmethod
def clear():
cores = sys_bl00mbox.collect_channels(False)
for core in cores:
core.delete()
@staticmethod
def foreground_clear():
cores = sys_bl00mbox.collect_channels(True)
for core in cores:
core.foreground = False
@staticmethod
def collect_channels(active, weak_ref=False):
cores = sys_bl00mbox.collect_channels(active)
for core in cores:
if weak_ref:
core = core.get_weak()
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
// SPDX-License-Identifier: CC0-1.0
#include <stdio.h>
#include "py/obj.h"
#include "py/runtime.h"
#include "bl00mbox.h"
#include "bl00mbox_os.h"
#include "bl00mbox_plugin_registry.h"
#include "bl00mbox_user.h"
#include "radspa.h"
#if !MICROPY_ENABLE_FINALISER
#error "bl00mbox needs finaliser"
#endif
#include "py/objexcept.h"
MP_DEFINE_EXCEPTION(ReferenceError, Exception)
typedef struct _channel_core_obj_t {
mp_obj_base_t base;
// will be NULLed on original if channel has been manually deleted
// both for weak and regular
bl00mbox_channel_t *chan;
// pointer to object that weakly references original channel object
// there is no more than one weak channel reference obj per channel
mp_obj_t weak;
// things we don't want garbage collected:
// - channel plugin
mp_obj_t channel_plugin;
// - list of all sources connect to the mixer
mp_obj_t sources_mx;
#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;
// list of all source plugins to avoid gc
mp_obj_t sources;
} 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:;
}
}
void bl00mbox_disconnect_rx_callback(void *rx, uint16_t signal_index) {
plugin_core_obj_t *self = rx;
mp_obj_list_t *list = MP_OBJ_TO_PTR(self->sources);
if (signal_index < list->len) {
list->items[signal_index] = mp_const_none;
} else {
bl00mbox_log_error("plugin signal list too short");
}
}
void bl00mbox_connect_rx_callback(void *rx, void *tx, uint16_t signal_index) {
plugin_core_obj_t *self = rx;
mp_obj_list_t *list = MP_OBJ_TO_PTR(self->sources);
if (signal_index < list->len) {
list->items[signal_index] = MP_OBJ_FROM_PTR(tx);
} else {
bl00mbox_log_error("plugin signal list too short");
}
}
void bl00mbox_disconnect_mx_callback(void *chan, void *tx) {
channel_core_obj_t *self = chan;
// don't have to check for duplicates, bl00mbox_user does that for us
mp_obj_list_remove(self->sources_mx, MP_OBJ_FROM_PTR(tx));
}
void bl00mbox_connect_mx_callback(void *chan, void *tx) {
channel_core_obj_t *self = chan;
// don't have to check for duplicates, bl00mbox_user does that for us
mp_obj_list_append(self->sources_mx, MP_OBJ_FROM_PTR(tx));
}
static char *strdup_raise(char *str) {
char *ret = strdup(str);
if (!ret) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
return ret;
}
static mp_obj_t get_connections_from_array(bl00mbox_array_t *array) {
if (!array) bl00mbox_error_unwrap(BL00MBOX_ERROR_OOM);
if (array->len) {
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 REGISTRY
// ========================
// TODO: clean this up
STATIC mp_obj_t mp_plugin_index_get_id(mp_obj_t index) {
radspa_descriptor_t *desc =
bl00mbox_plugin_registry_get_descriptor_from_index(
mp_obj_get_int(index));
if (desc == NULL) return mp_const_none;
return mp_obj_new_int(desc->id);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_plugin_index_get_id_obj,
mp_plugin_index_get_id);
STATIC mp_obj_t mp_plugin_index_get_name(mp_obj_t index) {
/// prints name
radspa_descriptor_t *desc =
bl00mbox_plugin_registry_get_descriptor_from_index(
mp_obj_get_int(index));
if (desc == NULL) return mp_const_none;
return mp_obj_new_str(desc->name, strlen(desc->name));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_plugin_index_get_name_obj,
mp_plugin_index_get_name);
STATIC mp_obj_t mp_plugin_index_get_description(mp_obj_t index) {
/// prints name
radspa_descriptor_t *desc =
bl00mbox_plugin_registry_get_descriptor_from_index(
mp_obj_get_int(index));
if (desc == NULL) return mp_const_none;
return mp_obj_new_str(desc->description, strlen(desc->description));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_plugin_index_get_description_obj,
mp_plugin_index_get_description);
STATIC mp_obj_t mp_plugin_registry_num_plugins(void) {
return mp_obj_new_int(bl00mbox_plugin_registry_get_plugin_num());
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_plugin_registry_num_plugins_obj,
mp_plugin_registry_num_plugins);
// ========================
// CHANNELS
// ========================
static mp_obj_t create_channel_plugin(bl00mbox_channel_t *chan) {
plugin_core_obj_t *self = m_new_obj_with_finaliser(plugin_core_obj_t);
self->base.type = &plugin_core_type;
self->plugin = chan->channel_plugin;
self->plugin->parent = self;
self->plugin->parent_self_ref = &self->plugin;
size_t num_signals = self->plugin->rugin->len_signals;
mp_obj_t nones[num_signals];
for (size_t i = 0; i < num_signals; i++) nones[i] = mp_const_none;
self->sources = mp_obj_new_list(num_signals, nones);
return MP_OBJ_FROM_PTR(self);
}
static mp_obj_t create_weak_channel_core(channel_core_obj_t *self) {
channel_core_obj_t *weak = m_new_obj_with_finaliser(channel_core_obj_t);
weak->base.type = &channel_core_type;
weak->weak = MP_OBJ_FROM_PTR(weak);
weak->chan = self->chan;
weak->chan->weak_parent_self_ref = &weak->chan;
// only the real channel should prevent these from being gc'd
weak->sources_mx = MP_OBJ_NULL;
weak->channel_plugin = MP_OBJ_NULL;
#ifdef BL00MBOX_CALLBACKS_FUNSAFE
weak->callback = MP_OBJ_NULL;
#endif
return MP_OBJ_FROM_PTR(weak);
}
STATIC mp_obj_t channel_core_make_new(const mp_obj_type_t *type, size_t n_args,
size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 1, 1, false);
channel_core_obj_t *self = m_new_obj_with_finaliser(channel_core_obj_t);
self->chan = NULL; // must initialize if obj gets gc'd due to exception
self->base.type = &channel_core_type;
self->sources_mx = mp_obj_new_list(0, NULL);
const char *name = strdup_raise(mp_obj_str_get_str(args[0]));
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->weak = create_weak_channel_core(self);
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_obj_t mp_channel_core_get_weak(mp_obj_t self_in) {
channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
channel_core_verify(self);
return self->weak;
}
MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_get_weak_obj,
mp_channel_core_get_weak);
mp_obj_t mp_channel_core_del(mp_obj_t self_in) {
channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
if (self->chan && (self->weak != self_in)) {
bl00mbox_log_info("destroyed channel %s", self->chan->name);
bl00mbox_channel_destroy(self->chan);
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_del_obj, mp_channel_core_del);
mp_obj_t mp_channel_core_delete(mp_obj_t self_in) {
channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
channel_core_verify(self);
bl00mbox_log_info("destroyed channel %s", self->chan->name);
bl00mbox_channel_destroy(self->chan);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_core_delete_obj, mp_channel_core_delete);
mp_obj_t mp_channel_core_clear(mp_obj_t self_in) {
channel_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
channel_core_verify(self);
self->callback = mp_const_none;
bl00mbox_channel_clear(self->chan);
return mp_const_none;
}
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) {
((channel_core_obj_t *)chan->parent)->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) {
char *ret = strdup_raise(chan->name);
dest[0] = mp_obj_new_str(ret, strlen(ret));
free(ret);
} else if (attr == MP_QSTR_channel_plugin) {
dest[0] = ((channel_core_obj_t *)chan->parent)->channel_plugin;
} else if (attr == MP_QSTR_callback) {
#ifdef BL00MBOX_CALLBACKS_FUNSAFE
dest[0] = ((channel_core_obj_t *)chan->parent)->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_weak),
MP_ROM_PTR(&mp_channel_core_get_weak_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_connected_mx),
MP_ROM_PTR(&mp_channel_core_get_connected_mx_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_plugins),
MP_ROM_PTR(&mp_channel_core_get_plugins_obj) },
};
STATIC MP_DEFINE_CONST_DICT(channel_core_locals_dict,
channel_core_locals_dict_table);
STATIC MP_DEFINE_CONST_OBJ_TYPE(channel_core_type, MP_QSTR_ChannelCore,
MP_TYPE_FLAG_NONE, make_new,
channel_core_make_new, locals_dict,
&channel_core_locals_dict, attr,
channel_core_attr);
static mp_obj_t mp_collect_channels(mp_obj_t active_in) {
// critical phase: make sure to not trigger any garbage collection until
// these are on the stack!!
bl00mbox_array_t *chans =
bl00mbox_collect_channels(mp_obj_is_true(active_in));
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);
// ========================
// PLUGINS
// ========================
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"));
}
channel_core_verify(chan_core);
bl00mbox_channel_t *chan = chan_core->chan;
int plugin_kind = mp_obj_get_int(args[1]);
int init_var = mp_obj_get_int(args[2]);
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;
// create empty source list
size_t num_signals = plugin->rugin->len_signals;
mp_obj_t nones[num_signals];
for (size_t i = 0; i < num_signals; i++) nones[i] = mp_const_none;
self->sources = mp_obj_new_list(num_signals, nones);
bl00mbox_log_info("created plugin %s",
self->plugin->rugin->descriptor->name);
return MP_OBJ_FROM_PTR(self);
}
mp_obj_t mp_plugin_core_del(mp_obj_t self_in) {
plugin_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
// do not verify here as it will result in prints from the gc if the channel
// object was already collected. don't gc the channel plugin, it is deleted
// with the channel.
if (self->plugin &&
(self->plugin->rugin->descriptor->id != BL00MBOX_CHANNEL_PLUGIN_ID)) {
bl00mbox_log_info("deleted plugin %s",
self->plugin->rugin->descriptor->name);
bl00mbox_plugin_destroy(self->plugin);
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(mp_plugin_core_del_obj, mp_plugin_core_del);
mp_obj_t mp_plugin_core_delete(mp_obj_t self_in) {
plugin_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
plugin_core_verify(self);
if (self->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) {
mp_raise_TypeError(
MP_ERROR_TEXT("cannot manually delete channel plugin"));
}
bl00mbox_log_info("deleted plugin");
bl00mbox_plugin_destroy(self->plugin);
return mp_const_none;
}
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) {
// no need to strdup here, descriptors don't die
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_DEFINE_CONST_OBJ_TYPE(plugin_core_type, MP_QSTR_PluginCore,
MP_TYPE_FLAG_NONE, make_new,
plugin_core_make_new, locals_dict,
&plugin_core_locals_dict, attr,
plugin_core_attr);
// ========================
// SIGNALS
// ========================
STATIC mp_obj_t signal_core_make_new(const mp_obj_type_t *type, size_t n_args,
size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 2, 2, false);
plugin_core_obj_t *plugin_core = MP_OBJ_TO_PTR(args[0]);
if (plugin_core->base.type != &plugin_core_type) {
mp_raise_TypeError(MP_ERROR_TEXT("first argument must be PluginCore"));
}
// 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_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;
}
MP_DEFINE_CONST_FUN_OBJ_2(mp_signal_core_connect_obj, mp_signal_core_connect);
// legacy support
STATIC mp_obj_t mp_signal_core_connect_mx(mp_obj_t self_in) {
signal_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
plugin_core_verify(MP_OBJ_TO_PTR(self->plugin_core));
bl00mbox_error_unwrap(bl00mbox_signal_connect_mx(&self->signal));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_connect_mx_obj,
mp_signal_core_connect_mx);
STATIC mp_obj_t mp_signal_core_disconnect(mp_obj_t self_in) {
signal_core_obj_t *self = MP_OBJ_TO_PTR(self_in);
plugin_core_verify(MP_OBJ_TO_PTR(self->plugin_core));
bl00mbox_signal_disconnect(&self->signal);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(mp_signal_core_disconnect_obj,
mp_signal_core_disconnect);
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 = strdup_raise(rignal->name);
dest[0] = mp_obj_new_str(ret, strlen(ret));
free(ret);
} else if (attr == MP_QSTR_mpx) {
dest[0] = mp_obj_new_int(rignal->name_multiplex);
} else if (attr == MP_QSTR_description) {
char *ret = strdup_raise(rignal->description);
dest[0] = mp_obj_new_str(ret, strlen(ret));
free(ret);
} else if (attr == MP_QSTR_unit) {
char *ret = strdup_raise(rignal->unit);
dest[0] = mp_obj_new_str(ret, strlen(ret));
free(ret);
} else if (attr == MP_QSTR_value) {
dest[0] = mp_obj_new_int(bl00mbox_signal_get_value(signal));
} else if (attr == MP_QSTR_connected_mx) {
bool ret = false;
if (bl00mbox_signal_is_output(signal)) {
bl00mbox_connection_t *conn =
bl00mbox_connection_from_signal(signal);
if (conn) ret = conn->connected_to_mixer;
}
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_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 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),
MP_ROM_PTR(&mp_plugin_index_get_id_obj) },
{ MP_ROM_QSTR(MP_QSTR_plugin_index_get_name),
MP_ROM_PTR(&mp_plugin_index_get_name_obj) },
{ MP_ROM_QSTR(MP_QSTR_plugin_index_get_description),
MP_ROM_PTR(&mp_plugin_index_get_description_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_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_SIGNAL_HINT_GAIN),
MP_ROM_INT(RADSPA_SIGNAL_HINT_GAIN) },
{ 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) },
{ MP_ROM_QSTR(MP_QSTR_ReferenceError),
MP_ROM_PTR(&mp_type_ReferenceError) },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_bl00mbox_globals, bl00mbox_globals_table);
const mp_obj_module_t bl00mbox_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&mp_module_bl00mbox_globals,
};
MP_REGISTER_MODULE(MP_QSTR_sys_bl00mbox, bl00mbox_user_cmodule);
#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, "line_in", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(channel_plugin, 1, "line_out", RADSPA_SIGNAL_HINT_INPUT, 0);
return channel_plugin;
}
#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);
#include "bl00mbox_line_in.h"
radspa_descriptor_t bl00mbox_line_in_desc = {
.name = "bl00mbox_line_in",
.id = 4001,
.description = "connects to the line input of bl00mbox",
.create_plugin_instance = bl00mbox_line_in_create,
.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);
radspa_signal_t * right_sig = radspa_signal_get_by_index(line_in, 1);
radspa_signal_t * mid_sig = radspa_signal_get_by_index(line_in, 2);
radspa_signal_t * gain_sig = radspa_signal_get_by_index(line_in, 3);
for(uint16_t i = 0; i < num_samples; i++){
int32_t gain = radspa_signal_get_value(gain_sig, i, render_pass_id);
if(left_sig->buffer != NULL){
int16_t left = radspa_gain(bl00mbox_line_in_interlaced[2*i], gain);
radspa_signal_set_value(left_sig, i, left);
}
if(right_sig->buffer != NULL){
int16_t right = radspa_gain(bl00mbox_line_in_interlaced[2*i+1], gain);
radspa_signal_set_value(right_sig, i, right);
}
if(mid_sig->buffer != NULL){
int16_t mid = bl00mbox_line_in_interlaced[2*i]>>1;
mid += bl00mbox_line_in_interlaced[2*i+1]>>1;
mid = radspa_gain(mid, gain);
radspa_signal_set_value(mid_sig, i, mid);
}
}
}
radspa_t * bl00mbox_line_in_create(uint32_t init_var){
radspa_t * line_in = radspa_standard_plugin_create(&bl00mbox_line_in_desc, 4, 0, 0);
if(line_in == NULL) return NULL;
line_in->render = bl00mbox_line_in_run;
radspa_signal_set(line_in, 0, "left", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(line_in, 1, "right", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(line_in, 2, "mid", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(line_in, 3, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN);
return line_in;
}
#pragma once
#include "radspa.h"
#include "radspa_helpers.h"
// SPECIAL REQUIREMENTS
#include "bl00mbox_audio.h"
extern radspa_descriptor_t bl00mbox_line_in_desc;
radspa_t * bl00mbox_line_in_create(uint32_t init_var);
void bl00mbox_line_in_run(radspa_t * line_in, uint16_t num_samples, uint32_t render_pass_id);
#include "env_adsr.h"
radspa_descriptor_t env_adsr_desc = {
.name = "env_adsr",
.id = 42,
.description = "simple ADSR envelope",
.create_plugin_instance = env_adsr_create,
.destroy_plugin_instance = radspa_standard_plugin_destroy
};
#define ENV_ADSR_NUM_SIGNALS 9
#define ENV_ADSR_OUTPUT 0
#define ENV_ADSR_PHASE 1
#define ENV_ADSR_INPUT 2
#define ENV_ADSR_TRIGGER 3
#define ENV_ADSR_ATTACK 4
#define ENV_ADSR_DECAY 5
#define ENV_ADSR_SUSTAIN 6
#define ENV_ADSR_RELEASE 7
#define ENV_ADSR_GATE 8
#define ENV_ADSR_PHASE_OFF 0
#define ENV_ADSR_PHASE_ATTACK 1
#define ENV_ADSR_PHASE_DECAY 2
#define ENV_ADSR_PHASE_SUSTAIN 3
#define ENV_ADSR_PHASE_RELEASE 4
radspa_t * env_adsr_create(uint32_t init_var){
radspa_t * env_adsr = radspa_standard_plugin_create(&env_adsr_desc, ENV_ADSR_NUM_SIGNALS, sizeof(env_adsr_data_t),0);
env_adsr->render = env_adsr_run;
radspa_signal_set(env_adsr, ENV_ADSR_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(env_adsr, ENV_ADSR_PHASE, "phase", RADSPA_SIGNAL_HINT_OUTPUT, 0);
radspa_signal_set(env_adsr, ENV_ADSR_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 32767);
radspa_signal_set(env_adsr, ENV_ADSR_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
radspa_signal_set(env_adsr, ENV_ADSR_ATTACK, "attack", RADSPA_SIGNAL_HINT_INPUT, 100);
radspa_signal_set(env_adsr, ENV_ADSR_DECAY, "decay", RADSPA_SIGNAL_HINT_INPUT, 250);
radspa_signal_set(env_adsr, ENV_ADSR_SUSTAIN, "sustain", RADSPA_SIGNAL_HINT_INPUT, 16000);
radspa_signal_set(env_adsr, ENV_ADSR_RELEASE, "release", RADSPA_SIGNAL_HINT_INPUT, 50);
radspa_signal_set(env_adsr, ENV_ADSR_GATE, "gate", RADSPA_SIGNAL_HINT_INPUT,0);
radspa_signal_get_by_index(env_adsr, ENV_ADSR_ATTACK)->unit = "ms";
radspa_signal_get_by_index(env_adsr, ENV_ADSR_DECAY)->unit = "ms";
radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN)->unit = "ms";
env_adsr_data_t * data = env_adsr->plugin_data;
data->trigger_prev = 0;
data->env_phase = ENV_ADSR_PHASE_OFF;
return env_adsr;
}
static int16_t env_adsr_run_single(env_adsr_data_t * env){
uint32_t tmp;
switch(env->env_phase){
case ENV_ADSR_PHASE_OFF:
env->env_counter = 0;;
break;
case ENV_ADSR_PHASE_ATTACK:
tmp = env->env_counter + env->attack;
if(tmp < env->env_counter){ // overflow
tmp = ~((uint32_t) 0); // max out
env->env_phase = ENV_ADSR_PHASE_DECAY;
}
env->env_counter = tmp;
break;
case ENV_ADSR_PHASE_DECAY:
tmp = env->env_counter - env->decay;
if(tmp > env->env_counter){ // underflow
tmp = 0; //bottom out
}
env->env_counter = tmp;
if(env->env_counter <= env->sustain){
env->env_counter = env->sustain;
env->env_phase = ENV_ADSR_PHASE_SUSTAIN;
} else if(env->env_counter < env->gate){
env->env_counter = 0;
env->env_phase = ENV_ADSR_PHASE_OFF;
}
break;
case ENV_ADSR_PHASE_SUSTAIN:
if(env->sustain == 0) env->env_phase = ENV_ADSR_PHASE_OFF;
env->env_counter = env->sustain;
break;
case ENV_ADSR_PHASE_RELEASE:
tmp = env->env_counter - env->release;
if(tmp > env->env_counter){ // underflow
tmp = 0; //bottom out
env->env_phase = ENV_ADSR_PHASE_OFF;
}
env->env_counter = tmp;
/*
if(env->env_counter < env->gate){
env->env_counter = 0;
env->env_phase = ENV_ADSR_PHASE_OFF;
}
*/
break;
}
return env->env_counter >> 17;
}
#define SAMPLE_RATE_SORRY 48000
#define ENV_ADSR_UNDERSAMPLING 5
static inline uint32_t env_adsr_time_ms_to_val_rise(uint16_t time_ms, uint32_t val){
if(!time_ms) return UINT32_MAX;
uint32_t div = time_ms * ((SAMPLE_RATE_SORRY)/1000);
return val/div;
}
static inline uint32_t uint32_sat_leftshift(uint32_t input, uint16_t left){
if(!left) return input; // nothing to do
if(input >> (32-left)) return UINT32_MAX; // sat
return input << left;
}
void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){
env_adsr_data_t * plugin_data = env_adsr->plugin_data;
radspa_signal_t * output_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_OUTPUT);
radspa_signal_t * phase_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_PHASE);
if((output_sig->buffer == NULL) && (phase_sig->buffer == NULL)) return;
radspa_signal_t * trigger_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_TRIGGER);
radspa_signal_t * input_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_INPUT);
radspa_signal_t * attack_sig = NULL;
radspa_signal_t * decay_sig = NULL;
radspa_signal_t * sustain_sig = NULL;
radspa_signal_t * release_sig = NULL;
radspa_signal_t * gate_sig = NULL;
int16_t ret = output_sig->value;
for(uint16_t i = 0; i < num_samples; i++){
static int16_t env = 0;
int16_t trigger = trigger_sig->get_value(trigger_sig, i, num_samples, render_pass_id);
int16_t vel = radspa_trigger_get(trigger, &(plugin_data->trigger_prev));
if(vel < 0){ // stop
if(plugin_data->env_phase != ENV_ADSR_PHASE_OFF){
plugin_data->env_phase = ENV_ADSR_PHASE_RELEASE;
plugin_data->release_init_val = plugin_data->env_counter;
}
} else if(vel > 0 ){ // start
plugin_data->env_phase = ENV_ADSR_PHASE_ATTACK;
plugin_data->velocity = ((uint32_t) vel) << 17;
}
if(!(i%(1<<ENV_ADSR_UNDERSAMPLING))){
uint16_t time_ms;
uint32_t sus;
switch(plugin_data->env_phase){
case ENV_ADSR_PHASE_OFF:
break;
case ENV_ADSR_PHASE_ATTACK:
if(attack_sig == NULL){
attack_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_ATTACK);
}
time_ms = attack_sig->get_value(attack_sig, i, num_samples, render_pass_id);
if(time_ms != plugin_data->attack_prev_ms){
plugin_data->attack = uint32_sat_leftshift(env_adsr_time_ms_to_val_rise(time_ms, UINT32_MAX), ENV_ADSR_UNDERSAMPLING);
plugin_data->attack_prev_ms = time_ms;
}
break;
case ENV_ADSR_PHASE_DECAY:
if(sustain_sig == NULL){
sustain_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN);
}
sus = sustain_sig->get_value(sustain_sig, i, num_samples, render_pass_id);
plugin_data->sustain = sus<<17;
if(gate_sig == NULL){
gate_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_GATE);
}
sus = gate_sig->get_value(gate_sig, i, num_samples, render_pass_id);
plugin_data->gate = sus<<17;
if(decay_sig == NULL){
decay_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_DECAY);
}
time_ms = decay_sig->get_value(decay_sig, i, num_samples, render_pass_id);
if(time_ms != plugin_data->decay_prev_ms){
plugin_data->decay = uint32_sat_leftshift(env_adsr_time_ms_to_val_rise(time_ms, UINT32_MAX-plugin_data->sustain), ENV_ADSR_UNDERSAMPLING);
plugin_data->decay_prev_ms = time_ms;
}
break;
case ENV_ADSR_PHASE_SUSTAIN:
if(sustain_sig == NULL){
sustain_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN);
}
sus = sustain_sig->get_value(sustain_sig, i, num_samples, render_pass_id);
plugin_data->sustain = sus<<17;
break;
case ENV_ADSR_PHASE_RELEASE:
if(gate_sig == NULL){
gate_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_GATE);
}
sus = gate_sig->get_value(gate_sig, i, num_samples, render_pass_id);
plugin_data->gate = sus<<17;
if(release_sig == NULL){
release_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_RELEASE);
}
time_ms = release_sig->get_value(release_sig, i, num_samples, render_pass_id);
if(time_ms != plugin_data->release_prev_ms){
plugin_data->release = uint32_sat_leftshift(env_adsr_time_ms_to_val_rise(time_ms, plugin_data->release_init_val), ENV_ADSR_UNDERSAMPLING);
plugin_data->release_prev_ms = time_ms;
}
break;
}
env = env_adsr_run_single(plugin_data);
}
if(env){
int16_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
ret = radspa_mult_shift(env, input);
} else {
ret = 0;
}
if(phase_sig->buffer != NULL) (phase_sig->buffer)[i] = plugin_data->env_phase;
if(output_sig->buffer != NULL) (output_sig->buffer)[i] = ret;
}
phase_sig->value = plugin_data->env_phase;
output_sig->value = ret;
}