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 //SPDX-License-Identifier: CC0-1.0
#include "bl00mbox_radspa_requirements.h" #include "bl00mbox_radspa_requirements.h"
bool radspa_host_request_buffer_render(int16_t * buf, uint16_t num_samples){ bool radspa_host_request_buffer_render(int16_t * buf){
bl00mbox_bud_t * bud = ((bl00mbox_connection_t *) buf)->source_bud; bl00mbox_plugin_t * plugin = ((bl00mbox_connection_t *) buf)->source.plugin;
bl00mbox_audio_bud_render(bud, num_samples); bl00mbox_audio_plugin_render(plugin);
return 1; return 1;
} }
...@@ -58,41 +58,4 @@ uint32_t radspa_sct_to_rel_freq(int16_t sct, int16_t undersample_pow){ ...@@ -58,41 +58,4 @@ uint32_t radspa_sct_to_rel_freq(int16_t sct, int16_t undersample_pow){
return ret; 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; } int16_t radspa_random(){ return xoroshiro64star()>>16; }
//SPDX-License-Identifier: CC0-1.0 //SPDX-License-Identifier: CC0-1.0
#include "bl00mbox_user.h" #include "bl00mbox_user.h"
static uint64_t bl00mbox_bud_index = 1; extern void bl00mbox_disconnect_rx_callback(void * rx, uint16_t signal_index);
bl00mbox_bud_t * bl00mbox_channel_get_bud_by_index(uint8_t channel, uint32_t 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){ // get signal struct from a signal index
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); radspa_signal_t * bl00mbox_signal_get_by_index(radspa_t * plugin, uint16_t signal_index){
uint16_t ret = 0; return &(plugin->signals[signal_index]);
if(chan->buds != NULL){
bl00mbox_bud_t * last = chan->buds;
ret++;
while(last->chan_next != NULL){
last = last->chan_next;
ret++;
} }
}
return ret; static inline bl00mbox_connection_t * conn_from_signal(bl00mbox_signal_t * signal){
return (bl00mbox_connection_t *) signal->rignal->buffer;
} }
uint64_t bl00mbox_channel_get_bud_by_list_pos(uint8_t channel, uint32_t pos){ static bool signal_is_input(bl00mbox_signal_t * signal){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); return signal->rignal->hints & RADSPA_SIGNAL_HINT_INPUT ? true : false;
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_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){ static bool signals_are_connected(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); if(!some->rignal->buffer) return false;
uint16_t ret = 0; return some->rignal->buffer == other->rignal->buffer;
if(chan->connections != NULL){
bl00mbox_connection_t * last = chan->connections;
ret++;
while(last->chan_next != NULL){
last = last->chan_next;
ret++;
} }
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_connection_t * bl00mbox_connection_from_signal(bl00mbox_signal_t * signal){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); return conn_from_signal(signal);
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++;
} }
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; return ret;
} }
uint64_t bl00mbox_channel_get_bud_by_mixer_list_pos(uint8_t channel, uint32_t pos){ uint16_t bl00mbox_channel_conns_num(bl00mbox_channel_t * chan){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); return chan->connections.len;
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;
} }
uint32_t bl00mbox_channel_get_signal_by_mixer_list_pos(uint8_t channel, uint32_t pos){ uint16_t bl00mbox_channel_mixer_num(bl00mbox_channel_t * chan){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); return chan->roots.len;
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_subscriber_num(uint8_t channel, uint64_t bud_index, uint16_t signal_index){ bl00mbox_array_t * bl00mbox_channel_collect_connections_mx(bl00mbox_channel_t * chan){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); bl00mbox_array_t * ret;
if(chan == NULL) return 0; ret = malloc(sizeof(bl00mbox_array_t) + chan->roots.len * sizeof(void *));
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); if(!ret) return NULL;
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; ret->len = chan->roots.len;
if(conn->subs != NULL){
bl00mbox_connection_subscriber_t * last = conn->subs; bl00mbox_set_iter_t iter;
ret++; bl00mbox_set_iter_start(&iter, &chan->roots);
while(last->next != NULL){ bl00mbox_connection_t * conn;
last = last->next; size_t i = 0;
ret++; while((conn = bl00mbox_set_iter_next(&iter))){
} ret->elems[i++] = &conn->source;
} }
return ret; return ret;
} }
uint64_t bl00mbox_channel_get_bud_by_subscriber_list_pos(uint8_t channel, uint64_t bud_index, bl00mbox_array_t * bl00mbox_signal_collect_connections(bl00mbox_signal_t * signal){
uint16_t signal_index, uint8_t pos){ // caller has to free memory
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); // return NULL -> OOM error
if(chan == NULL) return 0; bl00mbox_array_t * ret = NULL;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); bl00mbox_connection_t * conn = conn_from_signal(signal);
if(bud == NULL) return 0; if(!conn){
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, signal_index); ret = malloc(sizeof(bl00mbox_array_t));
if(sig == NULL) return 0; if(!ret) return NULL;
bl00mbox_connection_t * conn = (bl00mbox_connection_t *) sig->buffer; // buffer sits on top of struct ret->len = 0;
if(conn == NULL) return 0; } else if(signal->rignal->hints & RADSPA_SIGNAL_HINT_INPUT){
ret = malloc(sizeof(bl00mbox_array_t) + sizeof(void *));
uint16_t ret = 0; if(!ret) return NULL;
if(conn->subs != NULL){ ret->len = 1;
bl00mbox_connection_subscriber_t * last = conn->subs; ret->elems[0] = &conn->source;
if(pos == ret) return (last->type == 0) ? last->bud_index : 0; } else {
ret++; // TODO: add sentinel for channel mixer connection
while(last->next != NULL){ ret = malloc(sizeof(bl00mbox_array_t) + conn->subscribers.len * sizeof(void *));
last = last->next; if(!ret) return NULL;
if(pos == ret) return (last->type == 0) ? last->bud_index : 0; ret->len = conn->subscribers.len;
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;
if(chan->connections != NULL){ bl00mbox_set_iter_t iter;
bl00mbox_connection_t * last = chan->connections; bl00mbox_set_iter_start(&iter, &conn->subscribers);
while(last->chan_next != NULL){ bl00mbox_signal_t * sub;
last = last->chan_next; 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; return ret;
} }
static bool weak_delete_connection(bl00mbox_connection_t * conn){ static void update_roots(bl00mbox_channel_t * chan){
if(conn->subs != NULL) return false; // 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; // render_buffers are simple, just copy the content of the set and add the output plugin manually
bl00mbox_bud_t * bud = conn->source_bud; int num_render_buffers = roots->len + (output_rignal->buffer ? 1 : 0);
if(bud != NULL){ bl00mbox_array_t * render_buffers = malloc(sizeof(bl00mbox_array_t) + sizeof(void *) * num_render_buffers);
radspa_signal_t * tx = radspa_signal_get_by_index(bud->plugin, conn->signal_index); // for render_plugins there may be duplicates. we still allocate full memory for now
if(tx != NULL){ int num_render_plugins = roots->len + always_render->len + (output_rignal->buffer ? 1 : 0);
bl00mbox_audio_waitfor_pointer_change(&(tx->buffer), NULL); 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 size_t index = 0;
bl00mbox_channel_t * chan = bl00mbox_get_channel(conn->channel);
if(chan->connections != NULL){ bl00mbox_set_iter_t iter;
if(chan->connections != conn){ // filling up mixer roots
bl00mbox_connection_t * prev = chan->connections; bl00mbox_set_iter_start(&iter, roots);
while(prev->chan_next != conn){ bl00mbox_connection_t * conn;
prev = prev->chan_next; while((conn = bl00mbox_set_iter_next(&iter))){
if(prev->chan_next == NULL){ 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; break;
} }
} }
if(prev->chan_next != NULL) bl00mbox_audio_waitfor_pointer_change(&(prev->chan_next), conn->chan_next); if(!is_duplicate){
} else { render_plugins->elems[index++] = conn->source.plugin;
bl00mbox_audio_waitfor_pointer_change(&(chan->connections), conn->chan_next);
} }
} }
free(conn); // adding always_render to the plugin list. those should be after the regular roots
return true; // 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){ // if we overshot let's trim excess memory.
/// creates a new bud instance of the plugin with descriptor id "id" and the initialization variable if(index != num_render_plugins){
/// "init_var" and appends it to the plugin list of the corresponding channel. returns pointer to bl00mbox_array_t * tmp = realloc(render_plugins, sizeof(bl00mbox_array_t) + sizeof(void *) * index);
/// the bud if successfull, else NULL. if(tmp) render_plugins = tmp;
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;
} }
bool bl00mbox_channel_delete_bud(uint8_t channel, uint32_t bud_index){ defer:
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); if(!(render_plugins && render_buffers)){
if(chan == NULL) return false; free(render_plugins);
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); free(render_buffers);
if(bud == NULL) return false; render_plugins = NULL;
render_buffers = NULL;
// disconnect all signals bl00mbox_log_error("out of memory, render list cleared")
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;
} }
prev = seek; #ifdef BL00MBOX_DEBUG
seek = seek->chan_next; else {
bl00mbox_log_info("new render data, %d plugins, %d buffers",
(int) render_plugins->len, (int) render_buffers->len);
} }
if(seek != NULL){ #endif
if(prev != NULL){ bl00mbox_array_t * render_plugins_prev = chan->render_plugins;
bl00mbox_audio_waitfor_pointer_change(&(prev->chan_next), seek->chan_next); bl00mbox_array_t * render_buffers_prev = chan->render_buffers;
} else { bl00mbox_take_lock(&chan->render_lock);
bl00mbox_audio_waitfor_pointer_change(&(chan->buds), seek->chan_next); 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); bl00mbox_channel_event(chan);
if(free_later) free(seek); return plugin;
return true;
} }
bool bl00mbox_channel_clear(uint8_t channel){ void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); bl00mbox_channel_t * chan = plugin->channel;
if(chan == NULL) return false;
bl00mbox_bud_t * bud = chan->buds; // remove from parent
while(bud != NULL){ if(* (plugin->parent_self_ref) != plugin){
bl00mbox_bud_t * bud_next = bud->chan_next; bl00mbox_log_error("plugin: parent_self_ref improper: plugin %s, %p, ref %p",
bl00mbox_channel_delete_bud(channel, bud->index); plugin->rugin->descriptor->name, plugin, * (plugin->parent_self_ref));
bud = bud_next;
} }
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){ // pop from sets
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); bl00mbox_plugin_set_always_render(plugin, false);
if(chan == NULL) return false; bl00mbox_set_remove(&chan->plugins, plugin);
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;
bl00mbox_channel_root_t * root = malloc(sizeof(bl00mbox_channel_root_t)); if(plugin == chan->channel_plugin) chan->channel_plugin = NULL;
if(root == NULL) return false;
bl00mbox_connection_subscriber_t * sub = malloc(sizeof(bl00mbox_connection_subscriber_t));
if(sub == NULL){ free(root); return false; }
bl00mbox_connection_t * conn; plugin->rugin->descriptor->destroy_plugin_instance(plugin->rugin);
if(tx->buffer == NULL){ // doesn't feed a buffer yet free(plugin);
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;
} }
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; static bl00mbox_connection_t * weak_create_connection(bl00mbox_signal_t * source){
sub->bud_index = bud_index; bl00mbox_connection_t * conn = conn_from_signal(source);
sub->signal_index = bud_signal_index; if(conn) return conn;
sub->next = NULL; conn = create_connection(source);
if(conn->subs == NULL){ return conn;
conn->subs = sub;
} else {
bl00mbox_connection_subscriber_t * seek = conn->subs;
while(seek->next != NULL){ seek = seek->next; }
seek->next = sub;
} }
root->con = conn; static bool weak_delete_connection(bl00mbox_connection_t * conn){
root->next = NULL; // 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){ // disconnect buffer from plugin
chan->root_list = root; bl00mbox_take_lock(&chan->render_lock);
} else { conn->source.rignal->buffer = NULL;
bl00mbox_channel_root_t * last_root = chan->root_list; bl00mbox_give_lock(&chan->render_lock);
while(last_root->next != NULL){ last_root = last_root->next; }
last_root->next = root; // 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; return true;
} }
bool bl00mbox_channel_disconnect_signal_from_output_mixer(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){ bl00mbox_error_t bl00mbox_signal_connect_mx(bl00mbox_signal_t * signal){
//TODO if(!signal_is_output(signal)) return BL00MBOX_ERROR_INVALID_CONNECTION;
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); bl00mbox_channel_t * chan = signal->plugin->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_connection_t * conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct bl00mbox_connection_t * conn = weak_create_connection(signal);
if(conn == NULL) return false; //not connected if(!conn) goto failed;
bl00mbox_channel_root_t * rt = chan->root_list; // check if already connected
bl00mbox_channel_root_t * rt_prev = NULL; 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){ update_roots(chan);
if(rt->con == conn) break; bl00mbox_connect_mx_callback(chan->parent, signal->plugin->parent);
rt_prev = rt; bl00mbox_channel_event(chan);
rt = rt->next; return BL00MBOX_ERROR_OK;
}
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);
}
if(conn->subs != NULL){ failed:
bl00mbox_connection_subscriber_t * seek = conn->subs; if(conn){
bl00mbox_connection_subscriber_t * prev = NULL; conn->connected_to_mixer = false;
while(seek != NULL){ weak_delete_connection(conn);
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);
} }
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); 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){ static void bl00mbox_signal_disconnect_rx(bl00mbox_signal_t * signal){
bl00mbox_bud_t * bud_rx = bl00mbox_channel_get_bud_by_index(channel, bud_rx_index); if(!signal_is_input(signal)) return;
if(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); // try to get connection and return if not connected
if(rx == NULL) return false; // signal index doesn't exist bl00mbox_connection_t * conn = conn_from_signal(signal);
if(rx->buffer == NULL) return false; if(!conn) return;
if(!(rx->hints & RADSPA_SIGNAL_HINT_INPUT)) return false;
bl00mbox_connection_t * conn = (bl00mbox_connection_t *) rx->buffer; // buffer sits on top of struct bl00mbox_channel_t * chan = signal->plugin->channel;
if(conn == NULL) return false; //not connected 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(signal->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) update_roots(chan);
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
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); 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){ void bl00mbox_signal_disconnect_tx(bl00mbox_signal_t * signal){
bl00mbox_bud_t * bud_tx = bl00mbox_channel_get_bud_by_index(channel, bud_tx_index); if(!signal_is_output(signal)) return;
if(bud_tx == NULL) return false; // bud index doesn't exist 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); // disconnect from mixer
if(tx == NULL) return false; // signal index doesn't exist if(conn->connected_to_mixer) bl00mbox_signal_disconnect_mx(signal);
if(tx->buffer == NULL) return false;
if(!(tx->hints & RADSPA_SIGNAL_HINT_OUTPUT)) return false;
bl00mbox_connection_t * conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct // disconnect all subscribers
if(conn == NULL) return false; //not connected 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){ void bl00mbox_signal_disconnect(bl00mbox_signal_t * signal){
switch(conn->subs->type){ if(!conn_from_signal(signal)) return;
case 0: if(signal_is_input(signal)){
bl00mbox_channel_disconnect_signal_rx(channel, conn->subs->bud_index, conn->subs->signal_index); bl00mbox_signal_disconnect_rx(signal);
break;
case 1:
bl00mbox_channel_disconnect_signal_from_output_mixer(channel, conn->subs->bud_index, conn->subs->signal_index);
break;
} }
if(signal_is_output(signal)){
bl00mbox_signal_disconnect_tx(signal);
bl00mbox_signal_disconnect_mx(signal);
} }
bl00mbox_channel_event(channel); if(conn_from_signal(signal)) bl00mbox_log_error("connection persists after disconnect");
return true;
} }
bool bl00mbox_channel_disconnect_signal(uint8_t channel, uint32_t bud_index, uint32_t signal_index){ bl00mbox_error_t bl00mbox_signal_connect(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); // are signals on the same channel?
if(bud == NULL) return false; // bud index doesn't exist if(some->plugin->channel != other->plugin->channel) return BL00MBOX_ERROR_INVALID_CONNECTION;
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, signal_index); bl00mbox_channel_t * chan = some->plugin->channel;
if(sig == NULL) return false; // signal index doesn't exist
if(sig->buffer == NULL) return false;
bl00mbox_channel_disconnect_signal_rx(channel, bud_index, signal_index); // which one is input, which one is output?
bl00mbox_channel_disconnect_signal_tx(channel, bud_index, signal_index); bl00mbox_signal_t * signal_rx;
bl00mbox_channel_disconnect_signal_from_output_mixer(channel, bud_index, signal_index); bl00mbox_signal_t * signal_tx;
if(sig->buffer == NULL) return true; if(signal_is_input(some) && signal_is_output(other)){
return false; 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_t * conn;
bl00mbox_connection_subscriber_t * sub; if(!(conn = conn_from_signal(signal_tx))){
if(tx->buffer == NULL){ // doesn't feed a buffer yet if(!(conn = create_connection(signal_tx))) return BL00MBOX_ERROR_OOM;
conn = create_connection(channel);
if(conn == NULL) return false; // no ram for connection
// set up new connection
conn->signal_index = bud_tx_signal_index;
conn->source_bud = bud_tx;
tx->buffer = conn->buffer;
} else { } else {
if(rx->buffer == tx->buffer) return false; // already connected if(signals_are_connected(signal_rx, signal_tx)) return BL00MBOX_ERROR_OK; // already connected
conn = (bl00mbox_connection_t *) tx->buffer; // buffer sits on top of struct
} }
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)); bl00mbox_signal_t * sub;
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;
}
rx->buffer = tx->buffer; sub = malloc(sizeof(bl00mbox_signal_t));
bl00mbox_channel_event(channel); if(!sub){
return true; weak_delete_connection(conn);
return BL00MBOX_ERROR_OOM;
} }
bool bl00mbox_channel_bud_exists(uint8_t channel, uint32_t bud_index){ memcpy(sub, signal_rx, sizeof(bl00mbox_signal_t));
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;
}
}
char * bl00mbox_channel_bud_get_name(uint8_t channel, uint32_t bud_index){ bl00mbox_set_add(&conn->subscribers, sub);
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;
}
int16_t bl00mbox_channel_bud_get_signal_value(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){ // we must block here else we could access the buffer of a constant signal
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); // and think it's nonconst, which is bad
if(chan == NULL) return false; bl00mbox_take_lock(&chan->render_lock);
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); signal_rx->rignal->buffer = signal_tx->rignal->buffer;
if(bud == NULL) return false; bl00mbox_give_lock(&chan->render_lock);
radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
if(sig == NULL) return false;
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_connect_rx_callback(signal_rx->plugin->parent, signal_tx->plugin->parent, signal_rx->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->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){ void bl00mbox_plugin_set_always_render(bl00mbox_plugin_t * plugin, bool value){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); bl00mbox_channel_t * chan = plugin->channel;
if(chan == NULL) return false; if(plugin->always_render == value) return;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); plugin->always_render = value;
if(bud == NULL) return false;
if(bud->plugin->plugin_table == NULL) return false; if(value){
if(table_index >= bud->plugin->plugin_table_len) return false; bl00mbox_set_add(&chan->always_render, plugin);
bud->plugin->plugin_table[table_index] = value; } else {
bl00mbox_channel_event(channel); bl00mbox_set_remove(&chan->always_render, plugin);
return true;
} }
update_roots(chan);
int16_t bl00mbox_channel_bud_get_table_value(uint8_t channel, uint32_t bud_index, uint32_t table_index){ bl00mbox_channel_event(chan);
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];
} }
int16_t * bl00mbox_channel_bud_get_table_pointer(uint8_t channel, uint32_t bud_index){ bl00mbox_error_t bl00mbox_signal_set_value(bl00mbox_signal_t * signal, int value){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); //while(signal->plugin->is_being_rendered) {};
if(chan == NULL) return false; if(!signal_is_input(signal)) return BL00MBOX_ERROR_INVALID_CONNECTION;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); if(conn_from_signal(signal)) bl00mbox_signal_disconnect(signal);
if(bud == NULL) return false; signal->rignal->value = value < -32767 ? -32767 : (value > 32767 ? 32767 : value);
if(bud->plugin->plugin_table == NULL) return false; return BL00MBOX_ERROR_OK;
return bud->plugin->plugin_table;
} }
uint32_t bl00mbox_channel_bud_get_table_len(uint8_t channel, uint32_t bud_index){ int16_t bl00mbox_signal_get_value(bl00mbox_signal_t * signal){
bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); //while(signal->plugin->is_being_rendered) {};
if(chan == NULL) return 0; return signal->rignal->buffer ? signal->rignal->buffer[0] : signal->rignal->value;
bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index);
if(bud == NULL) return 0;
return bud->plugin->plugin_table_len;
} }
#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 @@ ...@@ -8,9 +8,5 @@
uint16_t bl00mbox_sources_count(); uint16_t bl00mbox_sources_count();
uint16_t bl00mbox_source_add(void* render_data, void* render_function); uint16_t bl00mbox_source_add(void* render_data, void* render_function);
void bl00mbox_source_remove(uint16_t index); void bl00mbox_source_remove(uint16_t index);
void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len); bool 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);
void bl00mbox_init(void); void bl00mbox_init(void);
//SPDX-License-Identifier: CC0-1.0 //SPDX-License-Identifier: CC0-1.0
#pragma once #pragma once
#include "bl00mbox_config.h"
//TODO: move this to kconfig someday #include "bl00mbox_os.h"
#define BL00MBOX_MAX_BUFFER_LEN 256 #include "bl00mbox_containers.h"
#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 <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <string.h> #include <string.h>
#include "radspa.h" #include "radspa.h"
#include "radspa_helpers.h"
struct _bl00mbox_bud_t; struct _bl00mbox_plugin_t;
struct _bl00mbox_connection_source_t; struct _bl00mbox_connection_source_t;
struct _bl00mbox_channel_root_t; struct _bl00mbox_channel_root_t;
struct _bl00mbox_channel_t; struct _bl00mbox_channel_t;
typedef struct _bl00mbox_bud_t{ extern int16_t * bl00mbox_line_in_interlaced;
radspa_t * plugin; // plugin
// pointer is unique identifier, no memcpy of this!
typedef struct _bl00mbox_plugin_t{
radspa_t * rugin; // radspa plugin
char * name; char * name;
uint64_t index; // unique index number for bud uint32_t id; // unique number in channel to for UI purposes
uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary
uint8_t channel; // index of channel that owns the plugin uint32_t init_var; // init var that was used for plugin creation
struct _bl00mbox_bud_t * chan_next; //for linked list in bl00mbox_channel_t volatile bool is_being_rendered; // true if rendering the plugin is in progress, else false.
} bl00mbox_bud_t; bool always_render;
struct _bl00mbox_channel_t * channel; // channel that owns the plugin
typedef struct _bl00mbox_connection_subscriber_t{
uint8_t type; // 0: standard signal input, 1: output mixer void * parent;
uint8_t channel; struct _bl00mbox_plugin_t ** parent_self_ref;
uint64_t bud_index; } bl00mbox_plugin_t;
uint32_t signal_index;
struct _bl00mbox_connection_subscriber_t * next; // pointer is NOT unique identifier, memcpy allowed
} bl00mbox_connection_subscriber_t; 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 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! int16_t buffer[BL00MBOX_MAX_BUFFER_LEN]; // MUST stay on top of struct bc type casting! TODO: offsetof()
struct _bl00mbox_bud_t * source_bud; bl00mbox_signal_t source;
uint32_t signal_index; // signal of source_bud that renders to buffer bl00mbox_set_t subscribers; // content: bl00mbox_signal_t;
struct _bl00mbox_connection_subscriber_t * subs; bool connected_to_mixer; // legacy thing, don't wanna sentinel subsribers
uint8_t channel;
struct _bl00mbox_connection_t * chan_next; //for linked list in bl00mbox_channel_t;
} bl00mbox_connection_t; } bl00mbox_connection_t;
typedef struct _bl00mbox_channel_root_t{ // pointer is unique identifier, no memcpy of this!
struct _bl00mbox_connection_t * con; typedef struct _bl00mbox_channel_t{
struct _bl00mbox_channel_root_t * next;
} bl00mbox_channel_root_t;
typedef struct{
bool is_active; // rendering can be skipped if false
bool is_free;
char * name; char * name;
int32_t volume; 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 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_channel_t * bl00mbox_get_channel(uint8_t chan); void bl00mbox_audio_init();
void bl00mbox_channel_enable(uint8_t chan);
bl00mbox_channel_t * bl00mbox_channel_create();
void bl00mbox_channel_enable(uint8_t chan); void bl00mbox_audio_plugin_render(bl00mbox_plugin_t * plugin);
void bl00mbox_channel_disable(uint8_t chan); bool bl00mbox_channel_get_foreground(bl00mbox_channel_t * chan);
void bl00mbox_channel_set_volume(uint8_t chan, uint16_t volume); void bl00mbox_channel_set_foreground(bl00mbox_channel_t * chan, bool enable);
int16_t bl00mbox_channel_get_volume(uint8_t chan); void bl00mbox_channel_set_background_mute_override(bl00mbox_channel_t * chan, bool enable);
void bl00mbox_channel_event(uint8_t chan); void bl00mbox_channel_clear(bl00mbox_channel_t * chan);
uint8_t bl00mbox_channel_get_free_index(); void bl00mbox_channel_destroy(bl00mbox_channel_t * chan);
void bl00mbox_channels_init(); void bl00mbox_channel_event(bl00mbox_channel_t * channel);
uint8_t bl00mbox_channel_get_foreground_index(); bl00mbox_array_t * bl00mbox_collect_channels(bool active);
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);
#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 @@ ...@@ -2,6 +2,8 @@
#pragma once #pragma once
#include "stdio.h" #include "stdio.h"
#include "radspa.h" #include "radspa.h"
#include "bl00mbox_os.h"
#include "bl00mbox_channel_plugin.h"
typedef struct _bl00mbox_plugin_registry_t{ typedef struct _bl00mbox_plugin_registry_t{
radspa_descriptor_t * descriptor; radspa_descriptor_t * descriptor;
......
...@@ -7,50 +7,34 @@ ...@@ -7,50 +7,34 @@
#include "bl00mbox_plugin_registry.h" #include "bl00mbox_plugin_registry.h"
#include "bl00mbox_audio.h" #include "bl00mbox_audio.h"
#include "bl00mbox_os.h"
#include "bl00mbox_containers.h"
#include <stdint.h> #include <stdint.h>
#include "bl00mbox_audio.h" #include "bl00mbox_audio.h"
#include "radspa_helpers.h" #include "radspa_helpers.h"
uint16_t bl00mbox_channel_buds_num(uint8_t channel); // lazy 2nd error channel: pointer return types resulting in NULL means OOM
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_plugins_num(bl00mbox_channel_t * chan);
uint16_t bl00mbox_channel_mixer_num(uint8_t channel); uint16_t bl00mbox_channel_conns_num(bl00mbox_channel_t * chan);
uint64_t bl00mbox_channel_get_bud_by_mixer_list_pos(uint8_t channel, uint32_t pos); uint16_t bl00mbox_channel_mixer_num(bl00mbox_channel_t * chan);
uint32_t bl00mbox_channel_get_signal_by_mixer_list_pos(uint8_t channel, uint32_t pos); bl00mbox_array_t * bl00mbox_channel_collect_plugins(bl00mbox_channel_t * chan);
bool bl00mbox_channel_clear(uint8_t channel); bl00mbox_array_t * bl00mbox_channel_collect_connections_mx(bl00mbox_channel_t * chan);
bool bl00mbox_channel_connect_signal_to_output_mixer(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index); bl00mbox_plugin_t * bl00mbox_plugin_create_unlisted(bl00mbox_channel_t * chan, radspa_descriptor_t * desc, uint32_t init_var);
bool bl00mbox_channel_connect_signal(uint8_t channel, uint32_t bud_rx_index, uint32_t bud_rx_signal_index, bl00mbox_plugin_t * bl00mbox_plugin_create(bl00mbox_channel_t * chan, uint32_t id, uint32_t init_var);
uint32_t bud_tx_index, uint32_t bud_tx_signal_index); void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin);
bool bl00mbox_channel_disconnect_signal_rx(uint8_t channel, uint32_t bud_rx_index, uint32_t bud_rx_signal_index); void bl00mbox_plugin_set_always_render(bl00mbox_plugin_t * plugin, bool value);
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); bl00mbox_error_t bl00mbox_signal_set_value(bl00mbox_signal_t * signal, int value);
bool bl00mbox_channel_disconnect_signal_from_output_mixer(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index); 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_bud_t * bl00mbox_channel_new_bud(uint8_t channel, uint32_t id, uint32_t init_var); bl00mbox_error_t bl00mbox_signal_connect_mx(bl00mbox_signal_t * some);
bool bl00mbox_channel_delete_bud(uint8_t channel, uint32_t bud_index); void bl00mbox_signal_disconnect(bl00mbox_signal_t * signal);
bool bl00mbox_channel_bud_exists(uint8_t channel, uint32_t bud_index); bl00mbox_array_t * bl00mbox_signal_collect_connections(bl00mbox_signal_t * signal);
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); bl00mbox_connection_t * bl00mbox_connection_from_signal(bl00mbox_signal_t * signal);
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); bool bl00mbox_signal_is_input(bl00mbox_signal_t * signal);
bool bl00mbox_signal_is_output(bl00mbox_signal_t * signal);
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);
...@@ -11,7 +11,7 @@ def terminal_scope( ...@@ -11,7 +11,7 @@ def terminal_scope(
fun=None, fun=None,
fun_ms=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: if signal_max <= signal_min:
return return
ms_counter = 0 ms_counter = 0
...@@ -24,12 +24,16 @@ def terminal_scope( ...@@ -24,12 +24,16 @@ def terminal_scope(
if fun_ms <= fun_counter: if fun_ms <= fun_counter:
fun() fun()
fun_counter = fun_counter % fun_ms 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: if val > width:
val = width val = width
if val < 0: if val < 0:
val = 0 val = 0
ret = f"{ms_counter:06d}"
ret += " [" + "X" * val + "." * (width - val) + "]" ret += " [" + "X" * val + "." * (width - val) + "]"
percent = int(100 * val / width) percent = int(100 * val / width)
ret += f" {percent:02d}%" ret += f" {percent:02d}%"
......
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
import math import math
import os
import bl00mbox import bl00mbox
import cpython.wave as wave import cpython.wave as wave
class _Patch: class _Patch:
def __init__(self, chan): def __init__(self, chan):
self.plugins = _PatchPluginList()
self.signals = _PatchSignalList() self.signals = _PatchSignalList()
self._channel = chan 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): def __repr__(self):
ret = "[patch] " + type(self).__name__ ret = "[patch] " + type(self).__name__
ret += "\n [signals:] " + "\n ".join(repr(self.signals).split("\n")) 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 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: class _PatchItemList:
def __init__(self): def __init__(self):
self._items = [] self._items = []
# workaround
def __iter__(self): def __iter__(self):
return iter(self._items) return iter(self._items)
...@@ -33,11 +62,10 @@ class _PatchItemList: ...@@ -33,11 +62,10 @@ class _PatchItemList:
return ret return ret
def __setattr__(self, key, value): def __setattr__(self, key, value):
# old style
current_value = getattr(self, key, None) current_value = getattr(self, key, None)
if current_value is None and not key.startswith("_"): if current_value is None and not key.startswith("_"):
self._items.append(key) self._items.append(key)
super().__setattr__(key, value) super().__setattr__(key, value)
...@@ -46,7 +74,7 @@ class _PatchSignalList(_PatchItemList): ...@@ -46,7 +74,7 @@ class _PatchSignalList(_PatchItemList):
current_value = getattr(self, key, None) current_value = getattr(self, key, None)
if isinstance(current_value, bl00mbox.Signal): if isinstance(current_value, bl00mbox.Signal):
current_value.value = value current_value.value = value
return else:
super().__setattr__(key, value) super().__setattr__(key, value)
...@@ -100,61 +128,158 @@ class tinysynth_fm(tinysynth): ...@@ -100,61 +128,158 @@ class tinysynth_fm(tinysynth):
class sampler(_Patch): 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) super().__init__(chan)
if filename.startswith("/"): self.buffer_offset_i16 = 11
f = wave.open("/flash/" + filename, "r") self._filename = ""
if type(init_var) == str:
self.plugins.sampler = chan.new(
bl00mbox.plugins.sampler, self._convert_filename(init_var)
)
else: 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.signals.trigger = self.plugins.sampler.signals.playback_trigger
self.plugins.sampler = chan.new(bl00mbox.plugins._sampler_ram, self.len_frames) 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 # terrible backwards compatibility hack, never do that IRL
assert f.getnchannels() in (1, 2) del self.plugins.sampler.signals._setattr_allowed
assert f.getcomptype() == "NONE" self.plugins.sampler.signals.pitch_shift = (
self.plugins.sampler.signals.playback_speed
if f.getnchannels() == 1: )
# fast path for mono self.plugins.sampler.signals._setattr_allowed = True
table = self.plugins.sampler.table_bytearray
for i in range(0, self.len_frames * 2, 100): def _convert_filename(self, filename):
table[i : i + 100] = f.readframes(50) # TODO: waht if filename doesn't exist?
if filename.startswith("/flash/") or filename.startswith("/sd/"):
return filename
elif filename.startswith("/"):
return "/flash/" + filename
else: else:
# somewhat fast path for stereo return "/flash/sys/samples/" + filename
table = self.plugins.sampler.table_int16_array
for i in range(self.len_frames): def load(self, filename):
frame = f.readframes(1) filename = self._convert_filename(filename)
value = int.from_bytes(frame[0:2], "little") try:
table[i] = value self.plugins.sampler.load(filename)
except:
f.close() # no proper exception catching bc backwards compat
self._filename = filename return False
self.signals.trigger = self.plugins.sampler.signals.trigger return True
self.signals.output = self.plugins.sampler.signals.output
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 @property
def filename(self): def filename(self):
return self._filename 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): class sequencer(_Patch):
def __init__(self, chan, num_tracks, num_steps): def __init__(self, chan, num_tracks, num_steps):
super().__init__(chan) super().__init__(chan)
self.num_steps = num_steps self.plugins.seq = chan.new(bl00mbox.plugins.sequencer, num_tracks, 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, init_var)
self.signals.bpm = self.plugins.seq.signals.bpm self.signals.bpm = self.plugins.seq.signals.bpm
self.signals.beat_div = self.plugins.seq.signals.beat_div self.signals.beat_div = self.plugins.seq.signals.beat_div
self.signals.step = self.plugins.seq.signals.step self.signals.step = self.plugins.seq.signals.step
self.signals.step_end = self.plugins.seq.signals.step_end 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.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) tracktable = [-32767] + ([0] * self.num_steps)
self.plugins.seq.table = tracktable * self.num_tracks self.plugins.seq.table = tracktable * self.num_tracks
...@@ -169,11 +294,12 @@ class sequencer(_Patch): ...@@ -169,11 +294,12 @@ class sequencer(_Patch):
) )
ret += ( ret += (
" step: " " 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]" ret += "\n [tracks]"
"""
for x, seq in enumerate(self.seqs): for x, seq in enumerate(self.seqs):
ret += ( ret += (
"\n " "\n "
...@@ -182,6 +308,7 @@ class sequencer(_Patch): ...@@ -182,6 +308,7 @@ class sequencer(_Patch):
+ "".join(["X " if x > 0 else ". " for x in seq.table[1:]]) + "".join(["X " if x > 0 else ". " for x in seq.table[1:]])
+ "]" + "]"
) )
"""
ret += "\n" + "\n".join(super().__repr__().split("\n")[1:]) ret += "\n" + "\n".join(super().__repr__().split("\n")[1:])
return ret return ret
...@@ -215,6 +342,7 @@ class sequencer(_Patch): ...@@ -215,6 +342,7 @@ class sequencer(_Patch):
class fuzz(_Patch): class fuzz(_Patch):
# DEPRECATED
def __init__(self, chan): def __init__(self, chan):
super().__init__(chan) super().__init__(chan)
self.plugins.dist = chan.new(bl00mbox.plugins._distortion) self.plugins.dist = chan.new(bl00mbox.plugins._distortion)
...@@ -278,27 +406,28 @@ class karplus_strong(_Patch): ...@@ -278,27 +406,28 @@ class karplus_strong(_Patch):
self.plugins.flanger = chan._new_plugin(bl00mbox.plugins.flanger) 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.manual.tone = "A2"
self.plugins.flanger.signals.input = self.plugins.noise.signals.output self.plugins.flanger.signals.input = self.plugins.noise.signals.output
self.signals.trigger = self.plugins.noise.signals.trigger 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.pitch = self.plugins.flanger.signals.manual
self.signals.output = self.plugins.flanger.signals.output self.signals.output = self.plugins.flanger.signals.output
self.signals.level = self.plugins.flanger.signals.level 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 @property
def decay(self): def decay(self):
return self._decay # deprecated
return self.plugins.flanger.signals.decay.value
@decay.setter @decay.setter
def decay(self, val): def decay(self, val):
tone = self.plugins.flanger.signals.manual.tone # deprecated
loss = (50 * (2 ** (-tone / 12))) // (val / 1000) self.plugins.flanger.signals.resonance = -2
if loss < 2: self.plugins.flanger.signals.decay = int(val)
loss = 2
self.plugins.flanger.signals.resonance = 32767 - loss
self._decay = 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;
}