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

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
Show changes
Showing
with 1615 additions and 0 deletions
# documentation
https://moon2embeddedaudio.gitlab.io/bl00mbox/main/
//SPDX-License-Identifier: CC0-1.0
#include "bl00mbox.h"
#include "bl00mbox_plugin_registry.h"
#include "bl00mbox_audio.h"
void bl00mbox_init(){
bl00mbox_plugin_registry_init();
bl00mbox_audio_init();
}
//SPDX-License-Identifier: CC0-1.0
#include "bl00mbox_audio.h"
#include "bl00mbox_user.h"
#include "bl00mbox_os.h"
#include "bl00mbox_channel_plugin.h"
#include <assert.h>
static uint16_t full_buffer_len;
static uint32_t render_pass_id;
int16_t * bl00mbox_line_in_interlaced = NULL;
static bl00mbox_lock_t user_lock = NULL;
// grab user_lock for r/w
static bl00mbox_set_t * all_chans = NULL;
// grab user_lock for r/w
static bl00mbox_set_t * background_mute_override_chans = NULL;
// grab user_lock for r/w
static bl00mbox_channel_t * foreground_chan = NULL;
// grab both user_lock and active_chans_lock for writing.
// for user-side reading grab user_lock.
// render task reads with active_chans_lock.
static bl00mbox_array_t * active_chans = NULL;
static bl00mbox_lock_t active_chans_lock = NULL;
static void update_active_chans(){
// must be called after changing foreground_chan or background_mute_override_chans
// while _still_ holding user_lock but not active_chans_lock
size_t num_chans = background_mute_override_chans->len;
if(foreground_chan) num_chans++;
bl00mbox_array_t * new_active_chans = malloc(sizeof(bl00mbox_array_t) + num_chans * sizeof(void *));
if(new_active_chans){
size_t index = 0;
if(foreground_chan){
new_active_chans->elems[index] = foreground_chan;
index++;
}
bl00mbox_set_iter_t iter;
bl00mbox_set_iter_start(&iter, background_mute_override_chans);
bl00mbox_channel_t * chan;
while((chan = bl00mbox_set_iter_next(&iter))){
if(chan == foreground_chan) continue;
new_active_chans->elems[index] = chan;
index++;
}
new_active_chans->len = index;
} else {
bl00mbox_log_error("out of memory");
}
#ifdef BL00MBOX_DEBUG
if(new_active_chans) bl00mbox_log_error("active chans: %d", (int) new_active_chans->len);
#endif
bl00mbox_array_t * previous_active_chans;
bl00mbox_take_lock(&active_chans_lock);
previous_active_chans = active_chans;
active_chans = new_active_chans;
bl00mbox_give_lock(&active_chans_lock);
free(previous_active_chans);
}
bl00mbox_array_t * bl00mbox_collect_channels(bool active){
bl00mbox_array_t * ret = NULL;
bl00mbox_take_lock(&user_lock);
if(active){
if(active_chans){
size_t ret_size = sizeof(bl00mbox_array_t);
ret_size += active_chans->len * sizeof(void *);
ret = malloc(ret_size);
if(ret) memcpy(ret, active_chans, ret_size);
} else {
ret = malloc(sizeof(bl00mbox_array_t));
if(ret) ret->len = 0;
}
} else {
ret = bl00mbox_set_to_array(all_chans);
}
bl00mbox_give_lock(&user_lock);
if(!ret) bl00mbox_log_error("out of memory");
return ret;
}
void bl00mbox_channel_set_background_mute_override(bl00mbox_channel_t * chan, bool enable){
bl00mbox_take_lock(&user_lock);
chan->background_mute_override = enable;
bool update;
if(enable){
update = bl00mbox_set_add(background_mute_override_chans, chan);
} else {
update = bl00mbox_set_remove(background_mute_override_chans, chan);
}
if(update) update_active_chans();
bl00mbox_give_lock(&user_lock);
}
bool bl00mbox_channel_get_foreground(bl00mbox_channel_t * chan){
return foreground_chan == chan;
}
void bl00mbox_channel_set_foreground(bl00mbox_channel_t * chan, bool enable){
if(bl00mbox_channel_get_foreground(chan) == enable) return;
bl00mbox_take_lock(&user_lock);
foreground_chan = enable ? chan : NULL;
update_active_chans();
bl00mbox_give_lock(&user_lock);
}
void bl00mbox_channel_event(bl00mbox_channel_t * chan){
#ifdef BL00MBOX_AUTO_FOREGROUNDING
bl00mbox_channel_set_foreground(chan, true);
#endif
}
bl00mbox_channel_t * bl00mbox_channel_create(){
bl00mbox_channel_t * chan = calloc(1, sizeof(bl00mbox_channel_t));
if(!chan) goto failed;
chan->volume = BL00MBOX_DEFAULT_CHANNEL_VOLUME;
chan->sys_gain = 4096;
// must be destroyed manually as it's not in the plugin list
chan->channel_plugin = bl00mbox_plugin_create_unlisted(chan, &bl00mbox_channel_plugin_desc, 0);
if(!chan->channel_plugin) goto failed;
if(!bl00mbox_create_lock(&chan->render_lock)) goto failed;
bl00mbox_take_lock(&user_lock);
if(!bl00mbox_set_add_skip_unique_check(all_chans, chan)) goto failed;
foreground_chan = chan;
update_active_chans();
bl00mbox_give_lock(&user_lock);
return chan;
failed:
if(chan){
if(chan->channel_plugin){
// supress errors
chan->channel_plugin->parent_self_ref = &chan->channel_plugin;
bl00mbox_plugin_destroy(chan->channel_plugin);
}
if(chan->render_lock) bl00mbox_delete_lock(&chan->render_lock);
free(chan);
}
bl00mbox_log_error("channel allocation failed");
return NULL;
}
void bl00mbox_channel_clear(bl00mbox_channel_t * chan){
// note: this does NOT destroy the channel_plugin as it is unlisted
bl00mbox_set_iter_t iter;
bl00mbox_set_iter_start(&iter, &chan->plugins);
bl00mbox_plugin_t * plugin;
while((plugin = bl00mbox_set_iter_next(&iter))){
bl00mbox_plugin_destroy(plugin);
}
chan->plugin_id = 0;
}
void bl00mbox_channel_destroy(bl00mbox_channel_t * chan){
bl00mbox_channel_clear(chan);
// remove from all lists
bl00mbox_take_lock(&user_lock);
bool is_active = false;
if(foreground_chan == chan){
foreground_chan = NULL;
is_active = true;
}
if(bl00mbox_set_remove(background_mute_override_chans, chan)){
is_active = true;
}
if(is_active) update_active_chans();
bl00mbox_set_remove(all_chans, chan);
bl00mbox_give_lock(&user_lock);
// remove from parent
if(* (chan->parent_self_ref) != chan){
bl00mbox_log_error("channel: parent_self_ref improper: channel %p, ref %p",
chan, * (chan->parent_self_ref));
}
* (chan->parent_self_ref) = NULL;
// remove from weak parent
if(chan->weak_parent_self_ref){
if(* (chan->weak_parent_self_ref) != chan){
bl00mbox_log_error("channel: weak_parent_self_ref improper: channel %p, ref %p",
chan, * (chan->weak_parent_self_ref));
}
* (chan->weak_parent_self_ref) = NULL;
}
#ifdef BL00MBOX_DEBUG
if(chan->connections.len) bl00mbox_log_error("connections nonempty");
if(chan->roots.len) bl00mbox_log_error("roots nonempty");
if(chan->always_render.len) bl00mbox_log_error("always render nonempty");
if(chan->plugins.len) bl00mbox_log_error("plugins nonempty");
bl00mbox_wait();
#endif
// be really sure that nobody else holds the lock. the renderer
// doesn't at this point, but if there's multiple tasks running
// clients there may be collisions.
// since the client api is generally not thread safe at this point
// it's okay, we can add the feature easily by just wrapping _all_
// client api in a lock at some point in the future.
bl00mbox_delete_lock(&chan->render_lock);
if(chan->channel_plugin) bl00mbox_plugin_destroy(chan->channel_plugin);
free(chan->render_plugins);
free(chan->render_buffers);
free(chan->name);
free(chan);
}
void bl00mbox_audio_plugin_render(bl00mbox_plugin_t * plugin){
if(plugin->render_pass_id == render_pass_id) return;
#ifdef BL00MBOX_LOOPS_ENABLE
if(plugin->is_being_rendered) return;
#endif
plugin->is_being_rendered = true;
plugin->rugin->render(plugin->rugin, full_buffer_len, render_pass_id);
plugin->render_pass_id = render_pass_id;
plugin->is_being_rendered = false;
}
static bool _bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t * out, bool adding){
chan->render_pass_id = render_pass_id;
int32_t vol = radspa_mult_shift(chan->volume, chan->sys_gain);
if(!vol) return false; // don't render if muted
// render these even if nothing is plugged into mixer
if(chan->render_plugins){
for(size_t i = 0; i < chan->render_plugins->len; i++){
bl00mbox_audio_plugin_render(chan->render_plugins->elems[i]);
}
}
bl00mbox_channel_plugin_update_values(chan->channel_plugin->rugin, render_pass_id);
if(!(chan->render_buffers && chan->render_buffers->len)) return false;
int32_t acc[full_buffer_len];
// first one non-adding
int16_t * buffer = chan->render_buffers->elems[0];
if(buffer[1] == -32768){
for(size_t i = 0; i < full_buffer_len; i++){
acc[i] = buffer[0];
}
} else {
for(size_t i = 0; i < full_buffer_len; i++){
acc[i] = buffer[i];
}
}
// rest adding
for(size_t i = 1; i < chan->render_buffers->len; i++){
buffer = chan->render_buffers->elems[i];
if(buffer[1] == -32768){
if(buffer[0]){
for(size_t i = 0; i < full_buffer_len; i++){
acc[i] += buffer[0];
}
}
} else {
for(size_t i = 0; i < full_buffer_len; i++){
acc[i] += buffer[i];
}
}
}
for(uint16_t i = 0; i < full_buffer_len; i++){
// flip around for rounding towards zero/mulsh boost
int invert = chan->dc < 0 ? -1 : 1;
chan->dc = chan->dc * invert;
chan->dc = ((uint64_t) chan->dc * (((1<<12) - 1)<<20)) >> 32;
chan->dc = chan->dc * invert;
chan->dc += acc[i];
acc[i] -= (chan->dc >> 12);
}
if(adding){
for(uint16_t i = 0; i < full_buffer_len; i++){
out[i] = radspa_add_sat(radspa_gain(acc[i], vol), out[i]);
}
} else {
for(uint16_t i = 0; i < full_buffer_len; i++){
out[i] = radspa_gain(acc[i], vol);
}
}
if(chan->compute_rms){
for(uint16_t i = 0; i < full_buffer_len; i++){
int32_t sq = acc[i];
sq = (sq * sq) - chan->mean_square;
// always round down with negative sq so that decay always works.
// bitshift instead of div does that for us nicely.
// cannot underflow as ((-a) >> 11) can never be less than -a.
chan->mean_square += sq >> 11;
}
}
return true;
}
static bool bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t * out, bool adding){
if(render_pass_id == chan->render_pass_id) return false;
bl00mbox_take_lock(&chan->render_lock);
bool ret = _bl00mbox_audio_channel_render(chan, out, adding);
bl00mbox_give_lock(&chan->render_lock);
// null it out if nothing was rendered
chan->mean_square *= ret;
return ret;
}
void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len){
full_buffer_len = len/2;
bl00mbox_line_in_interlaced = rx;
int16_t acc[full_buffer_len];
bool acc_init = false;
bl00mbox_take_lock(&active_chans_lock);
render_pass_id++;
if(active_chans){
for(size_t i = 0; i < active_chans->len; i++){
acc_init = bl00mbox_audio_channel_render(active_chans->elems[i], acc, acc_init) || acc_init;
}
}
bl00mbox_give_lock(&active_chans_lock);
if(acc_init){
for(uint16_t i = 0; i < full_buffer_len; i++){
tx[2*i] = acc[i];
tx[2*i+1] = acc[i];
}
} else {
memset(tx, 0, len * sizeof(int16_t));
}
}
void bl00mbox_audio_init(){
assert(bl00mbox_create_lock(&active_chans_lock));
// micropython objects are generally not thread safe, so for most of the user API we need
// not care about locking after they've been created. however, bl00mbox Channel objects may
// be created by different threads (i.e., thread API), so these sets must be fully thread safe.
assert(bl00mbox_create_lock(&user_lock));
all_chans = calloc(1, sizeof(bl00mbox_set_t));
assert(all_chans);
background_mute_override_chans = calloc(1, sizeof(bl00mbox_set_t));
assert(background_mute_override_chans);
}
#include "bl00mbox_containers.h"
static bool equals(void * some, void * other, bl00mbox_set_key_t key){
if(!key) return some == other;
return key(some, other);
}
static bool set_add_inner(bl00mbox_set_t * set, void * content){
bl00mbox_ll_t * ll = malloc(sizeof(bl00mbox_ll_t));
if(!ll) return false;
ll->content = content;
ll->next = set->start;
set->start = ll;
set->len++;
return true;
}
bool bl00mbox_set_contains(bl00mbox_set_t * set, void * content){
if(!content) return false; // NULL pointers can't be in set
if(!set->start) return false;
bl00mbox_ll_t * seek = set->start;
while(seek){
if(equals(seek->content, content, set->key)) break;
seek = seek->next;
}
return seek;
}
bool bl00mbox_set_add_skip_unique_check(bl00mbox_set_t * set, void * content){
if(!content) return false; // don't allow for NULL pointers in set
#ifdef BL00MBOX_DEBUG
if(bl00mbox_set_contains(set, content)){
bl00mbox_log_error("set corrupted");
return false;
}
#endif
return set_add_inner(set, content);
}
bool bl00mbox_set_add(bl00mbox_set_t * set, void * content){
if(bl00mbox_set_contains(set, content)) return false;
return set_add_inner(set, content);
}
bool bl00mbox_set_remove(bl00mbox_set_t * set, void * content){
if(!content) return false;
bl00mbox_ll_t * seek = set->start;
bl00mbox_ll_t * prev = NULL;
while(seek){
if(equals(seek->content, content, set->key)) break;
prev = seek;
seek = seek->next;
}
if(seek){
bl00mbox_ll_t ** target = prev ? &(prev->next) : &(set->start);
(* target) = seek->next;
set->len--;
}
free(seek);
return true;
}
void bl00mbox_set_iter_start(bl00mbox_set_iter_t * iter, bl00mbox_set_t * set){
iter->next = set->start;
}
void * bl00mbox_set_iter_next(bl00mbox_set_iter_t * iter){
if(!iter->next) return NULL;
void * ret = iter->next->content;
iter->next = iter->next->next;
return ret;
}
bl00mbox_array_t * bl00mbox_set_to_array(bl00mbox_set_t * set){
bl00mbox_array_t * ret = malloc(sizeof(bl00mbox_array_t) + set->len * sizeof(void *));
if(!ret) return NULL;
ret->len = set->len;
bl00mbox_set_iter_t iter;
bl00mbox_set_iter_start(&iter, set);
void * content;
size_t index = 0;
while((content = bl00mbox_set_iter_next(&iter))){
ret->elems[index++] = content;
}
return ret;
}
#include "bl00mbox_os.h"
#ifdef BL00MBOX_FREERTOS
bool bl00mbox_create_lock(bl00mbox_lock_t * lock){
assert(*lock == NULL);
* lock = xSemaphoreCreateMutex();
return(*lock != NULL);
}
void bl00mbox_delete_lock(bl00mbox_lock_t * lock){ vSemaphoreDelete(* lock); }
void bl00mbox_take_lock(bl00mbox_lock_t * lock){ xSemaphoreTake(* lock, portMAX_DELAY); }
void bl00mbox_give_lock(bl00mbox_lock_t * lock){ xSemaphoreGive(* lock); }
#ifdef BL00MBOX_DEBUG
void bl00mbox_wait() { vTaskDelay(10); }
#endif
#endif
#if defined(BL00MBOX_ESPIDF)
// macros in header
#elif defined(BL00MBOX_PRINTF)
#include <stdarg.h>
#include <string.h>
static void _bl00mbox_log(char * pre, char * txt, va_list args){
int len = strlen(pre) + strlen(txt) + 2;
char msg[len];
snprintf(msg, len, "%s%s\n", pre, txt);
vprintf(msg, args);
}
void bl00mbox_log_error(char * txt, ...){
va_list args;
va_start(args, txt);
_bl00mbox_log("bl00mbox error: ", txt, args);
va_end(args);
};
void bl00mbox_log_info(char * txt, ...){
va_list args;
va_start(args, txt);
_bl00mbox_log("bl00mbox info: ", txt, args);
va_end(args);
};
#else
void bl00mbox_log_error(char * txt, ...){};
void bl00mbox_log_info(char * txt, ...){};
#endif
//SPDX-License-Identifier: CC0-1.0
#include "bl00mbox_plugin_registry.h"
static bl00mbox_plugin_registry_t * bl00mbox_plugin_registry = NULL;
static uint16_t bl00mbox_plugin_registry_len = 0;
static bool bl00mbox_plugin_registry_is_initialized = false;
static void plugin_add(radspa_descriptor_t * descriptor){
if(descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID){
bl00mbox_log_error("plugin list id collision");
}
if(bl00mbox_plugin_registry_len == 65535){
bl00mbox_log_error("too many plugins registered");
return;
}
// create plugin registry entry
bl00mbox_plugin_registry_t * p = malloc(sizeof(bl00mbox_plugin_registry_t));
if(!p){
bl00mbox_log_error("no memory for plugin list");
return;
}
p->descriptor = descriptor;
p->next = NULL;
// go to end of list
bl00mbox_plugin_registry_t * plast = bl00mbox_plugin_registry;
if(plast == NULL){
bl00mbox_plugin_registry = p;
} else {
while(plast->next != NULL){
if(plast->descriptor->id == p->descriptor->id){
bl00mbox_log_error("plugin list id collision");
return;
}
plast = plast->next;
}
plast->next = p;
}
bl00mbox_plugin_registry_len++;
}
uint16_t bl00mbox_plugin_registry_get_plugin_num(void){
return bl00mbox_plugin_registry_len;
}
radspa_descriptor_t * bl00mbox_plugin_registry_get_descriptor_from_id(uint32_t id){
/// searches plugin registry for first descriptor with given id number
/// and returns pointer to it. returns NULL if no match is found.
bl00mbox_plugin_registry_t * p = bl00mbox_plugin_registry;
while(p != NULL){
if(p->descriptor->id == id) break;
p = p->next;
}
if(p != NULL) return p->descriptor;
return NULL;
}
radspa_descriptor_t * bl00mbox_plugin_registry_get_descriptor_from_index(uint32_t index){
/// returns pointer to descriptor of registry entry at given index.
/// returns NULL if out of range.
if(index >= bl00mbox_plugin_registry_len) return NULL;
bl00mbox_plugin_registry_t * p = bl00mbox_plugin_registry;
for(uint16_t i = 0; i < index; i++){
p = p->next;
if(p == NULL){
bl00mbox_log_error("bl00mbox: plugin list length error");
abort();
}
}
return p->descriptor;
}
radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index){
/// returns pointer to descriptor of registry entry at given index.
/// returns NULL if out of range.
if(index >= bl00mbox_plugin_registry_len) return NULL;
bl00mbox_plugin_registry_t * p = bl00mbox_plugin_registry;
for(uint16_t i = 0; i < index; i++){
p = p->next;
if(p == NULL){
bl00mbox_log_error("bl00mbox: plugin list length error");
abort();
}
}
return p->descriptor;
}
/* REGISTER PLUGINS HERE!
* - include .c file to SRCS in bl00mbox/CMakeLists.txt
* - include .h file directory to INCLUDE_DIRS in bl00mbox/CMakeLists.txt
* - include .h file below
* - use plugin_add in bl00mbox_plugin_registry_init as
* exemplified below
*
* NOTE: the plugin registry linked list is intended to be filled once at
* boot time. dynamically adding plugins at runtime may or may not work,
* removing plugins from the registry at runtime is not intended.
*/
#include "osc_fm.h"
#include "osc.h"
#include "env_adsr.h"
#include "ampliverter.h"
#include "delay.h"
#include "lowpass.h"
#include "filter.h"
#include "sequencer.h"
#include "sampler.h"
#include "flanger.h"
#include "noise.h"
#include "noise_burst.h"
#include "distortion.h"
#include "mixer.h"
#include "multipitch.h"
#include "trigger_merge.h"
#include "slew_rate_limiter.h"
#include "range_shifter.h"
#include "poly_squeeze.h"
#include "bl00mbox_line_in.h"
#include "buffer.h"
void bl00mbox_plugin_registry_init(void){
if(bl00mbox_plugin_registry_is_initialized) return;
// do not add bl00mbox_channel_plugin, it is not to be spawned by users
plugin_add(&osc_desc);
plugin_add(&filter_desc);
plugin_add(&sequencer_desc);
plugin_add(&sampler_desc);
plugin_add(&multipitch_desc);
plugin_add(&trigger_merge_desc);
plugin_add(&bl00mbox_line_in_desc);
plugin_add(&buffer_desc);
plugin_add(&distortion_desc);
plugin_add(&mixer_desc);
plugin_add(&flanger_desc);
plugin_add(&noise_desc);
plugin_add(&noise_burst_desc);
plugin_add(&env_adsr_desc);
plugin_add(&delay_desc);
plugin_add(&range_shifter_desc);
plugin_add(&poly_squeeze_desc);
plugin_add(&slew_rate_limiter_desc);
plugin_add(&ampliverter_desc);
plugin_add(&osc_fm_desc);
plugin_add(&lowpass_desc);
}
//SPDX-License-Identifier: CC0-1.0
#include "bl00mbox_radspa_requirements.h"
bool radspa_host_request_buffer_render(int16_t * buf){
bl00mbox_plugin_t * plugin = ((bl00mbox_connection_t *) buf)->source.plugin;
bl00mbox_audio_plugin_render(plugin);
return 1;
}
// py: bigtable = [int(22/200*(2**(14-5+8+x*4096/2400/64))) for x in range(64)]
// for 48kHz main sample rate
static const uint16_t bigtable[64] = {
14417, 14686, 14960, 15240, 15524, 15813, 16108, 16409,
16715, 17027, 17345, 17668, 17998, 18334, 18676, 19024,
19379, 19741, 20109, 20484, 20866, 21255, 21652, 22056,
22467, 22887, 23313, 23748, 24191, 24643, 25103, 25571,
26048, 26534, 27029, 27533, 28047, 28570, 29103, 29646,
30199, 30763, 31336, 31921, 32517, 33123, 33741, 34371,
35012, 35665, 36330, 37008, 37699, 38402, 39118, 39848,
40592, 41349, 42120, 42906, 43706, 44522, 45352, 46199
};
// py: smoltable = [int(22/240*(2**(15-5+9+x*4096/2400/64/64))) for x in range(64)]
// for 48kHz main sample rate
static const uint16_t smoltable[64] = {
48059, 48073, 48087, 48101, 48115, 48129, 48143, 48156,
48170, 48184, 48198, 48212, 48226, 48240, 48254, 48268,
48282, 48296, 48310, 48324, 48338, 48352, 48366, 48380,
48394, 48407, 48421, 48435, 48449, 48463, 48477, 48491,
48505, 48519, 48533, 48548, 48562, 48576, 48590, 48604,
48618, 48632, 48646, 48660, 48674, 48688, 48702, 48716,
48730, 48744, 48758, 48772, 48786, 48801, 48815, 48829,
48843, 48857, 48871, 48885, 48899, 48913, 48928, 48942,
};
uint32_t radspa_sct_to_rel_freq(int16_t sct, int16_t undersample_pow){
/// returns approx. proportional to 2**((sct/2400) + undersample_pow) so that
/// a uint32_t accumulator overflows at 440Hz with sct = INT16_MAX - 6*2400
/// when sampled at (48>>undersample_pow)kHz
// compiler explorer says this is 33 instructions with O2. might be alright?
uint32_t a = sct;
a = sct + 28*2400 - 32767 - 330;
// at O2 u get free division for each modulo. still slow, 10 instructions or so.
int16_t octa = a / 2400;
a = a % 2400;
uint8_t bigindex = a / 64;
uint8_t smolindex = a % 64;
uint32_t ret = 2; //weird but trust us
ret *= bigtable[bigindex];
ret *= smoltable[smolindex];
int16_t shift = 27 - octa - undersample_pow;
if(shift > 0){
ret = ret >> shift;
}
return ret;
}
int16_t radspa_random(){ return xoroshiro64star()>>16; }
//SPDX-License-Identifier: CC0-1.0
#include "bl00mbox_user.h"
extern void bl00mbox_disconnect_rx_callback(void * rx, uint16_t signal_index);
extern void bl00mbox_connect_rx_callback(void * rx, void * tx, uint16_t signal_index);
extern void bl00mbox_disconnect_mx_callback(void * chan, void * tx);
extern void bl00mbox_connect_mx_callback(void * chan, void * tx);
// get signal struct from a signal index
radspa_signal_t * bl00mbox_signal_get_by_index(radspa_t * plugin, uint16_t signal_index){
return &(plugin->signals[signal_index]);
}
static inline bl00mbox_connection_t * conn_from_signal(bl00mbox_signal_t * signal){
return (bl00mbox_connection_t *) signal->rignal->buffer;
}
static bool signal_is_input(bl00mbox_signal_t * signal){
return signal->rignal->hints & RADSPA_SIGNAL_HINT_INPUT ? true : false;
}
static bool signal_is_output(bl00mbox_signal_t * signal){
return signal->rignal->hints & RADSPA_SIGNAL_HINT_OUTPUT ? true : false;
}
static bool signal_equals(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
return (some->plugin == other->plugin) && (some->index == other->index);
}
static bool signals_are_connected(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
if(!some->rignal->buffer) return false;
return some->rignal->buffer == other->rignal->buffer;
}
bool bl00mbox_signal_is_output(bl00mbox_signal_t * signal){
return signal_is_output(signal);
}
bool bl00mbox_signal_is_input(bl00mbox_signal_t * signal){
return signal_is_input(signal);
}
bl00mbox_connection_t * bl00mbox_connection_from_signal(bl00mbox_signal_t * signal){
return conn_from_signal(signal);
}
uint16_t bl00mbox_channel_plugins_num(bl00mbox_channel_t * chan){
return chan->plugins.len;
}
bl00mbox_array_t * bl00mbox_channel_collect_plugins(bl00mbox_channel_t * chan){
unsigned int len = chan->plugins.len;
bl00mbox_array_t * ret;
ret = malloc(sizeof(bl00mbox_array_t) + len * sizeof(void *));
if(!ret) return NULL;
ret->len = len;
bl00mbox_set_iter_t iter;
bl00mbox_set_iter_start(&iter, &chan->plugins);
bl00mbox_plugin_t * plugin;
size_t i = 0;
while((plugin = bl00mbox_set_iter_next(&iter))) ret->elems[i++] = plugin;
return ret;
}
uint16_t bl00mbox_channel_conns_num(bl00mbox_channel_t * chan){
return chan->connections.len;
}
uint16_t bl00mbox_channel_mixer_num(bl00mbox_channel_t * chan){
return chan->roots.len;
}
bl00mbox_array_t * bl00mbox_channel_collect_connections_mx(bl00mbox_channel_t * chan){
bl00mbox_array_t * ret;
ret = malloc(sizeof(bl00mbox_array_t) + chan->roots.len * sizeof(void *));
if(!ret) return NULL;
ret->len = chan->roots.len;
bl00mbox_set_iter_t iter;
bl00mbox_set_iter_start(&iter, &chan->roots);
bl00mbox_connection_t * conn;
size_t i = 0;
while((conn = bl00mbox_set_iter_next(&iter))){
ret->elems[i++] = &conn->source;
}
return ret;
}
bl00mbox_array_t * bl00mbox_signal_collect_connections(bl00mbox_signal_t * signal){
// caller has to free memory
// return NULL -> OOM error
bl00mbox_array_t * ret = NULL;
bl00mbox_connection_t * conn = conn_from_signal(signal);
if(!conn){
ret = malloc(sizeof(bl00mbox_array_t));
if(!ret) return NULL;
ret->len = 0;
} else if(signal->rignal->hints & RADSPA_SIGNAL_HINT_INPUT){
ret = malloc(sizeof(bl00mbox_array_t) + sizeof(void *));
if(!ret) return NULL;
ret->len = 1;
ret->elems[0] = &conn->source;
} else {
// TODO: add sentinel for channel mixer connection
ret = malloc(sizeof(bl00mbox_array_t) + conn->subscribers.len * sizeof(void *));
if(!ret) return NULL;
ret->len = conn->subscribers.len;
bl00mbox_set_iter_t iter;
bl00mbox_set_iter_start(&iter, &conn->subscribers);
bl00mbox_signal_t * sub;
size_t i = 0;
while((sub = bl00mbox_set_iter_next(&iter))){
ret->elems[i++] = sub;
}
}
return ret;
}
static void update_roots(bl00mbox_channel_t * chan){
// create list of plugins to be rendered, i.e. all roots and always_actives
bl00mbox_set_t * roots = &chan->roots; // content: bl00mbox_connection_t
bl00mbox_set_t * always_render = &chan->always_render; // content: bl00mbox_plugin_t
radspa_signal_t * output_rignal = &chan->channel_plugin->rugin->signals[1];
// render_buffers are simple, just copy the content of the set and add the output plugin manually
int num_render_buffers = roots->len + (output_rignal->buffer ? 1 : 0);
bl00mbox_array_t * render_buffers = malloc(sizeof(bl00mbox_array_t) + sizeof(void *) * num_render_buffers);
// for render_plugins there may be duplicates. we still allocate full memory for now
int num_render_plugins = roots->len + always_render->len + (output_rignal->buffer ? 1 : 0);
bl00mbox_array_t * render_plugins = malloc(sizeof(bl00mbox_array_t) + sizeof(void *) * num_render_plugins);
if(!(render_buffers && render_plugins)) goto defer;
size_t index = 0;
bl00mbox_set_iter_t iter;
// filling up mixer roots
bl00mbox_set_iter_start(&iter, roots);
bl00mbox_connection_t * conn;
while((conn = bl00mbox_set_iter_next(&iter))){
render_buffers->elems[index] = conn->buffer;
render_plugins->elems[index] = conn->source.plugin;
index++;
}
render_buffers->len = index;
// if someone is connected to the channel_plugin output:
// add to render_buffers/render_plugin
if(output_rignal->buffer){
render_buffers->elems[index] = output_rignal->buffer;
render_buffers->len++;
conn = output_rignal->buffer;
bl00mbox_plugin_t * plugin = conn->source.plugin;
bool is_duplicate = false;
for(size_t rindex = 0; rindex < index; rindex++){
if(render_plugins->elems[rindex] == plugin){
is_duplicate = true;
break;
}
}
if(!is_duplicate){
render_plugins->elems[index++] = conn->source.plugin;
}
}
// adding always_render to the plugin list. those should be after the regular roots
// because we might mess with natural render order otherwise
size_t duplicate_index = index;
bl00mbox_set_iter_start(&iter, always_render);
bl00mbox_plugin_t * plugin;
while((plugin = bl00mbox_set_iter_next(&iter))){
// check for duplicates
bool is_duplicate = false;
for(size_t rindex = 0; rindex < duplicate_index; rindex++){
if(render_plugins->elems[rindex] == plugin){
is_duplicate = true;
break;
}
}
if(!is_duplicate) render_plugins->elems[index++] = plugin;
}
render_plugins->len = index;
// if we overshot let's trim excess memory.
if(index != num_render_plugins){
bl00mbox_array_t * tmp = realloc(render_plugins, sizeof(bl00mbox_array_t) + sizeof(void *) * index);
if(tmp) render_plugins = tmp;
}
defer:
if(!(render_plugins && render_buffers)){
free(render_plugins);
free(render_buffers);
render_plugins = NULL;
render_buffers = NULL;
bl00mbox_log_error("out of memory, render list cleared")
}
#ifdef BL00MBOX_DEBUG
else {
bl00mbox_log_info("new render data, %d plugins, %d buffers",
(int) render_plugins->len, (int) render_buffers->len);
}
#endif
bl00mbox_array_t * render_plugins_prev = chan->render_plugins;
bl00mbox_array_t * render_buffers_prev = chan->render_buffers;
bl00mbox_take_lock(&chan->render_lock);
chan->render_plugins = render_plugins;
chan->render_buffers = render_buffers;
bl00mbox_give_lock(&chan->render_lock);
free(render_plugins_prev);
free(render_buffers_prev);
}
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;
}
bl00mbox_channel_event(chan);
return plugin;
}
void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin){
bl00mbox_channel_t * chan = plugin->channel;
// remove from parent
if(* (plugin->parent_self_ref) != plugin){
bl00mbox_log_error("plugin: parent_self_ref improper: plugin %s, %p, ref %p",
plugin->rugin->descriptor->name, plugin, * (plugin->parent_self_ref));
}
* (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);
}
// pop from sets
bl00mbox_plugin_set_always_render(plugin, false);
bl00mbox_set_remove(&chan->plugins, plugin);
if(plugin == chan->channel_plugin) chan->channel_plugin = NULL;
plugin->rugin->descriptor->destroy_plugin_instance(plugin->rugin);
free(plugin);
}
static bl00mbox_connection_t * create_connection(bl00mbox_signal_t * source){
if(!signal_is_output(source)) return NULL;
bl00mbox_connection_t * ret = calloc(1, sizeof(bl00mbox_connection_t));
if(ret == NULL) return NULL;
ret->subscribers.key = signal_equals;
memcpy(&ret->source, source, sizeof(bl00mbox_signal_t));
if(!bl00mbox_set_add_skip_unique_check(&source->plugin->channel->connections, ret)){
free(ret);
return NULL;
}
bl00mbox_channel_t * chan = source->plugin->channel;
bl00mbox_take_lock(&chan->render_lock);
source->rignal->buffer = &ret->buffer;
bl00mbox_give_lock(&chan->render_lock);
return ret;
}
static bl00mbox_connection_t * weak_create_connection(bl00mbox_signal_t * source){
bl00mbox_connection_t * conn = conn_from_signal(source);
if(conn) return conn;
conn = create_connection(source);
return conn;
}
static bool weak_delete_connection(bl00mbox_connection_t * conn){
// are there still active connections?
if(conn->subscribers.len || conn->connected_to_mixer) return false;
bl00mbox_channel_t * chan = conn->source.plugin->channel;
// disconnect buffer from plugin
bl00mbox_take_lock(&chan->render_lock);
conn->source.rignal->buffer = NULL;
bl00mbox_give_lock(&chan->render_lock);
// remove from connections list
if(!bl00mbox_set_remove(&chan->connections, conn)) bl00mbox_log_error("connection list corruption");
#ifdef BL00MBOX_DEBUG
if(conn->subscribers.len) bl00mbox_log_error("subscribers nonempty");
#endif
free(conn);
return true;
}
bl00mbox_error_t bl00mbox_signal_connect_mx(bl00mbox_signal_t * signal){
if(!signal_is_output(signal)) return BL00MBOX_ERROR_INVALID_CONNECTION;
bl00mbox_channel_t * chan = signal->plugin->channel;
bl00mbox_connection_t * conn = weak_create_connection(signal);
if(!conn) goto failed;
// check if already connected
if(conn->connected_to_mixer) return BL00MBOX_ERROR_OK;
conn->connected_to_mixer = true;
if(!bl00mbox_set_add(&chan->roots, conn)) goto failed;
update_roots(chan);
bl00mbox_connect_mx_callback(chan->parent, signal->plugin->parent);
bl00mbox_channel_event(chan);
return BL00MBOX_ERROR_OK;
failed:
if(conn){
conn->connected_to_mixer = false;
weak_delete_connection(conn);
}
bl00mbox_log_error("couldn't connect to mixer");
return BL00MBOX_ERROR_OOM;
}
static void bl00mbox_signal_disconnect_mx(bl00mbox_signal_t * signal){
if(!signal_is_output(signal)) return;
bl00mbox_connection_t * conn = conn_from_signal(signal);
if(conn == NULL) return; //not connected
conn->connected_to_mixer = false;
bl00mbox_channel_t * chan = signal->plugin->channel;
bl00mbox_set_remove(&chan->roots, conn);
update_roots(chan);
bl00mbox_disconnect_mx_callback(chan->parent, signal->plugin->parent);
weak_delete_connection(conn);
}
static void bl00mbox_signal_disconnect_rx(bl00mbox_signal_t * signal){
if(!signal_is_input(signal)) return;
// try to get connection and return if not connected
bl00mbox_connection_t * conn = conn_from_signal(signal);
if(!conn) return;
bl00mbox_channel_t * chan = signal->plugin->channel;
bl00mbox_take_lock(&chan->render_lock);
signal->rignal->buffer = NULL;
bl00mbox_give_lock(&chan->render_lock);
if(signal->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) update_roots(chan);
bl00mbox_set_remove(&conn->subscribers, signal);
weak_delete_connection(conn);
bl00mbox_disconnect_rx_callback(signal->plugin->parent, signal->index);
}
void bl00mbox_signal_disconnect_tx(bl00mbox_signal_t * signal){
if(!signal_is_output(signal)) return;
bl00mbox_connection_t * conn = conn_from_signal(signal);
if(!conn) return;
// disconnect from mixer
if(conn->connected_to_mixer) bl00mbox_signal_disconnect_mx(signal);
// disconnect all subscribers
bl00mbox_set_iter_t iter;
bl00mbox_set_iter_start(&iter, &conn->subscribers);
bl00mbox_signal_t * sub;
while((sub = bl00mbox_set_iter_next(&iter))){
bl00mbox_signal_disconnect_rx(sub);
}
}
void bl00mbox_signal_disconnect(bl00mbox_signal_t * signal){
if(!conn_from_signal(signal)) return;
if(signal_is_input(signal)){
bl00mbox_signal_disconnect_rx(signal);
}
if(signal_is_output(signal)){
bl00mbox_signal_disconnect_tx(signal);
bl00mbox_signal_disconnect_mx(signal);
}
if(conn_from_signal(signal)) bl00mbox_log_error("connection persists after disconnect");
}
bl00mbox_error_t bl00mbox_signal_connect(bl00mbox_signal_t * some, bl00mbox_signal_t * other){
// are signals on the same channel?
if(some->plugin->channel != other->plugin->channel) return BL00MBOX_ERROR_INVALID_CONNECTION;
bl00mbox_channel_t * chan = some->plugin->channel;
// which one is input, which one is output?
bl00mbox_signal_t * signal_rx;
bl00mbox_signal_t * signal_tx;
if(signal_is_input(some) && signal_is_output(other)){
signal_rx = some;
signal_tx = other;
} else if(signal_is_input(other) && signal_is_output(some)){
signal_rx = other;
signal_tx = some;
} else {
return BL00MBOX_ERROR_INVALID_CONNECTION;
}
bl00mbox_connection_t * conn;
if(!(conn = conn_from_signal(signal_tx))){
if(!(conn = create_connection(signal_tx))) return BL00MBOX_ERROR_OOM;
} else {
if(signals_are_connected(signal_rx, signal_tx)) return BL00MBOX_ERROR_OK; // already connected
}
bl00mbox_signal_disconnect_rx(signal_rx);
bl00mbox_signal_t * sub;
sub = malloc(sizeof(bl00mbox_signal_t));
if(!sub){
weak_delete_connection(conn);
return BL00MBOX_ERROR_OOM;
}
memcpy(sub, signal_rx, sizeof(bl00mbox_signal_t));
bl00mbox_set_add(&conn->subscribers, sub);
// we must block here else we could access the buffer of a constant signal
// and think it's nonconst, which is bad
bl00mbox_take_lock(&chan->render_lock);
signal_rx->rignal->buffer = signal_tx->rignal->buffer;
bl00mbox_give_lock(&chan->render_lock);
if(signal_rx->plugin->rugin->descriptor->id == BL00MBOX_CHANNEL_PLUGIN_ID) update_roots(chan);
bl00mbox_connect_rx_callback(signal_rx->plugin->parent, signal_tx->plugin->parent, signal_rx->index);
bl00mbox_channel_event(chan);
return BL00MBOX_ERROR_OK;
}
void bl00mbox_plugin_set_always_render(bl00mbox_plugin_t * plugin, bool value){
bl00mbox_channel_t * chan = plugin->channel;
if(plugin->always_render == value) return;
plugin->always_render = value;
if(value){
bl00mbox_set_add(&chan->always_render, plugin);
} else {
bl00mbox_set_remove(&chan->always_render, plugin);
}
update_roots(chan);
bl00mbox_channel_event(chan);
}
bl00mbox_error_t bl00mbox_signal_set_value(bl00mbox_signal_t * signal, int value){
//while(signal->plugin->is_being_rendered) {};
if(!signal_is_input(signal)) return BL00MBOX_ERROR_INVALID_CONNECTION;
if(conn_from_signal(signal)) bl00mbox_signal_disconnect(signal);
signal->rignal->value = value < -32767 ? -32767 : (value > 32767 ? 32767 : value);
return BL00MBOX_ERROR_OK;
}
int16_t bl00mbox_signal_get_value(bl00mbox_signal_t * signal){
//while(signal->plugin->is_being_rendered) {};
return signal->rignal->buffer ? signal->rignal->buffer[0] : signal->rignal->value;
}
#pragma once
#define BL00MBOX_FLOW3R
//#define BL00MBOX_DEBUG
#ifdef BL00MBOX_FLOW3R
//#include "flow3r_bsp.h"
//#define BL00MBOX_MAX_BUFFER_LEN FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE
#define BL00MBOX_MAX_BUFFER_LEN 64
#define BL00MBOX_DEFAULT_CHANNEL_VOLUME 8192
#define BL00MBOX_AUTO_FOREGROUNDING
#define BL00MBOX_LOOPS_ENABLE
#define BL00MBOX_FREERTOS
#define BL00MBOX_ESPIDF
// note: this option relies on implementation details of the garbage
// collector, specifically the fact that it is stop-the-world and
// collects all unreachable objects it can find before it gives control
// back to micropython. changing it from stop-the-world to anything
// else is to our knowledge not possible without rewriting all mutators,
// so we consider this aspect future proof. also we see no reason to
// not collect all of them since sweeping is very fast, unless it is
// outsourced to a separate sweeping task, in which case this option
// may result in use-after-free, but we don't really see that happening
// anytime soon.
// if you want to use this option in such an environment however,
// setting a ChannelCore's callback to None before dropping all
// references to it solves the issue.
#define BL00MBOX_CALLBACKS_FUNSAFE
#endif
/* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org)
To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
See <http://creativecommons.org/publicdomain/zero/1.0/>. */
#include <stdint.h>
/* This is xoroshiro64* 1.0, our best and fastest 32-bit small-state
generator for 32-bit floating-point numbers. We suggest to use its
upper bits for floating-point generation, as it is slightly faster than
xoroshiro64**. It passes all tests we are aware of except for linearity
tests, as the lowest six bits have low linear complexity, so if low
linear complexity is not considered an issue (as it is usually the
case) it can be used to generate 32-bit outputs, too.
We suggest to use a sign test to extract a random Boolean value, and
right shifts to extract subsets of bits.
The state must be seeded so that it is not everywhere zero. */
static inline uint32_t rotl(const uint32_t x, int k) {
return (x << k) | (x >> (32 - k));
}
// mod1: state seed
static uint32_t s[2] = {420, 69};
//uint32_t next(void) {
uint32_t xoroshiro64star(void) {
// mod2: changed name to avoid namespace collisions.
const uint32_t s0 = s[0];
uint32_t s1 = s[1];
const uint32_t result = s0 * 0x9E3779BB;
s1 ^= s0;
s[0] = rotl(s0, 26) ^ s1 ^ (s1 << 9); // a, b
s[1] = rotl(s1, 13); // c
return result;
}
#pragma once
uint32_t xoroshiro64star(void);
//SPDX-License-Identifier: CC0-1.0
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define SAMPLE_RATE 48000
uint16_t bl00mbox_sources_count();
uint16_t bl00mbox_source_add(void* render_data, void* render_function);
void bl00mbox_source_remove(uint16_t index);
bool bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len);
void bl00mbox_init(void);
//SPDX-License-Identifier: CC0-1.0
#pragma once
#include "bl00mbox_config.h"
#include "bl00mbox_os.h"
#include "bl00mbox_containers.h"
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "radspa.h"
#include "radspa_helpers.h"
struct _bl00mbox_plugin_t;
struct _bl00mbox_connection_source_t;
struct _bl00mbox_channel_root_t;
struct _bl00mbox_channel_t;
extern int16_t * bl00mbox_line_in_interlaced;
// pointer is unique identifier, no memcpy of this!
typedef struct _bl00mbox_plugin_t{
radspa_t * rugin; // radspa plugin
char * name;
uint32_t id; // unique number in channel to for UI purposes
uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary
uint32_t init_var; // init var that was used for plugin creation
volatile bool is_being_rendered; // true if rendering the plugin is in progress, else false.
bool always_render;
struct _bl00mbox_channel_t * channel; // channel that owns the plugin
void * parent;
struct _bl00mbox_plugin_t ** parent_self_ref;
} bl00mbox_plugin_t;
// pointer is NOT unique identifier, memcpy allowed
typedef struct {
bl00mbox_plugin_t * plugin;
radspa_signal_t * rignal;
int index;
} bl00mbox_signal_t;
typedef struct _bl00mbox_connection_t{ //child of bl00mbox_ll_t
int16_t buffer[BL00MBOX_MAX_BUFFER_LEN]; // MUST stay on top of struct bc type casting! TODO: offsetof()
bl00mbox_signal_t source;
bl00mbox_set_t subscribers; // content: bl00mbox_signal_t;
bool connected_to_mixer; // legacy thing, don't wanna sentinel subsribers
} bl00mbox_connection_t;
// pointer is unique identifier, no memcpy of this!
typedef struct _bl00mbox_channel_t{
char * name;
int32_t volume;
bool background_mute_override;
// secondary gain that the channel user is supposed to leave untouched so that the OS
// can change gain too, for example for a system mixer. we still want to apply both in the
// same pass, so we keep it here.
int32_t sys_gain;
// whether to keep track of the rms volume of the channel. adds a bit of cpu load, best
// keep it off if not used.
bool compute_rms;
// we are storing the un-rooted value. this means that if you want the value in decibels
// you have to only do the log operation and can skip the root as you can express roots
// as divisions in the log domain which is much cheaper.
uint32_t mean_square;
// average output used for DC blocking.
int32_t dc;
uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary
uint32_t plugin_id; // used to give each plugin in channel a unique identifier
// 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;
void bl00mbox_audio_init();
bl00mbox_channel_t * bl00mbox_channel_create();
void bl00mbox_audio_plugin_render(bl00mbox_plugin_t * plugin);
bool bl00mbox_channel_get_foreground(bl00mbox_channel_t * chan);
void bl00mbox_channel_set_foreground(bl00mbox_channel_t * chan, bool enable);
void bl00mbox_channel_set_background_mute_override(bl00mbox_channel_t * chan, bool enable);
void bl00mbox_channel_clear(bl00mbox_channel_t * chan);
void bl00mbox_channel_destroy(bl00mbox_channel_t * chan);
void bl00mbox_channel_event(bl00mbox_channel_t * channel);
bl00mbox_array_t * bl00mbox_collect_channels(bool active);
#pragma once
#include "bl00mbox_os.h"
typedef struct _bl00mbox_ll_t {
struct _bl00mbox_ll_t * next;
void * content;
} bl00mbox_ll_t;
typedef struct {
size_t len;
void * elems[];
} bl00mbox_array_t;
typedef bool (* bl00mbox_set_key_t)(void *, void *);
// initialize all zeroed out except for content type
typedef struct {
bl00mbox_ll_t * start;
bl00mbox_set_key_t key;
size_t len;
} bl00mbox_set_t;
typedef struct {
bl00mbox_ll_t * next;
}bl00mbox_set_iter_t;
bool bl00mbox_set_contains(bl00mbox_set_t * set, void * content);
bool bl00mbox_set_add_skip_unique_check(bl00mbox_set_t * set, void * content);
bool bl00mbox_set_add(bl00mbox_set_t * set, void * content);
bool bl00mbox_set_remove(bl00mbox_set_t * set, void * content);
// removing from set during iteration is safe, adding is NOT
void bl00mbox_set_iter_start(bl00mbox_set_iter_t * iter, bl00mbox_set_t * set);
void * bl00mbox_set_iter_next(bl00mbox_set_iter_t * iter);
bl00mbox_array_t * bl00mbox_set_to_array(bl00mbox_set_t * set);
#pragma once
#include "bl00mbox_config.h"
#include <stdbool.h>
typedef enum {
BL00MBOX_ERROR_OK = false,
BL00MBOX_ERROR_OOM,
BL00MBOX_ERROR_INVALID_CONNECTION,
BL00MBOX_ERROR_INVALID_IDENTIFIER,
} bl00mbox_error_t;
#ifdef BL00MBOX_FREERTOS
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
typedef SemaphoreHandle_t bl00mbox_lock_t;
#endif
bool bl00mbox_create_lock(bl00mbox_lock_t * lock);
void bl00mbox_delete_lock(bl00mbox_lock_t * lock);
void bl00mbox_take_lock(bl00mbox_lock_t * lock);
void bl00mbox_give_lock(bl00mbox_lock_t * lock);
#ifdef BL00MBOX_DEBUG
void bl00mbox_wait();
#endif
#ifdef BL00MBOX_ESPIDF
#include "esp_log.h"
#define bl00mbox_log_error(txt, ...) ESP_LOGE("bl00mbox", txt, ##__VA_ARGS__);
#ifdef BL00MBOX_DEBUG
#define bl00mbox_log_info(txt, ...) ESP_LOGE("bl00mbox", txt, ##__VA_ARGS__);
#else
#define bl00mbox_log_info(txt, ...) ESP_LOGI("bl00mbox", txt, ##__VA_ARGS__);
#endif
#else
void bl00mbox_log_error(char * txt, ...);
void bl00mbox_log_info(char * txt, ...);
#endif
//SPDX-License-Identifier: CC0-1.0
#pragma once
#include "stdio.h"
#include "radspa.h"
#include "bl00mbox_os.h"
#include "bl00mbox_channel_plugin.h"
typedef struct _bl00mbox_plugin_registry_t{
radspa_descriptor_t * descriptor;
struct _bl00mbox_plugin_registry_t * next;
} bl00mbox_plugin_registry_t;
radspa_descriptor_t * bl00mbox_plugin_registry_get_descriptor_from_id(uint32_t id);
radspa_descriptor_t * bl00mbox_plugin_registry_get_descriptor_from_index(uint32_t index);
void bl00mbox_plugin_registry_init(void);
uint16_t bl00mbox_plugin_registry_get_plugin_num(void);
//SPDX-License-Identifier: CC0-1.0
#pragma once
#include "bl00mbox_audio.h"
#include "radspa.h"
#include "radspa_helpers.h"
#include "xoroshiro64star.h"
//SPDX-License-Identifier: CC0-1.0
#pragma once
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "bl00mbox_plugin_registry.h"
#include "bl00mbox_audio.h"
#include "bl00mbox_os.h"
#include "bl00mbox_containers.h"
#include <stdint.h>
#include "bl00mbox_audio.h"
#include "radspa_helpers.h"
// lazy 2nd error channel: pointer return types resulting in NULL means OOM
uint16_t bl00mbox_channel_plugins_num(bl00mbox_channel_t * chan);
uint16_t bl00mbox_channel_conns_num(bl00mbox_channel_t * chan);
uint16_t bl00mbox_channel_mixer_num(bl00mbox_channel_t * chan);
bl00mbox_array_t * bl00mbox_channel_collect_plugins(bl00mbox_channel_t * chan);
bl00mbox_array_t * bl00mbox_channel_collect_connections_mx(bl00mbox_channel_t * chan);
bl00mbox_plugin_t * bl00mbox_plugin_create_unlisted(bl00mbox_channel_t * chan, radspa_descriptor_t * desc, uint32_t init_var);
bl00mbox_plugin_t * bl00mbox_plugin_create(bl00mbox_channel_t * chan, uint32_t id, uint32_t init_var);
void bl00mbox_plugin_destroy(bl00mbox_plugin_t * plugin);
void bl00mbox_plugin_set_always_render(bl00mbox_plugin_t * plugin, bool value);
bl00mbox_error_t bl00mbox_signal_set_value(bl00mbox_signal_t * signal, int value);
int16_t bl00mbox_signal_get_value(bl00mbox_signal_t * signal);
bl00mbox_error_t bl00mbox_signal_connect(bl00mbox_signal_t * some, bl00mbox_signal_t * other);
bl00mbox_error_t bl00mbox_signal_connect_mx(bl00mbox_signal_t * some);
void bl00mbox_signal_disconnect(bl00mbox_signal_t * signal);
bl00mbox_array_t * bl00mbox_signal_collect_connections(bl00mbox_signal_t * signal);
bl00mbox_connection_t * bl00mbox_connection_from_signal(bl00mbox_signal_t * signal);
bool bl00mbox_signal_is_input(bl00mbox_signal_t * signal);
bool bl00mbox_signal_is_output(bl00mbox_signal_t * signal);
# SPDX-License-Identifier: CC0-1.0
from bl00mbox._user import *
import bl00mbox._patches as patches
import bl00mbox._helpers as helpers
from bl00mbox._plugins import plugins
# SPDX-License-Identifier: CC0-1.0
import time
def terminal_scope(
signal,
signal_min=-32767,
signal_max=32767,
delay_ms=20,
width=80,
fun=None,
fun_ms=None,
):
"""give it a signal and show it on terminal"""
if signal_max <= signal_min:
return
ms_counter = 0
fun_counter = 0
if fun != None:
fun()
while True:
if fun != None:
if fun_ms != None:
if fun_ms <= fun_counter:
fun()
fun_counter = fun_counter % fun_ms
raw_val = signal.value
ret = f"{ms_counter:06d}"
if raw_val == -32768:
ret += " [" + "?" * width + "] INVALID"
else:
val = int((width * (raw_val - signal_min)) / (signal_max - signal_min))
if val > width:
val = width
if val < 0:
val = 0
ret += " [" + "X" * val + "." * (width - val) + "]"
percent = int(100 * val / width)
ret += f" {percent:02d}%"
print(ret)
time.sleep_ms(delay_ms)
ms_counter += delay_ms
fun_counter += delay_ms
# terminal_scope(a.env.signals.output, 0, fun = a.start, fun_ms = 1000)
def sct_to_note_name(sct):
sct = sct - 18367 + 100
octave = ((sct + 9 * 200) // 2400) + 4
tones = ["A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab"]
tone = tones[(sct // 200) % 12]
return tone + str(octave)
def note_name_to_sct(name):
tones = ["A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab"]
semitones = tones.index(name[0])
if semitones > 2:
semitones -= 12
if name[1] == "b":
octave = int(name[2:])
semitones -= 1
elif name[1] == "#":
octave = int(name[2:])
semitones += 1
else:
octave = int(name[1:])
return 18367 + (octave - 4) * 2400 + (200 * semitones)
def sct_to_freq(sct):
return 440 * 2 ** ((sct - 18367) / 2400)