From dfc7f0529c1a691593bf803bec25bb84e4b47114 Mon Sep 17 00:00:00 2001 From: moon2 <moon2protonmail@protonmail.com> Date: Thu, 17 Aug 2023 13:44:34 +0000 Subject: [PATCH] bl00mbox: slow line in --- components/bl00mbox/CMakeLists.txt | 2 + components/bl00mbox/bl00mbox_audio.c | 5 +- .../bl00mbox/bl00mbox_plugin_registry.c | 2 + components/bl00mbox/bl00mbox_user.c | 3 + components/bl00mbox/include/bl00mbox_audio.h | 2 + components/bl00mbox/plugins/ampliverter.c | 4 +- .../plugins/bl00mbox/bl00mbox_line_in.c | 48 ++++++++++ .../plugins/bl00mbox/bl00mbox_line_in.h | 9 ++ components/bl00mbox/plugins/delay.c | 3 +- components/bl00mbox/plugins/distortion.c | 3 +- components/bl00mbox/plugins/env_adsr.c | 13 +-- components/bl00mbox/plugins/flanger.c | 3 +- components/bl00mbox/plugins/lowpass.c | 5 +- components/bl00mbox/plugins/mixer.c | 4 +- components/bl00mbox/plugins/multipitch.c | 25 ++---- components/bl00mbox/plugins/noise.c | 6 +- components/bl00mbox/plugins/noise_burst.c | 8 +- components/bl00mbox/plugins/osc_fm.c | 3 +- components/bl00mbox/plugins/sampler.c | 74 +++++++++++---- components/bl00mbox/plugins/sampler.h | 5 ++ components/bl00mbox/plugins/sequencer.c | 11 +-- .../bl00mbox/plugins/slew_rate_limiter.c | 3 +- components/bl00mbox/radspa/radspa.h | 31 +++++-- components/bl00mbox/radspa/radspa_helpers.c | 38 ++++++-- components/bl00mbox/radspa/radspa_helpers.h | 7 ++ python_payload/apps/tiny_sampler/__init__.py | 90 +++++++++++++++++++ python_payload/apps/tiny_sampler/flow3r.toml | 11 +++ python_payload/bl00mbox/_patches.py | 66 ++++++++------ python_payload/mypystubs/audio.pyi | 11 +++ python_payload/mypystubs/bl00mbox/_user.pyi | 1 + 30 files changed, 370 insertions(+), 126 deletions(-) create mode 100644 components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.c create mode 100644 components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.h create mode 100644 python_payload/apps/tiny_sampler/__init__.py create mode 100644 python_payload/apps/tiny_sampler/flow3r.toml diff --git a/components/bl00mbox/CMakeLists.txt b/components/bl00mbox/CMakeLists.txt index 487573d07d..905d3ea301 100644 --- a/components/bl00mbox/CMakeLists.txt +++ b/components/bl00mbox/CMakeLists.txt @@ -21,11 +21,13 @@ idf_component_register( plugins/lowpass.c plugins/mixer.c plugins/slew_rate_limiter.c + plugins/bl00mbox/bl00mbox_line_in.c radspa/radspa_helpers.c extern/xoroshiro64star.c INCLUDE_DIRS include plugins + plugins/bl00mbox radspa extern REQUIRES diff --git a/components/bl00mbox/bl00mbox_audio.c b/components/bl00mbox/bl00mbox_audio.c index 3664d64c7b..4b8ff07fec 100644 --- a/components/bl00mbox/bl00mbox_audio.c +++ b/components/bl00mbox/bl00mbox_audio.c @@ -8,6 +8,8 @@ void bl00mbox_audio_disable(){ bl00mbox_audio_run = false; } static uint32_t render_pass_id; +int16_t * bl00mbox_line_in_interlaced = NULL; + // fixed-length list of channels static bl00mbox_channel_t channels[BL00MBOX_CHANNELS]; static int8_t last_chan_event = 0; @@ -97,7 +99,7 @@ bool bl00mbox_channel_set_free(uint8_t channel_index, bool free){ } uint8_t bl00mbox_channel_get_free_index(){ - uint8_t ret = 1; + uint8_t ret = 1; // never return system channel for(; ret < BL00MBOX_CHANNELS; ret++){ if(bl00mbox_get_channel(ret)->is_free){ bl00mbox_get_channel(ret)->is_free = false; @@ -226,6 +228,7 @@ void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len){ render_pass_id++; // fresh pass, all relevant sources must be recomputed uint16_t mono_len = len/2; + bl00mbox_line_in_interlaced = rx; int16_t acc[mono_len]; // system channel always runs non-adding bl00mbox_audio_channel_render(&(channels[0]), acc, mono_len, 0); diff --git a/components/bl00mbox/bl00mbox_plugin_registry.c b/components/bl00mbox/bl00mbox_plugin_registry.c index 46edee771b..80edc3152b 100644 --- a/components/bl00mbox/bl00mbox_plugin_registry.c +++ b/components/bl00mbox/bl00mbox_plugin_registry.c @@ -101,6 +101,7 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index) #include "mixer.h" #include "multipitch.h" #include "slew_rate_limiter.h" +#include "bl00mbox_line_in.h" void bl00mbox_plugin_registry_init(void){ if(bl00mbox_plugin_registry_is_initialized) return; @@ -118,4 +119,5 @@ void bl00mbox_plugin_registry_init(void){ plugin_add(&mixer_desc); plugin_add(&slew_rate_limiter_desc); plugin_add(&multipitch_desc); + plugin_add(&bl00mbox_line_in_desc); } diff --git a/components/bl00mbox/bl00mbox_user.c b/components/bl00mbox/bl00mbox_user.c index 3c31abad16..db84045d04 100644 --- a/components/bl00mbox/bl00mbox_user.c +++ b/components/bl00mbox/bl00mbox_user.c @@ -705,6 +705,9 @@ int16_t bl00mbox_channel_bud_get_signal_value(uint8_t channel, uint32_t bud_inde radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index); if(sig == NULL) return false; + if((sig->hints & RADSPA_SIGNAL_HINT_OUTPUT) && (sig->buffer != NULL)){ + return sig->buffer[0]; + } return sig->value; } diff --git a/components/bl00mbox/include/bl00mbox_audio.h b/components/bl00mbox/include/bl00mbox_audio.h index 84d7fd59c4..a28eb9bc5b 100644 --- a/components/bl00mbox/include/bl00mbox_audio.h +++ b/components/bl00mbox/include/bl00mbox_audio.h @@ -21,6 +21,8 @@ struct _bl00mbox_connection_source_t; struct _bl00mbox_channel_root_t; struct _bl00mbox_channel_t; +extern int16_t * bl00mbox_line_in_interlaced; + typedef struct _bl00mbox_bud_t{ radspa_t * plugin; // plugin char * name; diff --git a/components/bl00mbox/plugins/ampliverter.c b/components/bl00mbox/plugins/ampliverter.c index 4dfdaf564f..23b3dced39 100644 --- a/components/bl00mbox/plugins/ampliverter.c +++ b/components/bl00mbox/plugins/ampliverter.c @@ -50,10 +50,8 @@ void ampliverter_run(radspa_t * ampliverter, uint16_t num_samples, uint32_t rend ret = radspa_mult_shift(ret, gain); ret = radspa_add_sat(ret, bias); } - (output_sig->buffer)[i] = ret; - + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } radspa_t * ampliverter_create(uint32_t init_var){ diff --git a/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.c b/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.c new file mode 100644 index 0000000000..a977a158cb --- /dev/null +++ b/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.c @@ -0,0 +1,48 @@ +#include "bl00mbox_line_in.h" + +radspa_descriptor_t bl00mbox_line_in_desc = { + .name = "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 +}; + +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 = gain_sig->get_value(gain_sig, i, num_samples, render_pass_id); + if(left_sig->buffer != NULL){ + int16_t left = radspa_gain(bl00mbox_line_in_interlaced[2*i], gain); + left_sig->set_value(left_sig, i, left, num_samples, render_pass_id); + } + + if(right_sig->buffer != NULL){ + int16_t right = radspa_gain(bl00mbox_line_in_interlaced[2*i], gain); + right_sig->set_value(right_sig, i, right, num_samples, render_pass_id); + } + + if(mid_sig->buffer != NULL){ + int16_t mid = bl00mbox_line_in_interlaced[2*i]>>1; + mid += bl00mbox_line_in_interlaced[2*i]>>1; + mid = radspa_gain(mid, gain); + mid_sig->set_value(mid_sig, i, mid, num_samples, render_pass_id); + } + } +} + +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_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN); + return line_in; +} diff --git a/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.h b/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.h new file mode 100644 index 0000000000..1a895b6463 --- /dev/null +++ b/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.h @@ -0,0 +1,9 @@ +#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); diff --git a/components/bl00mbox/plugins/delay.c b/components/bl00mbox/plugins/delay.c index d87218e401..dbf5d6c873 100644 --- a/components/bl00mbox/plugins/delay.c +++ b/components/bl00mbox/plugins/delay.c @@ -67,9 +67,8 @@ void delay_run(radspa_t * delay, uint16_t num_samples, uint32_t render_pass_id){ ret = radspa_add_sat(radspa_mult_shift(dry_vol,dry), radspa_mult_shift(wet,level)); - (output_sig->buffer)[i] = ret; + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } radspa_t * delay_create(uint32_t init_var){ diff --git a/components/bl00mbox/plugins/distortion.c b/components/bl00mbox/plugins/distortion.c index 7effbea2d2..3a542b94c6 100644 --- a/components/bl00mbox/plugins/distortion.c +++ b/components/bl00mbox/plugins/distortion.c @@ -29,9 +29,8 @@ void distortion_run(radspa_t * distortion, uint16_t num_samples, uint32_t render int32_t blend = input & ((1<<7)-1); ret = dist[index]*((1<<7)-blend) + dist[index+1]*blend; ret = ret >> 7; - (output_sig->buffer)[i] = ret; + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } radspa_t * distortion_create(uint32_t init_var){ diff --git a/components/bl00mbox/plugins/env_adsr.c b/components/bl00mbox/plugins/env_adsr.c index d4c04942cd..d3f4fc8950 100644 --- a/components/bl00mbox/plugins/env_adsr.c +++ b/components/bl00mbox/plugins/env_adsr.c @@ -129,10 +129,9 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas radspa_signal_t * release_sig = NULL; radspa_signal_t * gate_sig = NULL; - int16_t ret = output_sig->value; - + int16_t env = 0; for(uint16_t i = 0; i < num_samples; i++){ - static int16_t env = 0; + int16_t ret = 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)); @@ -214,12 +213,8 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas 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->set_value(phase_sig, i, plugin_data->env_phase, num_samples, render_pass_id); + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - phase_sig->value = plugin_data->env_phase; - output_sig->value = ret; } diff --git a/components/bl00mbox/plugins/flanger.c b/components/bl00mbox/plugins/flanger.c index 1f68d48188..4a4f8571d4 100644 --- a/components/bl00mbox/plugins/flanger.c +++ b/components/bl00mbox/plugins/flanger.c @@ -78,9 +78,8 @@ void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_ ret = radspa_add_sat(radspa_mult_shift(dry, dry_vol), radspa_mult_shift(radspa_clip(wet), mix)); ret = radspa_clip(radspa_gain(ret, level)); - (output_sig->buffer)[i] = ret; + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } radspa_t * flanger_create(uint32_t init_var){ diff --git a/components/bl00mbox/plugins/lowpass.c b/components/bl00mbox/plugins/lowpass.c index f2087ed488..fbc8b9dae0 100644 --- a/components/bl00mbox/plugins/lowpass.c +++ b/components/bl00mbox/plugins/lowpass.c @@ -98,8 +98,6 @@ void lowpass_run(radspa_t * lowpass, uint16_t num_samples, uint32_t render_pass_ radspa_signal_t * q_sig = radspa_signal_get_by_index(lowpass, LOWPASS_Q); radspa_signal_t * gain_sig = radspa_signal_get_by_index(lowpass, LOWPASS_GAIN); - static int16_t ret = 0; - for(uint16_t i = 0; i < num_samples; i++){ int16_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id); int32_t freq = freq_sig->get_value(freq_sig, i, num_samples, render_pass_id); @@ -114,9 +112,8 @@ void lowpass_run(radspa_t * lowpass, uint16_t num_samples, uint32_t render_pass_ float out = apply_lowpass(data, input) + 0.5; int16_t ret = radspa_clip(radspa_gain((int32_t) out, gain)); - (output_sig->buffer)[i] = ret; + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } radspa_t * lowpass_create(uint32_t real_init_var){ diff --git a/components/bl00mbox/plugins/mixer.c b/components/bl00mbox/plugins/mixer.c index 397fd142cc..6a68364660 100644 --- a/components/bl00mbox/plugins/mixer.c +++ b/components/bl00mbox/plugins/mixer.c @@ -31,9 +31,9 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){ // remove dc (* dc_acc) = (ret + (* dc_acc)*1023) >> 10; ret -= (* dc_acc); - (output_sig->buffer)[i] = radspa_clip(radspa_gain(ret, gain)); + ret = radspa_clip(radspa_gain(ret, gain)); + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } radspa_t * mixer_create(uint32_t init_var){ diff --git a/components/bl00mbox/plugins/multipitch.c b/components/bl00mbox/plugins/multipitch.c index 50ff80ec20..e07d1a8b8f 100644 --- a/components/bl00mbox/plugins/multipitch.c +++ b/components/bl00mbox/plugins/multipitch.c @@ -18,40 +18,27 @@ void multipitch_run(radspa_t * multipitch, uint16_t num_samples, uint32_t render radspa_signal_t * output_sigs[num_outputs]; radspa_signal_t * pitch_sigs[num_outputs]; for(uint8_t j = 0; j < num_outputs; j++){ - output_sigs[j] = radspa_signal_get_by_index(multipitch, 2 + j); - pitch_sigs[j] = radspa_signal_get_by_index(multipitch, 3 + j); + output_sigs[j] = radspa_signal_get_by_index(multipitch, 2 + 2 * j); + pitch_sigs[j] = radspa_signal_get_by_index(multipitch, 3 + 2 * j); if(output_sigs[j]->buffer != NULL) output_request = true; } if(!output_request) return; - int32_t ret = 0; - int32_t rets[num_outputs]; - for(uint16_t i = 0; i < num_samples; i++){ + int32_t ret = 0; int32_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id); ret = input; if(thru_sig->buffer != NULL) (thru_sig->buffer)[i] = ret; + thru_sig->set_value(thru_sig, i, ret, num_samples, render_pass_id); int32_t pitch; for(uint8_t j = 0; j < num_outputs; j++){ pitch = pitch_sigs[j]->get_value(pitch_sigs[j], i, num_samples, render_pass_id); - rets[j] = pitch + input - RADSPA_SIGNAL_VAL_SCT_A440; - if(output_sigs[j]->buffer != NULL) (output_sigs[j]->buffer)[i] = rets[j]; - } - } - - // clang-tidy only, num_samples is always nonzero - if(!num_samples){ - for(uint8_t j = 0; j < num_outputs; j++){ - rets[j] = 0; + ret = pitch + input - RADSPA_SIGNAL_VAL_SCT_A440; + output_sigs[j]->set_value(output_sigs[j], i, ret, num_samples, render_pass_id); } } - - for(uint8_t j = 0; j < num_outputs; j++){ - output_sigs[j]->value = rets[j]; - } - thru_sig->value = ret; } radspa_t * multipitch_create(uint32_t init_var){ diff --git a/components/bl00mbox/plugins/noise.c b/components/bl00mbox/plugins/noise.c index 364456f517..032aec1c2a 100644 --- a/components/bl00mbox/plugins/noise.c +++ b/components/bl00mbox/plugins/noise.c @@ -16,12 +16,10 @@ void noise_run(radspa_t * noise, uint16_t num_samples, uint32_t render_pass_id){ radspa_signal_t * output_sig = radspa_signal_get_by_index(noise, NOISE_OUTPUT); if(output_sig->buffer == NULL) return; - static int16_t ret = 0; for(uint16_t i = 0; i < num_samples; i++){ - (output_sig->buffer)[i] = radspa_random(); - + int16_t ret = radspa_random(); + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } radspa_t * noise_create(uint32_t init_var){ diff --git a/components/bl00mbox/plugins/noise_burst.c b/components/bl00mbox/plugins/noise_burst.c index 13c16138f6..97010fdf5a 100644 --- a/components/bl00mbox/plugins/noise_burst.c +++ b/components/bl00mbox/plugins/noise_burst.c @@ -38,9 +38,8 @@ void noise_burst_run(radspa_t * noise_burst, uint16_t num_samples, uint32_t rend radspa_signal_t * trigger_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_TRIGGER); radspa_signal_t * length_ms_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_LENGTH_MS); - int16_t ret = output_sig->value; - for(uint16_t i = 0; i < num_samples; i++){ + int16_t ret = 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)); @@ -55,10 +54,7 @@ void noise_burst_run(radspa_t * noise_burst, uint16_t num_samples, uint32_t rend if(plugin_data->counter < plugin_data->limit){ ret = radspa_random(); plugin_data->counter++; - } else { - ret = 0; } - if(output_sig->buffer != NULL) (output_sig->buffer)[i] = ret; + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } diff --git a/components/bl00mbox/plugins/osc_fm.c b/components/bl00mbox/plugins/osc_fm.c index 750a8ee3bb..66cf21a251 100644 --- a/components/bl00mbox/plugins/osc_fm.c +++ b/components/bl00mbox/plugins/osc_fm.c @@ -52,9 +52,8 @@ void osc_fm_run(radspa_t * osc_fm, uint16_t num_samples, uint32_t render_pass_id int32_t tmp = (plugin_data->counter) >> 17; tmp = (tmp*2) - 32767; ret = waveshaper(tmp, wave); - (output_sig->buffer)[i] = ret; + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } static inline int16_t triangle(int16_t saw){ diff --git a/components/bl00mbox/plugins/sampler.c b/components/bl00mbox/plugins/sampler.c index e5ed7ac51f..e4ff33b72d 100644 --- a/components/bl00mbox/plugins/sampler.c +++ b/components/bl00mbox/plugins/sampler.c @@ -4,14 +4,16 @@ radspa_t * sampler_create(uint32_t init_var); radspa_descriptor_t sampler_desc = { .name = "_sampler_ram", .id = 696969, - .description = "simple sampler that stores a copy of the sample in ram", + .description = "simple sampler that stores a copy of the sample in ram and has basic recording functionality", .create_plugin_instance = sampler_create, .destroy_plugin_instance = radspa_standard_plugin_destroy }; -#define SAMPLER_NUM_SIGNALS 2 +#define SAMPLER_NUM_SIGNALS 4 #define SAMPLER_OUTPUT 0 #define SAMPLER_TRIGGER 1 +#define SAMPLER_REC_IN 2 +#define SAMPLER_REC_TRIGGER 3 void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_id){ radspa_signal_t * output_sig = radspa_signal_get_by_index(sampler, SAMPLER_OUTPUT); @@ -19,35 +21,61 @@ void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_ sampler_data_t * data = sampler->plugin_data; int16_t * buf = sampler->plugin_table; radspa_signal_t * trigger_sig = radspa_signal_get_by_index(sampler, SAMPLER_TRIGGER); + radspa_signal_t * rec_in_sig = radspa_signal_get_by_index(sampler, SAMPLER_REC_IN); + radspa_signal_t * rec_trigger_sig = radspa_signal_get_by_index(sampler, SAMPLER_REC_TRIGGER); static int32_t ret = 0; uint32_t buffer_size = sampler->plugin_table_len; for(uint16_t i = 0; i < num_samples; i++){ + int16_t rec_trigger = radspa_trigger_get(rec_trigger_sig->get_value(rec_trigger_sig, i, num_samples, render_pass_id), &(data->rec_trigger_prev)); + int16_t trigger = radspa_trigger_get(trigger_sig->get_value(trigger_sig, i, num_samples, render_pass_id), &(data->trigger_prev)); - int16_t trigger = trigger_sig->get_value(trigger_sig, i, num_samples, render_pass_id); - - int16_t vel = radspa_trigger_get(trigger, &(data->trigger_prev)); - - if(vel > 0){ - data->read_head_position = 0; - data->volume = vel; - } else if(vel < 0){ - data->read_head_position = buffer_size; + if(rec_trigger > 0){ + if(!(data->rec_active)){ + data->read_head_position = data->sample_len; // reset sample player into off + data->write_head_position = 0; + data->sample_len = 0; + data->rec_active = true; + } + } else if(rec_trigger < 0){ + if(data->rec_active){ + if(data->sample_len == buffer_size){ + data->sample_start = data->write_head_position; + } else { + data->sample_start = 0; + } + data->rec_active = false; + } } - if(data->read_head_position < buffer_size){ - ret = radspa_mult_shift(buf[data->read_head_position], data->volume); - data->read_head_position++; + if(data->rec_active){ + int16_t rec_in = rec_in_sig->get_value(rec_in_sig, i, num_samples, render_pass_id); + buf[data->write_head_position] = rec_in; + data->write_head_position++; + if(data->write_head_position >= buffer_size) data->write_head_position = 0; + if(data->sample_len < buffer_size) data->sample_len++; } else { - //ret = (ret * 255)>>8; // avoid dc clicks with bad samples - ret = 0; - } + if(trigger > 0){ + data->read_head_position = 0; + data->volume = trigger; + } else if(trigger < 0){ + data->read_head_position = data->sample_len; + } - (output_sig->buffer)[i] = ret; + if(data->read_head_position < data->sample_len){ + uint32_t sample_offset_pos = data->read_head_position + data->sample_start; + if(sample_offset_pos >= data->sample_len) sample_offset_pos -= data->sample_len; + ret = radspa_mult_shift(buf[sample_offset_pos], data->volume); + data->read_head_position++; + } else { + //ret = (ret * 255)>>8; // avoid dc clicks with bad samples + ret = 0; + } + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); + } } - output_sig->value = ret; } radspa_t * sampler_create(uint32_t init_var){ @@ -58,5 +86,13 @@ radspa_t * sampler_create(uint32_t init_var){ sampler->render = sampler_run; radspa_signal_set(sampler, SAMPLER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); radspa_signal_set(sampler, SAMPLER_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + radspa_signal_set(sampler, SAMPLER_REC_IN, "rec_in", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(sampler, SAMPLER_REC_TRIGGER, "rec_trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + sampler_data_t * data = sampler->plugin_data; + data->trigger_prev = 0; + data->rec_trigger_prev = 0; + data->rec_active = false; + data->sample_start = 0; + data->sample_len = sampler->plugin_table_len; return sampler; } diff --git a/components/bl00mbox/plugins/sampler.h b/components/bl00mbox/plugins/sampler.h index 53bedfbacd..9958d9b9f2 100644 --- a/components/bl00mbox/plugins/sampler.h +++ b/components/bl00mbox/plugins/sampler.h @@ -4,8 +4,13 @@ typedef struct { uint32_t read_head_position; + uint32_t write_head_position; + uint32_t sample_start; + uint32_t sample_len; int16_t trigger_prev; + int16_t rec_trigger_prev; int16_t volume; + bool rec_active; } sampler_data_t; extern radspa_descriptor_t sampler_desc; diff --git a/components/bl00mbox/plugins/sequencer.c b/components/bl00mbox/plugins/sequencer.c index 5345e2d96e..c642ae98fd 100644 --- a/components/bl00mbox/plugins/sequencer.c +++ b/components/bl00mbox/plugins/sequencer.c @@ -98,17 +98,12 @@ void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_p } for(uint8_t j = 0; j < data->num_tracks; j++){ - if(track_sigs[j]->buffer != NULL) (track_sigs[j]->buffer)[i] = data->tracks[j].track_fill; + track_sigs[j]->set_value(track_sigs[j], i, data->tracks[j].track_fill, num_samples, render_pass_id); } - if(sync_out_sig->buffer != NULL) (sync_out_sig->buffer)[i] = data->sync_out; - if(step_sig->buffer != NULL) (step_sig->buffer)[i] = data->step; + sync_out_sig->set_value(sync_out_sig, i, data->sync_out, num_samples, render_pass_id); + step_sig->set_value(step_sig, i, data->step, num_samples, render_pass_id); } } - for(uint8_t j = 0; j < data->num_tracks; j++){ - track_sigs[j]->value = data->tracks[j].track_fill; - } - sync_out_sig->value = data->sync_out; - step_sig->value = data->step; } radspa_t * sequencer_create(uint32_t init_var){ diff --git a/components/bl00mbox/plugins/slew_rate_limiter.c b/components/bl00mbox/plugins/slew_rate_limiter.c index f1907b7c74..576bd51ae2 100644 --- a/components/bl00mbox/plugins/slew_rate_limiter.c +++ b/components/bl00mbox/plugins/slew_rate_limiter.c @@ -44,8 +44,7 @@ void slew_rate_limiter_run(radspa_t * slew_rate_limiter, uint16_t num_samples, u } else { ret = input; } - (output_sig->buffer)[i] = ret; + output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id); } - output_sig->value = ret; } diff --git a/components/bl00mbox/radspa/radspa.h b/components/bl00mbox/radspa/radspa.h index 6e018c4a1a..a49d860eed 100644 --- a/components/bl00mbox/radspa/radspa.h +++ b/components/bl00mbox/radspa/radspa.h @@ -4,7 +4,7 @@ // this file, kindly append "-modified" to the version string below so it is not mistaken // for an official release. -// Version 0.1.0+ +// Version 0.2.0 /* Realtime Audio Developer's Simple Plugin Api * @@ -43,6 +43,10 @@ #define RADSPA_SIGNAL_HINT_TRIGGER (1<<2) #define RADSPA_SIGNAL_HINT_GAIN (1<<3) #define RADSPA_SIGNAL_HINT_SCT (1<<5) +#define RADSPSA_SIGNAL_HINT_REDUCED_RANGE (1<<6) +#define RADSPSA_SIGNAL_HINT_POS_SHIFT (1<<7) +// 6 bit number, 0 for non-stepped, else number of steps +#define RADSPSA_SIGNAL_HINT_STEPPED_LSB (1<<8) #define RADSPA_SIGNAL_VAL_SCT_A440 (INT16_MAX - 6*2400) #define RADSPA_SIGNAL_VAL_UNITY_GAIN (1<<12) @@ -60,21 +64,30 @@ typedef struct _radspa_descriptor_t{ } radspa_descriptor_t; typedef struct _radspa_signal_t{ + // this bitfield determines the type of the signal, see RADSPA_SIGNAL_HINTS_* uint32_t hints; + // this is the name of the signal as shown to the user. + // allowed characters: lowercase, numbers, underscore, may not start with number + // if name_multplex >= 0: may not end with number char * name; + // arbitrary formatted string to describe what the signal is for char * description; + // unit that corresponds to value, may be empty. note: some RADSPA_SIGNAL_HINTS_* + // imply units. field may be empty. char * unit; + // -1 to disable signal multiplexing int8_t name_multiplex; - - int16_t * buffer; // full buffer of num_samples. may be NULL. - - // used for input channels only - int16_t value; //static value, should be used if buffer is NULL. + // buffer full of samples, may be NULL + int16_t * buffer; + // static value to be used when buffer is NULL for input signals only + int16_t value; uint32_t render_pass_id; - // function to retrieve value. radspa_helpers provides an example. + // function for input signals to retrieve their value at a buffer index. radspa_helpers provides an example. this function should be set by the host. int16_t (* get_value)(struct _radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id); - - struct _radspa_signal_t * next; //signals are in a linked list + // function for output signals to set a value at a buffer index. radspa_helpers provides an example. this function should be set by the host. + void (* set_value)(struct _radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id); + // linked list pointer + struct _radspa_signal_t * next; } radspa_signal_t; typedef struct _radspa_t{ diff --git a/components/bl00mbox/radspa/radspa_helpers.c b/components/bl00mbox/radspa/radspa_helpers.c index 7bfdbbd586..a3c1a17995 100644 --- a/components/bl00mbox/radspa/radspa_helpers.c +++ b/components/bl00mbox/radspa/radspa_helpers.c @@ -77,6 +77,7 @@ int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_ sig->value = value; sig->name_multiplex = -1; sig->get_value = radspa_signal_get_value; + sig->set_value = radspa_signal_set_value; //find end of linked list uint16_t list_index = 0; @@ -97,16 +98,41 @@ int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_ } int16_t radspa_signal_get_value(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id){ - if(sig->buffer != NULL){ - if(sig->render_pass_id != render_pass_id){ - radspa_host_request_buffer_render(sig->buffer, num_samples); //, render_pass_id); - sig->render_pass_id = render_pass_id; - } - return sig->buffer[index]; + if(sig->buffer == NULL){ + return radspa_signal_get_value_disconnected(sig, index, num_samples, render_pass_id); + } else { + return radspa_signal_get_value_connected(sig, index, num_samples, render_pass_id); + } +} + +inline int16_t radspa_signal_get_value_connected(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id){ + if(sig->render_pass_id != render_pass_id){ + radspa_host_request_buffer_render(sig->buffer, num_samples); //, render_pass_id); + sig->render_pass_id = render_pass_id; } + return sig->buffer[index]; +} + +inline int16_t radspa_signal_get_value_disconnected(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id){ return sig->value; } +void radspa_signal_set_value(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id){ + if(sig->buffer == NULL){ + radspa_signal_set_value_disconnected(sig, index, value, num_samples, render_pass_id); + } else { + radspa_signal_set_value_connected(sig, index, value, num_samples, render_pass_id); + } +} + +inline void radspa_signal_set_value_connected(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id){ + sig->buffer[index] = value; +} + +inline void radspa_signal_set_value_disconnected(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id){ + sig->value = value; +} + radspa_t * radspa_standard_plugin_create(radspa_descriptor_t * desc, uint8_t num_signals, size_t plugin_data_size, uint32_t plugin_table_size){ radspa_t * ret = calloc(1, sizeof(radspa_t)); if(ret == NULL) return NULL; diff --git a/components/bl00mbox/radspa/radspa_helpers.h b/components/bl00mbox/radspa/radspa_helpers.h index c8e3717629..29f1ebf97f 100644 --- a/components/bl00mbox/radspa/radspa_helpers.h +++ b/components/bl00mbox/radspa/radspa_helpers.h @@ -28,3 +28,10 @@ void radspa_signals_free(radspa_t * plugin); * of radspa_host_request_buffer_render. */ int16_t radspa_signal_get_value(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id); + +int16_t radspa_signal_get_value_connected(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id); +int16_t radspa_signal_get_value_disconnected(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id); + +void radspa_signal_set_value(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id); +void radspa_signal_set_value_connected(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id); +void radspa_signal_set_value_disconnected(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id); diff --git a/python_payload/apps/tiny_sampler/__init__.py b/python_payload/apps/tiny_sampler/__init__.py new file mode 100644 index 0000000000..7b2f38c337 --- /dev/null +++ b/python_payload/apps/tiny_sampler/__init__.py @@ -0,0 +1,90 @@ +import bl00mbox +import captouch +import leds +import audio + +from st3m.application import Application, ApplicationContext +from st3m.input import InputState +from st3m.goose import Tuple, Iterator, Optional, Callable, List, Any, TYPE_CHECKING +from ctx import Context +from st3m.ui.view import View, ViewManager + + +class TinySampler(Application): + def __init__(self, app_ctx: ApplicationContext) -> None: + super().__init__(app_ctx) + self.blm = bl00mbox.Channel("tiny sampler") + + self.samplers: List[bl00mbox.patches._Patch | Any] = [None] * 5 + self.line_in = self.blm.new(bl00mbox.plugins.line_in) + self.blm.volume = ( + 30000 # TODO: increase onboard mic line in gain and remove this + ) + self.line_in.signals.gain = 30000 + for i in range(5): + self.samplers[i] = self.blm.new(bl00mbox.patches.sampler, 5000) + self.samplers[i].signals.output = self.blm.mixer + self.samplers[i].signals.rec_in = self.line_in.signals.right + self.is_recording = [False] * 5 + audio.input_set_source(audio.INPUT_SOURCE_ONBOARD_MIC) + + self.ct_prev = captouch.read() + + def _highlight_petal(self, num: int, r: int, g: int, b: int) -> None: + for i in range(7): + leds.set_rgb((4 * num - i + 3) % 40, r, g, b) + + def draw(self, ctx: Context) -> None: + dist = 90 + ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() + + ctx.font = ctx.get_font_name(0) + ctx.text_align = ctx.MIDDLE + ctx.font_size = 24 + for i in range(5): + ctx.rgb(0.8, 0.8, 0.8) + ctx.move_to(0, -dist) + ctx.text("play" + str(i)) + ctx.move_to(0, 0) + ctx.rotate(6.28 / 10) + ctx.move_to(0, -dist) + if self.is_recording[i]: + ctx.rgb(1, 0, 0) + ctx.text("rec" + str(i)) + ctx.move_to(0, 0) + ctx.rotate(6.28 / 10) + + def think(self, ins: InputState, delta_ms: int) -> None: + super().think(ins, delta_ms) + + leds.set_all_rgb(0, 0, 0) + for i in range(5): + if self.is_recording[i]: + self._highlight_petal(i * 2, 255, 0, 0) + else: + self._highlight_petal(i * 2, 0, 255, 0) + leds.update() + + ct = captouch.read() + for i in range(5): + if ct.petals[i * 2].pressed and not self.ct_prev.petals[i * 2].pressed: + if not self.is_recording[i]: + self.samplers[i].signals.trigger.start() + + for i in range(5): + if ( + ct.petals[(i * 2) + 1].pressed + and not self.ct_prev.petals[(i * 2) + 1].pressed + ): + if not self.is_recording[i]: + self.samplers[i].signals.rec_trigger.start() + self.is_recording[i] = True + if ( + not ct.petals[(i * 2) + 1].pressed + and self.ct_prev.petals[(i * 2) + 1].pressed + ): + if self.is_recording[i]: + self.samplers[i].signals.rec_trigger.stop() + self.is_recording[i] = False + + self.ct_prev = ct diff --git a/python_payload/apps/tiny_sampler/flow3r.toml b/python_payload/apps/tiny_sampler/flow3r.toml new file mode 100644 index 0000000000..18917097fc --- /dev/null +++ b/python_payload/apps/tiny_sampler/flow3r.toml @@ -0,0 +1,11 @@ +[app] +name = "tiny sampler" +menu = "Music" + +[entry] +class = "TinySampler" + +[metadata] +author = "Flow3r Badge Authors" +license = "LGPL-3.0-only" +url = "https://git.flow3r.garden/flow3r/flow3r-firmware" diff --git a/python_payload/bl00mbox/_patches.py b/python_payload/bl00mbox/_patches.py index d60317b6e6..3942399fe1 100644 --- a/python_payload/bl00mbox/_patches.py +++ b/python_payload/bl00mbox/_patches.py @@ -103,39 +103,53 @@ class sampler(_Patch): requires a wave file. default path: /sys/samples/ """ - def __init__(self, chan, filename): + def __init__(self, chan, init_var): + # init can be filename to load into ram super().__init__(chan) - if filename.startswith("/flash/") or filename.startswith("/sd/"): - f = wave.open(filename, "r") - elif filename.startswith("/"): - f = wave.open("/flash/" + filename, "r") - else: - f = wave.open("/flash/sys/samples/" + filename, "r") + if type(init_var) == str: + filename = init_var + if filename.startswith("/flash/") or filename.startswith("/sd/"): + f = wave.open(filename, "r") + elif filename.startswith("/"): + f = wave.open("/flash/" + filename, "r") + else: + f = wave.open("/flash/sys/samples/" + filename, "r") - self.len_frames = f.getnframes() - self.plugins.sampler = chan.new(bl00mbox.plugins._sampler_ram, self.len_frames) + self.len_frames = f.getnframes() + self.plugins.sampler = chan.new( + bl00mbox.plugins._sampler_ram, self.len_frames + ) - assert f.getsampwidth() == 2 - assert f.getnchannels() in (1, 2) - assert f.getcomptype() == "NONE" + assert f.getsampwidth() == 2 + assert f.getnchannels() in (1, 2) + assert f.getcomptype() == "NONE" - if f.getnchannels() == 1: - # fast path for mono - table = self.plugins.sampler.table_bytearray - for i in range(0, self.len_frames * 2, 100): - table[i : i + 100] = f.readframes(50) + if f.getnchannels() == 1: + # fast path for mono + table = self.plugins.sampler.table_bytearray + for i in range(0, self.len_frames * 2, 100): + table[i : i + 100] = f.readframes(50) + else: + # somewhat fast path for stereo + table = self.plugins.sampler.table_int16_array + for i in range(self.len_frames): + frame = f.readframes(1) + value = int.from_bytes(frame[0:2], "little") + table[i] = value + + f.close() + self._filename = filename else: - # somewhat fast path for stereo - table = self.plugins.sampler.table_int16_array - for i in range(self.len_frames): - frame = f.readframes(1) - value = int.from_bytes(frame[0:2], "little") - table[i] = value - - f.close() - self._filename = filename + self.len_frames = int(48 * init_var) + self.plugins.sampler = chan.new( + bl00mbox.plugins._sampler_ram, self.len_frames + ) + self._filename = "" + self.signals.trigger = self.plugins.sampler.signals.trigger self.signals.output = self.plugins.sampler.signals.output + self.signals.rec_in = self.plugins.sampler.signals.rec_in + self.signals.rec_trigger = self.plugins.sampler.signals.rec_trigger @property def filename(self): diff --git a/python_payload/mypystubs/audio.pyi b/python_payload/mypystubs/audio.pyi index e084c7dc41..40570a450a 100644 --- a/python_payload/mypystubs/audio.pyi +++ b/python_payload/mypystubs/audio.pyi @@ -18,3 +18,14 @@ def set_mute(v: bool) -> None: def get_mute() -> bool: pass + +def input_set_source(source: int) -> None: + pass + +def input_get_source() -> int: + pass + +INPUT_SOURCE_NONE: int +INPUT_SOURCE_LINE_IN: int +INPUT_SOURCE_HEADSET_MIC: int +INPUT_SOURCE_ONBOARD_MIC: int diff --git a/python_payload/mypystubs/bl00mbox/_user.pyi b/python_payload/mypystubs/bl00mbox/_user.pyi index 37956c8d43..1332563d5b 100644 --- a/python_payload/mypystubs/bl00mbox/_user.pyi +++ b/python_payload/mypystubs/bl00mbox/_user.pyi @@ -48,6 +48,7 @@ class Channel: mixer: "ChannelMixer" foreground: bool free: bool + volume: int def __init__(self, name: str): ... def clear(self) -> None: ... -- GitLab