diff --git a/components/bl00mbox/CMakeLists.txt b/components/bl00mbox/CMakeLists.txt index f4fe44122c8535d8fad5b78f94aa5e2c53b5cd59..734fd79ed7087ac0cd2f9e56bc7c6e77f7deb21e 100644 --- a/components/bl00mbox/CMakeLists.txt +++ b/components/bl00mbox/CMakeLists.txt @@ -15,6 +15,10 @@ idf_component_register( plugins/flanger.c plugins/sequencer.c plugins/noise.c + plugins/distortion.c + plugins/lowpass.c + plugins/mixer.c + plugins/slew_rate_limiter.c radspa/radspa_helpers.c extern/xoroshiro64star.c INCLUDE_DIRS diff --git a/components/bl00mbox/bl00mbox_plugin_registry.c b/components/bl00mbox/bl00mbox_plugin_registry.c index e18ca82ce3dba92af5587864d62d57d2a3deb814..278bead36c141f7e8e3e7c01917bf903b20544b8 100644 --- a/components/bl00mbox/bl00mbox_plugin_registry.c +++ b/components/bl00mbox/bl00mbox_plugin_registry.c @@ -91,11 +91,14 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index) #include "env_adsr.h" #include "ampliverter.h" #include "delay.h" -//#include "filter.h" +#include "lowpass.h" #include "sequencer.h" #include "sampler.h" #include "flanger.h" #include "noise.h" +#include "distortion.h" +#include "mixer.h" +#include "slew_rate_limiter.h" void bl00mbox_plugin_registry_init(void){ if(bl00mbox_plugin_registry_is_initialized) return; @@ -103,9 +106,12 @@ void bl00mbox_plugin_registry_init(void){ plugin_add(&liverter_desc); plugin_add(&env_adsr_desc); plugin_add(&delay_desc); -// plugin_add(&filter_desc); + plugin_add(&lowpass_desc); plugin_add(&sequencer_desc); plugin_add(&sampler_desc); plugin_add(&flanger_desc); plugin_add(&noise_desc); + plugin_add(&distortion_desc); + plugin_add(&mixer_desc); + plugin_add(&slew_rate_limiter_desc); } diff --git a/components/bl00mbox/bl00mbox_radspa_requirements.c b/components/bl00mbox/bl00mbox_radspa_requirements.c index dc86121be1530b8fbdea9619d67800ec33ef139b..e5310ff4c1db90d89ebd88b9b79e46bd86c7b154 100644 --- a/components/bl00mbox/bl00mbox_radspa_requirements.c +++ b/components/bl00mbox/bl00mbox_radspa_requirements.c @@ -68,7 +68,8 @@ int16_t radspa_clip(int32_t a){ } int16_t radspa_add_sat(int32_t a, int32_t b){ return radspa_clip(a+b); } -int16_t radspa_mult_shift(int32_t a, int32_t b){ return radspa_clip((a*b)>>15); } +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; diff --git a/components/bl00mbox/bl00mbox_user.c b/components/bl00mbox/bl00mbox_user.c index cf237406d04d35429ac92844c9daedd5f82055d4..e17eccfea9d66913b48a0d16f6fc0dbb060a3af1 100644 --- a/components/bl00mbox/bl00mbox_user.c +++ b/components/bl00mbox/bl00mbox_user.c @@ -654,6 +654,16 @@ char * bl00mbox_channel_bud_get_signal_name(uint8_t channel, uint32_t bud_index, 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; diff --git a/components/bl00mbox/include/bl00mbox_user.h b/components/bl00mbox/include/bl00mbox_user.h index 9e5fe3fb039b602846d8e1d1a6867399f39eabf8..23d0e6e171821b57394254c047ec3789c1b06b47 100644 --- a/components/bl00mbox/include/bl00mbox_user.h +++ b/components/bl00mbox/include/bl00mbox_user.h @@ -36,6 +36,7 @@ uint32_t bl00mbox_channel_bud_get_plugin_id(uint8_t channel, uint32_t bud_index) uint16_t bl00mbox_channel_bud_get_num_signals(uint8_t channel, uint32_t bud_index); char * bl00mbox_channel_bud_get_signal_name(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index); +int8_t bl00mbox_channel_bud_get_signal_name_multiplex(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index); char * bl00mbox_channel_bud_get_signal_description(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index); char * bl00mbox_channel_bud_get_signal_unit(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index); bool bl00mbox_channel_bud_set_signal_value(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index, int16_t value); diff --git a/components/bl00mbox/plugins/brok3n/filter.c b/components/bl00mbox/plugins/brok3n/filter.c deleted file mode 100644 index d9cdd4ff830cc365cd72ab3e6620602db4ec7f1f..0000000000000000000000000000000000000000 --- a/components/bl00mbox/plugins/brok3n/filter.c +++ /dev/null @@ -1,141 +0,0 @@ -#include "filter.h" - -radspa_t * filter_create(uint32_t init_var); -radspa_descriptor_t filter_desc = { - .name = "filter", - .id = 69420, - .description = "simple filter", - .create_plugin_instance = filter_create, - .destroy_plugin_instance = radspa_standard_plugin_destroy -}; - -#define FILTER_NUM_SIGNALS 5 -#define FILTER_OUTPUT 0 -#define FILTER_INPUT 1 -#define FILTER_FREQ 2 -#define FILTER_Q 3 -#define FILTER_GAIN 4 - - -static int16_t apply_filter(filter_data_t * data, int32_t input, int32_t gain){ - data->pos++; - if(data->pos >= 3) data->pos = 0; - - int64_t out = 0; - int64_t in_acc = input; - - for(int8_t i=0; i<2; i++){ - int8_t pos = data->pos - i - 1; - if(pos < 0) pos += 3; - in_acc += (2-i)*data->in_history[pos]; - - int16_t shift = data->out_coeff_shift[i] - data->in_coeff_shift; - if(shift >= 0){ - out -= (data->out_history[pos] * data->out_coeff[i]) >> shift; - } else { - out -= (data->out_history[pos] * data->out_coeff[i]) << (-shift); - } - } - out += in_acc * data->in_coeff; - out = out >> data->in_coeff_shift; - - data->in_history[data->pos] = input; - data->out_history[data->pos] = out; - - return radspa_mult_shift(out, gain); -} - -static uint8_t coeff_shift(float coeff) { - int32_t ret = 16; - while(1){ - int32_t test_val = (1<<ret) * coeff; - if(test_val < 0) test_val = -test_val; - if(test_val > (1<<12)){ - ret -= 3; - } else if (test_val < (1<<8)){ - ret += 3; - } else { - break; - } - if(ret > 28) break; - if(ret < 4) break; - } - return ret; -} - -static void set_filter_coeffs(filter_data_t * data, float freq, float q){ - //molasses, sorry - if(freq == 0) return; - if(q == 0) return; - float K = 2*48000; - float omega = freq * 6.28; - float A[3]; - A[0] = K*K; - A[1] = K*omega/q; - A[2] = omega*omega; - float B = omega*omega; - - float sum_a = A[0] + A[1] + A[2]; - - float round; - int16_t shift; - - round = B / sum_a; - shift = coeff_shift(round); - data->in_coeff = round * (1<<shift); - data->in_coeff_shift = shift; - - round = 2.*(A[2]-A[0]) / sum_a; - shift = coeff_shift(round); - data->out_coeff[0] = round * (1<<shift); - data->out_coeff_shift[0] = shift; - - round = (A[0]-A[1]+A[2]) / sum_a; - shift = coeff_shift(round); - data->out_coeff[1] = round * (1<<shift); - data->out_coeff_shift[1] = shift; -} - -void filter_run(radspa_t * filter, uint16_t num_samples, uint32_t render_pass_id){ - radspa_signal_t * output_sig = radspa_signal_get_by_index(filter, FILTER_OUTPUT); - if(output_sig->buffer == NULL) return; - filter_data_t * data = filter->plugin_data; - radspa_signal_t * input_sig = radspa_signal_get_by_index(filter, FILTER_INPUT); - radspa_signal_t * freq_sig = radspa_signal_get_by_index(filter, FILTER_FREQ); - radspa_signal_t * q_sig = radspa_signal_get_by_index(filter, FILTER_Q); - radspa_signal_t * gain_sig = radspa_signal_get_by_index(filter, FILTER_GAIN); - - static int16_t ret = 0; - - for(uint16_t i = 0; i < num_samples; i++){ - int16_t input = radspa_signal_get_value(input_sig, i, num_samples, render_pass_id); - int32_t freq = radspa_signal_get_value(freq_sig, i, num_samples, render_pass_id); - int16_t q = radspa_signal_get_value(q_sig, i, num_samples, render_pass_id); - int16_t gain = radspa_signal_get_value(gain_sig, i, num_samples, render_pass_id); - - if((freq != data->prev_freq) | (q != data->prev_q)){ - set_filter_coeffs(data, freq, ((float) q + 1.)/1000.); - data->prev_freq = freq; - data->prev_q = q; - } - - ret = apply_filter(data, input, gain); - (output_sig->buffer)[i] = ret; - } - output_sig->value = ret; -} - -radspa_t * filter_create(uint32_t real_init_var){ - radspa_t * filter = radspa_standard_plugin_create(&filter_desc, FILTER_NUM_SIGNALS, sizeof(filter_data_t), 0); - if(filter == NULL) return NULL; - filter->render = filter_run; - radspa_signal_set(filter, FILTER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); - radspa_signal_set(filter, FILTER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0); - radspa_signal_set(filter, FILTER_FREQ, "freq", RADSPA_SIGNAL_HINT_INPUT, 500); - radspa_signal_set(filter, FILTER_Q, "reso", RADSPA_SIGNAL_HINT_INPUT, 1000); - radspa_signal_set(filter, FILTER_GAIN, "gain", RADSPA_SIGNAL_HINT_INPUT, 32760); - filter_data_t * data = filter->plugin_data; - data->pos = 0; - data->prev_freq = 1<<24; - return filter; -} diff --git a/components/bl00mbox/plugins/brok3n/filter.h b/components/bl00mbox/plugins/brok3n/filter.h deleted file mode 100644 index 36e3d1e93c06e5b01ef76a9e76a9360be44aa07f..0000000000000000000000000000000000000000 --- a/components/bl00mbox/plugins/brok3n/filter.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include <radspa.h> - -typedef struct { - int64_t in_history[3]; - int64_t in_coeff; - int16_t in_coeff_shift; - int64_t out_history[3]; - int64_t out_coeff[2]; - int16_t out_coeff_shift[2]; - - int32_t prev_freq; - int16_t prev_q; - int8_t pos; -} filter_data_t; - -extern radspa_descriptor_t filter_desc; -radspa_t * filter_create(uint32_t init_var); -void filter_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id); diff --git a/components/bl00mbox/plugins/delay.c b/components/bl00mbox/plugins/delay.c index 756e703d72df7d4228bc9f21632a1b839de702b3..d87218e4010a30ac382a5a46832eab586ae522c5 100644 --- a/components/bl00mbox/plugins/delay.c +++ b/components/bl00mbox/plugins/delay.c @@ -41,7 +41,9 @@ void delay_run(radspa_t * delay, uint16_t num_samples, uint32_t render_pass_id){ data->write_head_position++; while(data->write_head_position >= buffer_size) data->write_head_position -= buffer_size; // maybe faster than % if(time != data->time_prev){ - data->read_head_position = time * (48000/1000) + data->write_head_position; + data->read_head_position = data->write_head_position; + data->read_head_position = - time * (48000/1000); + if(data->read_head_position < 0) data->read_head_position += buffer_size; data->time_prev = time; } else { data->read_head_position++; @@ -72,11 +74,13 @@ void delay_run(radspa_t * delay, uint16_t num_samples, uint32_t render_pass_id){ radspa_t * delay_create(uint32_t init_var){ if(init_var == 0) init_var = 500; + if(init_var > 10000) init_var = 10000; uint32_t buffer_size = init_var*(48000/1000); radspa_t * delay = radspa_standard_plugin_create(&delay_desc, DELAY_NUM_SIGNALS, sizeof(delay_data_t), buffer_size); if(delay == NULL) return NULL; delay_data_t * plugin_data = delay->plugin_data; + plugin_data->time_prev = UINT32_MAX; plugin_data->max_delay = init_var; delay->render = delay_run; radspa_signal_set(delay, DELAY_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); diff --git a/components/bl00mbox/plugins/delay.h b/components/bl00mbox/plugins/delay.h index c0882a1085164432b99351c98871834d39e1ad94..9519edd44abe7a00de27111833be11f6dd2b021d 100644 --- a/components/bl00mbox/plugins/delay.h +++ b/components/bl00mbox/plugins/delay.h @@ -3,7 +3,7 @@ #include <radspa_helpers.h> typedef struct { - uint32_t read_head_position; + int32_t read_head_position; uint32_t write_head_position; uint32_t max_delay; uint32_t time_prev; diff --git a/components/bl00mbox/plugins/distortion.c b/components/bl00mbox/plugins/distortion.c new file mode 100644 index 0000000000000000000000000000000000000000..4dfcb404b8be7c4a7656fa0b3240b70ed8bd78c7 --- /dev/null +++ b/components/bl00mbox/plugins/distortion.c @@ -0,0 +1,55 @@ +#include "distortion.h" + +radspa_t * distortion_create(uint32_t init_var); +radspa_descriptor_t distortion_desc = { + .name = "distortion", + .id = 9000, + .description = "distortion with linear interpolation between int16 values in plugin_table[129]", + .create_plugin_instance = distortion_create, + .destroy_plugin_instance = radspa_standard_plugin_destroy +}; + +#define DISTORTION_NUM_SIGNALS 2 +#define DISTORTION_OUTPUT 0 +#define DISTORTION_INPUT 1 + +void distortion_run(radspa_t * distortion, uint16_t num_samples, uint32_t render_pass_id){ + radspa_signal_t * output_sig = radspa_signal_get_by_index(distortion, DISTORTION_OUTPUT); + if(output_sig->buffer == NULL) return; + int16_t * dist = distortion->plugin_table; + radspa_signal_t * input_sig = radspa_signal_get_by_index(distortion, DISTORTION_INPUT); + + static int32_t ret = 0; + + for(uint16_t i = 0; i < num_samples; i++){ + + int32_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id); + input += 32768; + uint8_t index = input>>9; + 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->value = ret; +} + +radspa_t * distortion_create(uint32_t init_var){ + radspa_t * distortion = radspa_standard_plugin_create(&distortion_desc, DISTORTION_NUM_SIGNALS, 0, (1<<7) + 1); + if(distortion == NULL) return NULL; + distortion->render = distortion_run; + radspa_signal_set(distortion, DISTORTION_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(distortion, DISTORTION_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0); + + // prefill table with mild saturation + int16_t * dist = distortion->plugin_table; + for(int32_t i = 0; i < ((1<<7)+1) ; i++){ + if(i < 64){ + dist[i] = ((i*i)*32767>>12) - 32767; + } else { + dist[i] = -((128-i)*(128-i)*32767>>12) + 32767; + } + } + + return distortion; +} diff --git a/components/bl00mbox/plugins/distortion.h b/components/bl00mbox/plugins/distortion.h new file mode 100644 index 0000000000000000000000000000000000000000..87455bad8d1e3a903d0bea099254ec9a6c3029ab --- /dev/null +++ b/components/bl00mbox/plugins/distortion.h @@ -0,0 +1,8 @@ +#pragma once +#include <radspa.h> +#include <radspa_helpers.h> + +extern radspa_descriptor_t distortion_desc; +radspa_t * distortion_create(uint32_t init_var); +void distortion_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id); + diff --git a/components/bl00mbox/plugins/flanger.c b/components/bl00mbox/plugins/flanger.c index 085ff80b62f0537f0ef2decd9ae9f643712cd4a6..1f68d48188018f1bc8be76241d807383641cd13d 100644 --- a/components/bl00mbox/plugins/flanger.c +++ b/components/bl00mbox/plugins/flanger.c @@ -77,7 +77,7 @@ void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_ int16_t dry_vol = (mix>0) ? (32767-mix) : (32767+mix); //always pos polarity ret = radspa_add_sat(radspa_mult_shift(dry, dry_vol), radspa_mult_shift(radspa_clip(wet), mix)); - ret = radspa_clip(radspa_mult_shift(ret, level)); + ret = radspa_clip(radspa_gain(ret, level)); (output_sig->buffer)[i] = ret; } output_sig->value = ret; @@ -93,8 +93,10 @@ radspa_t * flanger_create(uint32_t init_var){ radspa_signal_set(flanger, FLANGER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); radspa_signal_set(flanger, FLANGER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0); radspa_signal_set(flanger, FLANGER_MANUAL, "manual", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, 18367); - radspa_signal_set(flanger, FLANGER_RESONANCE, "resonance", RADSPA_SIGNAL_HINT_INPUT, 16000); - radspa_signal_set(flanger, FLANGER_LEVEL, "level", RADSPA_SIGNAL_HINT_INPUT, 16000); + radspa_signal_set(flanger, FLANGER_RESONANCE, "resonance", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, + RADSPA_SIGNAL_VAL_UNITY_GAIN/2); + radspa_signal_set(flanger, FLANGER_LEVEL, "level", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, + RADSPA_SIGNAL_VAL_UNITY_GAIN); radspa_signal_set(flanger, FLANGER_MIX, "mix", RADSPA_SIGNAL_HINT_INPUT, 1<<14); return flanger; } diff --git a/components/bl00mbox/plugins/lowpass.c b/components/bl00mbox/plugins/lowpass.c new file mode 100644 index 0000000000000000000000000000000000000000..f2087ed4880cf4acf394b0cb164a3245a5b9e285 --- /dev/null +++ b/components/bl00mbox/plugins/lowpass.c @@ -0,0 +1,140 @@ +#include "lowpass.h" + +radspa_t * lowpass_create(uint32_t init_var); +radspa_descriptor_t lowpass_desc = { + .name = "lowpass", + .id = 69420, + .description = "2nd order lowpass lowpass, runs rly sluggish rn, sowy", + .create_plugin_instance = lowpass_create, + .destroy_plugin_instance = radspa_standard_plugin_destroy +}; + +#define LOWPASS_NUM_SIGNALS 5 +#define LOWPASS_OUTPUT 0 +#define LOWPASS_INPUT 1 +#define LOWPASS_FREQ 2 +#define LOWPASS_Q 3 +#define LOWPASS_GAIN 4 + +static float apply_lowpass(lowpass_data_t * data, int32_t input){ + data->pos++; + if(data->pos >= 3) data->pos = 0; + + float out = 0; + float in_acc = input; + + for(int8_t i = 0; i<2; i++){ + int8_t pos = data->pos - i - 1; + if(pos < 0) pos += 3; + in_acc += (2-i)*data->in_history[pos]; + + out -= (data->out_history[pos] * data->out_coeff[i]); + } + out += in_acc * data->in_coeff; + + data->in_history[data->pos] = input; + data->out_history[data->pos] = out; + + return out; +} + +static uint8_t coeff_shift(float coeff) { + int32_t ret = 16; + while(1){ + int32_t test_val = (1<<ret) * coeff; + if(test_val < 0) test_val = -test_val; + if(test_val > (1<<12)){ + ret -= 3; + } else if (test_val < (1<<8)){ + ret += 3; + } else { + break; + } + if(ret > 28) break; + if(ret < 4) break; + } + return ret; +} + +static void set_lowpass_coeffs(lowpass_data_t * data, float freq, float q){ + //molasses, sorry + if(freq == 0) return; + if(q == 0) return; + float K = 2*48000; + float omega = freq * 6.28; + float A[3]; + A[0] = K*K; + A[1] = K*omega/q; + A[2] = omega*omega; + float B = omega*omega; + + float sum_a = A[0] + A[1] + A[2]; + + float round; + //int16_t shift; + + round = B / sum_a; + //shift = coeff_shift(round); + data->in_coeff = round;// * (1<<shift); + //data->in_coeff_shift = shift; + + round = 2.*(A[2]-A[0]) / sum_a; + //shift = coeff_shift(round); + data->out_coeff[0] = round;// * (1<<shift); + //data->out_coeff_shift[0] = shift; + + round = (A[0]-A[1]+A[2]) / sum_a; + //shift = coeff_shift(round); + data->out_coeff[1] = round;// * (1<<shift); + //data->out_coeff_shift[1] = shift; +} + +void lowpass_run(radspa_t * lowpass, uint16_t num_samples, uint32_t render_pass_id){ + radspa_signal_t * output_sig = radspa_signal_get_by_index(lowpass, LOWPASS_OUTPUT); + if(output_sig->buffer == NULL) return; + lowpass_data_t * data = lowpass->plugin_data; + radspa_signal_t * input_sig = radspa_signal_get_by_index(lowpass, LOWPASS_INPUT); + radspa_signal_t * freq_sig = radspa_signal_get_by_index(lowpass, LOWPASS_FREQ); + 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); + int16_t q = q_sig->get_value(q_sig, i, num_samples, render_pass_id); + int16_t gain = gain_sig->get_value(gain_sig, i, num_samples, render_pass_id); + + if((freq != data->prev_freq) | (q != data->prev_q)){ + set_lowpass_coeffs(data, freq, ((float) q + 1.)/1000.); + data->prev_freq = freq; + data->prev_q = q; + } + + 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->value = ret; +} + +radspa_t * lowpass_create(uint32_t real_init_var){ + radspa_t * lowpass = radspa_standard_plugin_create(&lowpass_desc, LOWPASS_NUM_SIGNALS, sizeof(lowpass_data_t), 0); + if(lowpass == NULL) return NULL; + lowpass->render = lowpass_run; + radspa_signal_set(lowpass, LOWPASS_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(lowpass, LOWPASS_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(lowpass, LOWPASS_FREQ, "freq", RADSPA_SIGNAL_HINT_INPUT, 500); + radspa_signal_set(lowpass, LOWPASS_Q, "reso", RADSPA_SIGNAL_HINT_INPUT, 1000); + radspa_signal_set(lowpass, LOWPASS_GAIN, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, + RADSPA_SIGNAL_VAL_UNITY_GAIN); + lowpass_data_t * data = lowpass->plugin_data; + data->pos = 0; + data->prev_freq = 1<<24; + for(uint8_t i = 0; i < 3;i++){ + data->in_history[i] = 0.; + data->out_history[i] = 0.; + } + return lowpass; +} diff --git a/components/bl00mbox/plugins/lowpass.h b/components/bl00mbox/plugins/lowpass.h new file mode 100644 index 0000000000000000000000000000000000000000..d0b138eebabc6edc3efbf9e89d54013a184b1667 --- /dev/null +++ b/components/bl00mbox/plugins/lowpass.h @@ -0,0 +1,20 @@ +#pragma once +#include <radspa.h> +#include <radspa_helpers.h> + +typedef struct { + float in_history[3]; + float in_coeff; + //int16_t in_coeff_shift; + float out_history[3]; + float out_coeff[2]; + //int16_t out_coeff_shift[2]; + + int32_t prev_freq; + int16_t prev_q; + int8_t pos; +} lowpass_data_t; + +extern radspa_descriptor_t lowpass_desc; +radspa_t * lowpass_create(uint32_t init_var); +void lowpass_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id); diff --git a/components/bl00mbox/plugins/mixer.c b/components/bl00mbox/plugins/mixer.c new file mode 100644 index 0000000000000000000000000000000000000000..501662c77a962cf313891cba77e3d38925505d73 --- /dev/null +++ b/components/bl00mbox/plugins/mixer.c @@ -0,0 +1,52 @@ +#include "mixer.h" + +radspa_descriptor_t mixer_desc = { + .name = "mixer", + .id = 21, + .description = "sums input and applies output gain\n init_var: number of inputs, 1-127, default 4", + .create_plugin_instance = mixer_create, + .destroy_plugin_instance = radspa_standard_plugin_destroy +}; + +void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){ + radspa_signal_t * output_sig = radspa_signal_get_by_index(mixer, 0); + if(output_sig->buffer == NULL) return; + radspa_signal_t * gain_sig = radspa_signal_get_by_index(mixer, 1); + + uint8_t num_inputs = mixer->len_signals - 2; + radspa_signal_t * input_sigs[num_inputs]; + for(uint8_t i = 0; i < num_inputs; i++){ + input_sigs[i] = radspa_signal_get_by_index(mixer, 2 + i); + } + + int32_t * dc_acc = mixer->plugin_data; + + static int32_t ret = 0; + for(uint16_t i = 0; i < num_samples; i++){ + int16_t gain = gain_sig->get_value(gain_sig, i, num_samples, render_pass_id); + ret = 0; + for(uint8_t j = 0; j < num_inputs; j++){ + ret += input_sigs[j]->get_value(input_sigs[j], i, num_samples, 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)); + } + output_sig->value = ret; +} + +radspa_t * mixer_create(uint32_t init_var){ + if(init_var == 0) init_var = 4; + if(init_var > 127) init_var = 127; + radspa_t * mixer = radspa_standard_plugin_create(&mixer_desc, 2 + init_var, sizeof(int32_t), 0); + if(mixer == NULL) return NULL; + mixer->render = mixer_run; + radspa_signal_set(mixer, 0, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + int16_t gain = RADSPA_SIGNAL_VAL_UNITY_GAIN/init_var; + radspa_signal_set(mixer, 1, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, gain); + radspa_signal_set_group(mixer, init_var, 2, "input", RADSPA_SIGNAL_HINT_INPUT, 0); + int32_t * dc_acc = mixer->plugin_data; + (* dc_acc) = 0; + return mixer; +} diff --git a/components/bl00mbox/plugins/mixer.h b/components/bl00mbox/plugins/mixer.h new file mode 100644 index 0000000000000000000000000000000000000000..79d6236c2b496fca4c0976c0cec8ec4e568d70f4 --- /dev/null +++ b/components/bl00mbox/plugins/mixer.h @@ -0,0 +1,8 @@ +#pragma once +#include <radspa.h> +#include <radspa_helpers.h> + +extern radspa_descriptor_t mixer_desc; +radspa_t * mixer_create(uint32_t init_var); +void mixer_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id); + diff --git a/components/bl00mbox/plugins/noise.c b/components/bl00mbox/plugins/noise.c index 3c0cb2bed7d02d166037defb0a16aeac1c13767f..364456f5178c88d3f928cf9824f76aee20766553 100644 --- a/components/bl00mbox/plugins/noise.c +++ b/components/bl00mbox/plugins/noise.c @@ -31,6 +31,3 @@ radspa_t * noise_create(uint32_t init_var){ radspa_signal_set(noise, NOISE_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); return noise; } - -#undef NOISE_NUM_SIGNALS -#undef NOISE_OUTPUT diff --git a/components/bl00mbox/plugins/osc_fm.c b/components/bl00mbox/plugins/osc_fm.c index ed525eec33665b76c1051576a784915efadfadcc..750a8ee3bb575792fce34fbcea2abc8e423bb724 100644 --- a/components/bl00mbox/plugins/osc_fm.c +++ b/components/bl00mbox/plugins/osc_fm.c @@ -66,7 +66,7 @@ static inline int16_t triangle(int16_t saw){ return tmp; } -inline int16_t waveshaper(int16_t saw, int16_t shape){ +static inline int16_t waveshaper(int16_t saw, int16_t shape){ int32_t tmp = saw; uint8_t sh = ((uint16_t) shape) >> 14; sh = (sh + 2)%4; diff --git a/components/bl00mbox/plugins/slew_rate_limiter.c b/components/bl00mbox/plugins/slew_rate_limiter.c new file mode 100644 index 0000000000000000000000000000000000000000..f1907b7c74dcd6995d6011fcc927f9354acdfaee --- /dev/null +++ b/components/bl00mbox/plugins/slew_rate_limiter.c @@ -0,0 +1,51 @@ +#include "slew_rate_limiter.h" + +static inline int16_t waveshaper(int16_t saw, int16_t shape); + +radspa_descriptor_t slew_rate_limiter_desc = { + .name = "slew_rate_limiter", + .id = 23, + .description = "very cheap nonlinear filter", + .create_plugin_instance = slew_rate_limiter_create, + .destroy_plugin_instance = radspa_standard_plugin_destroy +}; + +#define SLEW_RATE_LIMITER_NUM_SIGNALS 3 +#define SLEW_RATE_LIMITER_OUTPUT 0 +#define SLEW_RATE_LIMITER_INPUT 1 +#define SLEW_RATE_LIMITER_SLEW_RATE 2 + +radspa_t * slew_rate_limiter_create(uint32_t init_var){ + radspa_t * slew_rate_limiter = radspa_standard_plugin_create(&slew_rate_limiter_desc, SLEW_RATE_LIMITER_NUM_SIGNALS, sizeof(slew_rate_limiter_data_t), 0); + slew_rate_limiter->render = slew_rate_limiter_run; + radspa_signal_set(slew_rate_limiter, SLEW_RATE_LIMITER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(slew_rate_limiter, SLEW_RATE_LIMITER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(slew_rate_limiter, SLEW_RATE_LIMITER_SLEW_RATE, "slew_rate", RADSPA_SIGNAL_HINT_INPUT, 1000); + return slew_rate_limiter; +} + +void slew_rate_limiter_run(radspa_t * slew_rate_limiter, uint16_t num_samples, uint32_t render_pass_id){ + radspa_signal_t * output_sig = radspa_signal_get_by_index(slew_rate_limiter, SLEW_RATE_LIMITER_OUTPUT); + if(output_sig->buffer == NULL) return; + radspa_signal_t * input_sig = radspa_signal_get_by_index(slew_rate_limiter, SLEW_RATE_LIMITER_INPUT); + radspa_signal_t * slew_rate_sig = radspa_signal_get_by_index(slew_rate_limiter, SLEW_RATE_LIMITER_SLEW_RATE); + + slew_rate_limiter_data_t * data = slew_rate_limiter->plugin_data; + + int32_t ret = 0; + for(uint16_t i = 0; i < num_samples; i++){ + int32_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id); + int32_t slew_rate = (uint16_t) slew_rate_sig->get_value(slew_rate_sig, i, num_samples, render_pass_id); + ret = data->prev; + if(input - ret > slew_rate){ + ret += slew_rate; + } else if(ret - input > slew_rate){ + ret -= slew_rate; + } else { + ret = input; + } + (output_sig->buffer)[i] = ret; + } + output_sig->value = ret; +} + diff --git a/components/bl00mbox/plugins/slew_rate_limiter.h b/components/bl00mbox/plugins/slew_rate_limiter.h new file mode 100644 index 0000000000000000000000000000000000000000..4c92240b201abab055a92d0493dfa36f3ee54988 --- /dev/null +++ b/components/bl00mbox/plugins/slew_rate_limiter.h @@ -0,0 +1,12 @@ +#pragma once +#include "radspa.h" +#include "radspa_helpers.h" + +typedef struct { + int32_t prev; +} slew_rate_limiter_data_t; + +extern radspa_descriptor_t slew_rate_limiter_desc; +radspa_t * slew_rate_limiter_create(uint32_t init_var); +void slew_rate_limiter_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id); + diff --git a/components/bl00mbox/radspa/radspa.h b/components/bl00mbox/radspa/radspa.h index 5ad964d53b376cc65ed7437a615169965503b1fc..6e018c4a1a9747a75b3a8e4f22ef9ecc3bb63e5b 100644 --- a/components/bl00mbox/radspa/radspa.h +++ b/components/bl00mbox/radspa/radspa.h @@ -41,11 +41,11 @@ #define RADSPA_SIGNAL_HINT_INPUT (1<<0) #define RADSPA_SIGNAL_HINT_OUTPUT (1<<1) #define RADSPA_SIGNAL_HINT_TRIGGER (1<<2) -#define RADSPA_SIGNAL_HINT_VOL (1<<3) +#define RADSPA_SIGNAL_HINT_GAIN (1<<3) #define RADSPA_SIGNAL_HINT_SCT (1<<5) #define RADSPA_SIGNAL_VAL_SCT_A440 (INT16_MAX - 6*2400) -#define RADSPA_SIGNAL_VAL_UNITY_GAIN (1<<11) +#define RADSPA_SIGNAL_VAL_UNITY_GAIN (1<<12) struct _radspa_descriptor_t; struct _radspa_signal_t; @@ -115,7 +115,9 @@ extern int16_t radspa_clip(int32_t a); // saturating int16 addition extern int16_t radspa_add_sat(int32_t a, int32_t b); // (a*b)>>15 -extern int16_t radspa_mult_shift(int32_t a, int32_t b); +extern int32_t radspa_mult_shift(int32_t a, int32_t b); +// (a*b)>>12 +extern int32_t radspa_gain(int32_t a, int32_t b); extern int16_t radspa_trigger_start(int16_t velocity, int16_t * hist); extern int16_t radspa_trigger_stop(int16_t * hist); diff --git a/components/bl00mbox/radspa/radspa_helpers.c b/components/bl00mbox/radspa/radspa_helpers.c index a557e51b831cdbf94362a2bffdc3cbcbed8fbe0e..4d9f3d01d9a1d7be14eabb728cb09d3cc6b78f67 100644 --- a/components/bl00mbox/radspa/radspa_helpers.c +++ b/components/bl00mbox/radspa/radspa_helpers.c @@ -18,6 +18,18 @@ void radspa_signal_set(radspa_t * plugin, uint8_t signal_index, char * name, uin sig->value = value; } +void radspa_signal_set_group(radspa_t * plugin, uint8_t group_len, uint8_t signal_index, char * name, + uint32_t hints, int16_t value){ + for(uint8_t i = 0; i < group_len; i++){ + radspa_signal_t * sig = radspa_signal_get_by_index(plugin, signal_index + i); + if(sig == NULL) return; + sig->name = name; + sig->hints = hints; + sig->value = value; + sig->name_multiplex = i; + } +} + int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_t value){ radspa_signal_t * sig = calloc(1,sizeof(radspa_signal_t)); if(sig == NULL) return -1; // allocation failed diff --git a/components/bl00mbox/radspa/radspa_helpers.h b/components/bl00mbox/radspa/radspa_helpers.h index e1b9fa0eb852a38869384d6ee1161e7bb23a9557..11b9709ac5ae220e25891fd192a2129880cdc103 100644 --- a/components/bl00mbox/radspa/radspa_helpers.h +++ b/components/bl00mbox/radspa/radspa_helpers.h @@ -6,10 +6,15 @@ int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_t value); // as above, but sets parameters of an already existing signal with at list position signal_index void radspa_signal_set(radspa_t * plugin, uint8_t signal_index, char * name, uint32_t hints, int16_t value); +void radspa_signal_set_group(radspa_t * plugin, uint8_t group_len, uint8_t signal_index, char * name, + uint32_t hints, int16_t value); + + // get signal struct from a signal index radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t signal_index); -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 * radspa_standard_plugin_create(radspa_descriptor_t * desc, uint8_t num_signals, size_t plugin_data_size, + uint32_t plugin_table_size); void radspa_standard_plugin_destroy(radspa_t * plugin); // frees all signal structs. typically used to destroy a plugin instance. diff --git a/components/micropython/usermodule/mp_sys_bl00mbox.c b/components/micropython/usermodule/mp_sys_bl00mbox.c index 40668da111bcbdadc6b72b8ffe31261ffbbea9a5..ecd10b25f880c00e2c83a990cec461b4aed094b8 100644 --- a/components/micropython/usermodule/mp_sys_bl00mbox.c +++ b/components/micropython/usermodule/mp_sys_bl00mbox.c @@ -7,6 +7,7 @@ #include "bl00mbox.h" #include "bl00mbox_plugin_registry.h" #include "bl00mbox_user.h" +#include "radspa.h" // ======================== // PLUGIN OPERATIONS @@ -233,6 +234,16 @@ STATIC mp_obj_t mp_channel_bud_get_signal_name(mp_obj_t chan, mp_obj_t bud, STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_name_obj, mp_channel_bud_get_signal_name); +STATIC mp_obj_t mp_channel_bud_get_signal_name_multiplex(mp_obj_t chan, + mp_obj_t bud, + mp_obj_t signal) { + int8_t ret = bl00mbox_channel_bud_get_signal_name_multiplex( + mp_obj_get_int(chan), mp_obj_get_int(bud), mp_obj_get_int(signal)); + return mp_obj_new_int(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_name_multiplex_obj, + mp_channel_bud_get_signal_name_multiplex); + STATIC mp_obj_t mp_channel_bud_get_signal_description(mp_obj_t chan, mp_obj_t bud, mp_obj_t signal) { @@ -488,6 +499,8 @@ STATIC const mp_map_elem_t bl00mbox_globals_table[] = { // SIGNAL OPERATIONS { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_name), MP_ROM_PTR(&mp_channel_bud_get_signal_name_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_name_multiplex), + MP_ROM_PTR(&mp_channel_bud_get_signal_name_multiplex_obj) }, { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_description), MP_ROM_PTR(&mp_channel_bud_get_signal_description_obj) }, { MP_ROM_QSTR(MP_QSTR_channel_bud_get_signal_unit), @@ -531,6 +544,12 @@ STATIC const mp_map_elem_t bl00mbox_globals_table[] = { // CONSTANTS { MP_ROM_QSTR(MP_QSTR_NUM_CHANNELS), MP_ROM_INT(BL00MBOX_CHANNELS) }, + { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_SCT), + MP_ROM_INT(RADSPA_SIGNAL_HINT_SCT) }, + { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_GAIN), + MP_ROM_INT(RADSPA_SIGNAL_HINT_GAIN) }, + { MP_ROM_QSTR(MP_QSTR_RADSPA_SIGNAL_HINT_TRIGGER), + MP_ROM_INT(RADSPA_SIGNAL_HINT_TRIGGER) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_bl00mbox_globals, bl00mbox_globals_table); diff --git a/python_payload/apps/shoegaze/__init__.py b/python_payload/apps/shoegaze/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7726f43a72f81a70bb36158b3745a5455e0fa738 --- /dev/null +++ b/python_payload/apps/shoegaze/__init__.py @@ -0,0 +1,208 @@ +from st3m.application import Application, ApplicationContext +from st3m.goose import Dict, Any, List +from st3m.input import InputState +from ctx import Context + +import json +import math +import captouch +import bl00mbox + +import leds +import hardware + +chords = [ + [-5, -5, 2, 7, 10], + [-4, -4, 0, 5, 8], + [-10, -2, 5, 8, 12], + [-12, 0, 3, 10, 15], + [-9, 3, 7, 14, 15], +] + +from st3m.application import Application, ApplicationContext + + +class ShoegazeApp(Application): + def __init__(self, app_ctx: ApplicationContext) -> None: + super().__init__(app_ctx) + + self.blm = bl00mbox.Channel() + + self.delay_on = True + self.fuzz_on = True + self.chord_index = 0 + self.chord: List[int] = [] + self.main_lp = self.blm.new(bl00mbox.plugins.lowpass) + self.main_fuzz = self.blm.new(bl00mbox.patches.fuzz) + self.main_mixer = self.blm.new(bl00mbox.plugins.mixer, 2) + self.git_strings = [ + self.blm.new(bl00mbox.patches.karplus_strong) for i in range(4) + ] + self.bass_string = self.blm.new(bl00mbox.patches.karplus_strong) + self.git_mixer = self.blm.new(bl00mbox.plugins.mixer, 4) + self.git_fuzz = self.blm.new(bl00mbox.patches.fuzz) + self.git_delay = self.blm.new(bl00mbox.plugins.delay) + self.git_lp = self.blm.new(bl00mbox.plugins.lowpass) + self.bass_lp = self.blm.new(bl00mbox.plugins.lowpass) + + self.git_mixer.signals.input0 = self.git_strings[0].signals.output + self.git_mixer.signals.input1 = self.git_strings[1].signals.output + self.git_mixer.signals.input2 = self.git_strings[2].signals.output + self.git_mixer.signals.input3 = self.git_strings[3].signals.output + self.git_mixer.signals.output = self.git_lp.signals.input + self.git_fuzz.buds.dist.signals.input = self.git_lp.signals.output + + self.bass_lp.signals.input = self.bass_string.signals.output + self.main_mixer.signals.input1 = self.bass_lp.signals.output + + self.main_fuzz.buds.dist.signals.input = self.main_mixer.signals.output + self.main_fuzz.buds.dist.signals.output = self.main_lp.signals.input + self.main_lp.signals.output = self.blm.mixer + + self.git_delay.signals.time = 200 + self.git_delay.signals.dry_vol = 32767 + self.git_delay.signals.level = 16767 + self.git_delay.signals.feedback = 27000 + + self.git_fuzz.intensity = 8 + self.git_fuzz.gate = 1500 + self.git_fuzz.volume = 12000 + self.git_lp.signals.freq = 700 + self.git_lp.signals.reso = 2500 + + self.bass_lp.signals.freq = 400 + self.bass_lp.signals.reso = 3000 + self.main_fuzz.intensity = 8 + self.main_fuzz.gate = 1500 + self.main_fuzz.volume = 32000 + + self.main_mixer.signals.gain = 2000 + self.main_lp.signals.reso = 2000 + + self.cp_prev = captouch.read() + + self._set_chord(3) + self.prev_captouch = [0] * 10 + self._tilt_bias = 0.0 + self._detune_prev = 0.0 + self._git_string_tuning = [0] * 4 + self._button_prev = 0 + self._update_connections() + + def _update_connections(self) -> None: + if self.fuzz_on: + self.bass_lp.signals.gain = 32767 + self.git_lp.signals.gain = 32767 + self.main_lp.signals.freq = 2500 + self.main_lp.signals.gain = 2000 + self.git_mixer.signals.gain = 4000 + self.main_lp.signals.input = self.main_fuzz.buds.dist.signals.output + if self.delay_on: + self.git_delay.signals.input = self.git_fuzz.buds.dist.signals.output + self.main_mixer.signals.input0 = self.git_delay.signals.output + else: + self.main_mixer.signals.input0 = self.git_fuzz.buds.dist.signals.output + else: + self.bass_lp.signals.gain = 2000 + self.git_lp.signals.gain = 2000 + self.main_lp.signals.freq = 6000 + self.main_lp.signals.gain = 4000 + self.git_mixer.signals.gain = 500 + self.main_lp.signals.input = self.main_mixer.signals.output + if self.delay_on: + self.git_delay.signals.input = self.git_lp.signals.output + self.main_mixer.signals.input0 = self.git_delay.signals.output + else: + self.main_mixer.signals.input0 = self.git_lp.signals.output + + def fuzz_toggle(self) -> None: + self.fuzz_on = not self.fuzz_on + self._update_connections() + + def delay_toggle(self) -> None: + self.delay_on = not self.delay_on + self._update_connections() + + def _set_chord(self, i: int) -> None: + hue = int(72 * (i + 0.5)) % 360 + if i != self.chord_index: + self.chord_index = i + for j in range(40): + leds.set_hsv(j, hue, 1, 0.2) + self.chord = chords[i] + leds.update() + + def draw(self, ctx: Context) -> None: + ctx.text_align = ctx.CENTER + ctx.font = ctx.get_font_name(5) + ctx.font_size = 30 + + ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() + + ctx.move_to(60, 60) + if self.delay_on: + ctx.rgb(0, 255, 0) + ctx.text("delay ON!") + else: + ctx.rgb(255, 0, 0) + ctx.text("delay off") + + ctx.move_to(50, -50) + detune = "detune: " + str(int(self._detune_prev * 100)) + ctx.rgb(0, 255, 0) + ctx.text(detune).rotate(-10) + + ctx.move_to(-50, 50) + if self.fuzz_on: + ctx.rgb(0, 255, 0) + ctx.text("fuzz ON!") + else: + ctx.rgb(255, 0, 0) + ctx.text("fuzz off") + ctx.restore() + + def think(self, ins: InputState, delta_ms: int) -> None: + super().think(ins, delta_ms) + + y = ins.imu.acc[0] + x = ins.imu.acc[1] + tilt = (y**2) + (x**2) + tilt = tilt / 50 + self._tilt_bias = 0.99 * self._tilt_bias + 0.01 * tilt + detune = (tilt - self._tilt_bias) * 0.5 + self._detune_prev * 0.5 + self._detune_prev = detune + + cts = captouch.read() + button = ins.left_button + if button != self._button_prev: + if button == 1: + self.delay_toggle() + if button == -1: + pass + # self.fuzz_toggle() + self._button_prev = button + + for i in range(1, 10, 2): + if cts.petals[i].pressed and (not self.cp_prev.petals[i].pressed): + k = int((i - 1) / 2) + self._set_chord(k) + + for i in range(2, 10, 2): + k = int(i / 2) - 1 + if cts.petals[i].pressed and (not self.cp_prev.petals[i].pressed): + self._git_string_tuning[k] = self.chord[k] - 12 + self.git_strings[k].signals.pitch.tone = self._git_string_tuning[k] + self.git_strings[k].decay = 3000 + self.git_strings[k].signals.trigger.start() + + self.git_strings[k].signals.pitch.tone = self._git_string_tuning[k] + detune + + if cts.petals[0].pressed and (not self.cp_prev.petals[0].pressed): + self.bass_string.signals.pitch.tone = self.chord[0] - 24 + self.bass_string.decay = 1000 + self.bass_string.signals.trigger.start() + + self.cp_prev = cts + + def __del__(self) -> None: + self.blm.clear() diff --git a/python_payload/apps/shoegaze/flow3r.toml b/python_payload/apps/shoegaze/flow3r.toml new file mode 100644 index 0000000000000000000000000000000000000000..067e5746f57e754538b51f72f87603df224bfbc4 --- /dev/null +++ b/python_payload/apps/shoegaze/flow3r.toml @@ -0,0 +1,11 @@ +[app] +name = "shoegaze" +menu = "Music" + +[entry] +class = "ShoegazeApp" + +[metadata] +author = "Flow3r Badge Authors" +license = "LGPL-3.0-only" +url = "https://git.flow3r.garden/flow3r/flow3r-firmware" diff --git a/python_payload/bl00mbox/__init__.py b/python_payload/bl00mbox/__init__.py index 92acbc2a6557dd7215a1e118ff9908e5e7b83399..6f4058f79d0c57320640a8c483e797c273f29cc1 100644 --- a/python_payload/bl00mbox/__init__.py +++ b/python_payload/bl00mbox/__init__.py @@ -1,519 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 -import sys_bl00mbox - -# note: consider the 'sys_bl00mbox' api super unstable for now pls :3 -import math - +from bl00mbox._user import * import bl00mbox._patches as patches import bl00mbox._helpers as helpers -from bl00mbox._patches import _Patch as PatchType from bl00mbox._plugins import plugins -from bl00mbox._plugins import _Plugin as PluginType - - -class Bl00mboxError(Exception): - pass - - -def _makeSignal(bud, signal_num): - hints = sys_bl00mbox.channel_bud_get_signal_hints( - bud.channel_num, bud.bud_num, signal_num - ) - if hints & 2: - signal = SignalOutput(bud, signal_num) - signal._hints = "output" - elif hints & 1: - if hints & 4: - signal = SignalInputTrigger(bud, signal_num) - signal._hints = "input/trigger" - elif hints & 32: - signal = SignalInputPitch(bud, signal_num) - signal._hints = "input/pitch" - else: - signal = SignalInput(bud, signal_num) - signal._hints = "input" - return signal - - -class ChannelMixer: - def __init__(self, channel): - self._channel = channel - pass - - def __repr__(self): - ret = "[channel mixer]" - ret += " (" + str(len(self.connections)) + " connections)" - for con in self.connections: - ret += "\n " + con.name - ret += " in [bud " + str(con._bud.bud_num) + "] " + con._bud.name - return ret - - def _unplug_all(self): - # TODO - pass - - @property - def connections(self): - ret = [] - for i in range(sys_bl00mbox.channel_mixer_num(self._channel.channel_num)): - b = sys_bl00mbox.channel_get_bud_by_mixer_list_pos( - self._channel.channel_num, i - ) - s = sys_bl00mbox.channel_get_signal_by_mixer_list_pos( - self._channel.channel_num, i - ) - sig = Signal(Bud(self._channel, 0, bud_num=b), s) - ret += [sig] - return ret - - -class Signal: - def __init__(self, bud, signal_num): - self._bud = bud - self._signal_num = signal_num - self._name = sys_bl00mbox.channel_bud_get_signal_name( - bud.channel_num, bud.bud_num, signal_num - ) - self._description = sys_bl00mbox.channel_bud_get_signal_description( - bud.channel_num, bud.bud_num, signal_num - ) - self._unit = sys_bl00mbox.channel_bud_get_signal_unit( - bud.channel_num, bud.bud_num, signal_num - ) - self._hints = "" - - def __repr__(self): - self._bud._check_existence() - - ret = self.name - if len(self.unit): - ret += " [" + self.unit + "]" - ret += " [" + self.hints + "]: " - conret = [] - direction = " <?> " - if isinstance(self, SignalInput): - direction = " <= " - if len(self.connections): - ret += str(self.connections[0].value) - else: - ret += str(self.value) - elif isinstance(self, SignalOutput): - direction = " => " - ret += str(self.value) - - for con in self.connections: - if isinstance(con, Signal): - conret += [ - direction - + con.name - + " in [bud " - + str(con._bud.bud_num) - + "] " - + con._bud.name - ] - if isinstance(con, ChannelMixer): - conret += [" ==> [channel mixer]"] - nl = "\n" - if len(conret) > 1: - ret += "\n" - nl += " " - ret += nl.join(conret) - return ret - - @property - def name(self): - return self._name - - @property - def description(self): - return self._description - - @property - def unit(self): - return self._unit - - @property - def hints(self): - return self._hints - - @property - def connections(self): - return [] - - @property - def value(self): - self._bud._check_existence() - return sys_bl00mbox.channel_bud_get_signal_value( - self._bud.channel_num, self._bud.bud_num, self._signal_num - ) - - -class SignalOutput(Signal): - @Signal.value.setter - def value(self, val): - if val == None: - sys_bl00mbox.channel_disconnect_signal_tx( - self._bud.channel_num, self._bud.bud_num, self._signal_num - ) - elif isinstance(val, SignalInput): - val.value = self - elif isinstance(val, ChannelMixer): - if val._channel.channel_num == self._bud.channel_num: - sys_bl00mbox.channel_connect_signal_to_output_mixer( - self._bud.channel_num, self._bud.bud_num, self._signal_num - ) - - @property - def connections(self): - cons = [] - chan = self._bud.channel_num - bud = self._bud.bud_num - sig = self._signal_num - for i in range(sys_bl00mbox.channel_subscriber_num(chan, bud, sig)): - b = sys_bl00mbox.channel_get_bud_by_subscriber_list_pos(chan, bud, sig, i) - s = sys_bl00mbox.channel_get_signal_by_subscriber_list_pos( - chan, bud, sig, i - ) - if (s >= 0) and (b > 0): - cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)] - elif s == -1: - cons += [ChannelMixer(Channel(chan))] - return cons - - -class SignalInput(Signal): - @Signal.value.setter - def value(self, val): - self._bud._check_existence() - if isinstance(val, SignalOutput): - if len(self.connections): - if not sys_bl00mbox.channel_disconnect_signal_rx( - self._bud.channel_num, self._bud.bud_num, self._signal_num - ): - return - sys_bl00mbox.channel_connect_signal( - self._bud.channel_num, - self._bud.bud_num, - self._signal_num, - val._bud.bud_num, - val._signal_num, - ) - elif isinstance(val, SignalInput): - # TODO - pass - elif (type(val) == int) or (type(val) == float): - if len(self.connections): - if not sys_bl00mbox.channel_disconnect_signal_rx( - self._bud.channel_num, self._bud.bud_num, self._signal_num - ): - return - sys_bl00mbox.channel_bud_set_signal_value( - self._bud.channel_num, self._bud.bud_num, self._signal_num, int(val) - ) - - @property - def connections(self): - cons = [] - chan = self._bud.channel_num - bud = self._bud.bud_num - sig = self._signal_num - b = sys_bl00mbox.channel_get_source_bud(chan, bud, sig) - s = sys_bl00mbox.channel_get_source_signal(chan, bud, sig) - if (s >= 0) and (b > 0): - cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)] - return cons - - -class SignalInputTrigger(SignalInput): - def start(self, velocity=32767): - if self.value > 0: - self.value = -velocity - else: - self.value = velocity - - def stop(self): - self.value = 0 - - -class SignalInputPitch(SignalInput): - def __init__(self, bud, signal_num): - SignalInput.__init__(self, bud, signal_num) - - @property - def tone(self): - return (self.value - (32767 - 2400 * 6)) / 200 - - @tone.setter - def tone(self, val): - 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) - - @property - def freq(self): - tone = (self.value - (32767 - 2400 * 6)) / 200 - return 440 * (2 ** (tone / 12)) - - @freq.setter - def freq(self, val): - tone = 12 * math.log(val / 440, 2) - self.value = (32767 - 2400 * 6) + 200 * tone - - def __repr__(self): - ret = SignalInput.__repr__(self) - ret += " / " + str(self.tone) + " semitones / " + str(self.freq) + "Hz" - return ret - - -class SignalList: - def __init__(self, bud): - self._list = [] - for signal_num in range( - sys_bl00mbox.channel_bud_get_num_signals(bud.channel_num, bud.bud_num) - ): - hints = sys_bl00mbox.channel_bud_get_signal_hints( - bud.channel_num, bud.bud_num, signal_num - ) - if hints & 2: - signal = SignalOutput(bud, signal_num) - signal._hints = "output" - elif hints & 1: - if hints & 4: - signal = SignalInputTrigger(bud, signal_num) - signal._hints = "input/trigger" - elif hints & 32: - signal = SignalInputPitch(bud, signal_num) - signal._hints = "input/pitch" - else: - signal = SignalInput(bud, signal_num) - signal._hints = "input" - self._list += [signal] - setattr(self, signal.name.split(" ")[0], signal) - - def __setattr__(self, key, value): - current_value = getattr(self, key, None) - if isinstance(current_value, Signal): - current_value.value = value - return - super().__setattr__(key, value) - - -class Bud: - def __init__(self, channel, plugin_id, init_var=0, bud_num=None): - self._channel_num = channel.channel_num - if bud_num == None: - self._plugin_id = plugin_id - self._bud_num = sys_bl00mbox.channel_new_bud( - self.channel_num, self.plugin_id, init_var - ) - if self._bud_num == None: - raise Bl00mboxError("bud init failed") - else: - self._bud_num = bud_num - self._check_existence() - self._name = sys_bl00mbox.channel_bud_get_plugin_id( - self.channel_num, self.bud_num - ) - self._name = sys_bl00mbox.channel_bud_get_name(self.channel_num, self.bud_num) - self._signals = SignalList(self) - - def __repr__(self): - self._check_existence() - ret = "[bud " + str(self.bud_num) + "] " + self.name - for sig in self.signals._list: - ret += "\n " + "\n ".join(repr(sig).split("\n")) - return ret - - def __del__(self): - self._check_existence() - sys_bl00mbox.channel_delete_bud(self.channel_num, self.bud_num) - - def _check_existence(self): - if not sys_bl00mbox.channel_bud_exists(self.channel_num, self.bud_num): - raise Bl00mboxError("bud has been deleted") - - @property - def channel_num(self): - return self._channel_num - - @property - def plugin_id(self): - return self._plugin_id - - @property - def name(self): - return self._name - - @property - def bud_num(self): - return self._bud_num - - @property - def signals(self): - return self._signals - - @property - def table(self): - ret = [] - for x in range( - sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) - ): - ret += [ - sys_bl00mbox.channel_bud_get_table_value( - self.channel_num, self.bud_num, x - ) - ] - return ret - - @table.setter - def table(self, stuff): - max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) - if len(stuff) > max_len: - stuff = stuff[:max_len] - for x, y in enumerate(stuff): - sys_bl00mbox.channel_bud_set_table_value( - self.channel_num, self.bud_num, x, y - ) - - -class ChannelOverview: - def __init__(self, nonempty=False): - self._nonempty = nonempty - - def __repr__(self): - nonempty = self._nonempty - ret = ( - "[channel list]\n foreground: [channel " - + str(sys_bl00mbox.channel_get_foreground()) - + "]" - ) - for i in range(sys_bl00mbox.NUM_CHANNELS): - c = Channel(i) - if nonempty: - if not len(c.buds): - continue - ret += "\n" + repr(c) - return ret - - -class Channel: - show_all = ChannelOverview() - show_nonempty = ChannelOverview(nonempty=True) - - def __init__(self, num=None): - if num == None: - self._channel_num = sys_bl00mbox.channel_get_free() - elif (int(num) < sys_bl00mbox.NUM_CHANNELS) and (int(num >= 0)): - self._channel_num = int(num) - - def __repr__(self): - ret = "[channel " + str(self.channel_num) + "]" - if self.foreground: - ret += " (foreground)" - if self.background_mute_override: - ret += " (background mute override)" - ret += "\n volume: " + str(self.volume) - b = sys_bl00mbox.channel_buds_num(self.channel_num) - ret += "\n buds: " + str(b) - if len(self.buds) != b: - ret += " (desync" + str(len(self.buds)) + ")" - ret += "\n " + "\n ".join(repr(self.mixer).split("\n")) - return ret - - def clear(self): - sys_bl00mbox.channel_clear(self.channel_num) - - def new_bud(self, thing, init_var=None): - bud_init_var = 0 - if (type(init_var) == int) or (type(init_var) == float): - bud_init_var = int(init_var) - bud = None - if isinstance(thing, PluginType): - bud = Bud(self, thing.plugin_id, bud_init_var) - if type(thing) == int: - bud = Bud(self, thing, bud_init_var) - return bud - - def new_patch(self, patch, init_var=None): - if init_var == None: - return patch(self) - else: - return patch(self, init_var) - - @staticmethod - def all(): - ret = ( - "[channel list]\n foreground: [channel " - + str(sys_bl00mbox.channel_get_foreground()) - + "]" - ) - for i in range(sys_bl00mbox.NUM_CHANNELS): - c = Channel(i) - ret += "\n" + repr(c) - return ret - - def new(self, thing, init_var=None): - if type(thing) == type: - if issubclass(thing, PatchType): - return self.new_patch(thing, init_var) - if isinstance(thing, PluginType) or (type(thing) == int): - return self.new_bud(thing, init_var) - - @property - def buds(self): - buds = [] - for i in range(sys_bl00mbox.channel_buds_num(self.channel_num)): - b = sys_bl00mbox.channel_get_bud_by_list_pos(self.channel_num, i) - bud = Bud(self, 0, bud_num=b) - buds += [bud] - return buds - - @property - def channel_num(self): - return self._channel_num - - @property - def connections(self): - return sys_bl00mbox.channel_conns_num(self.channel_num) - - @property - def volume(self): - return sys_bl00mbox.channel_get_volume(self.channel_num) - - @volume.setter - def volume(self, value): - sys_bl00mbox.channel_set_volume(self.channel_num, value) - - @property - def mixer(self): - return ChannelMixer(self) - - @mixer.setter - def mixer(self, val): - if isinstance(val, SignalOutput): - val.value = self.mixer - elif val is None: - ChannelMixer(self)._unplug_all - else: - raise Bl00mboxError("can't connect this") - - @property - def background_mute_override(self): - return sys_bl00mbox.channel_get_background_mute_override(self.channel_num) - - @background_mute_override.setter - def background_mute_override(self, val): - sys_bl00mbox.channel_set_background_mute_override(self.channel_num, val) - - @property - def foreground(self): - return sys_bl00mbox.channel_get_foreground() == self.channel_num - - @foreground.setter - def foreground(self, val): - if val: - sys_bl00mbox.channel_set_foreground(self.channel_num) - elif sys_bl00mbox.channel_get_foreground() == self.channel_num: - sys_bl00mbox.channel_set_foreground(0) diff --git a/python_payload/bl00mbox/_patches.py b/python_payload/bl00mbox/_patches.py index 02956f51f813fad50c6b1bd8155516bcef93b624..7fd2bb6ca3e0811fe2bddb297382c046a27c6b7e 100644 --- a/python_payload/bl00mbox/_patches.py +++ b/python_payload/bl00mbox/_patches.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: CC0-1.0 import math +import bl00mbox + try: import cpython.wave as wave except ImportError: @@ -12,6 +14,14 @@ class _Patch: return True +class _PatchSignalList: + pass + + +class _PatchBudList: + pass + + class tinysynth(_Patch): def __init__(self, chan): self.channel = chan @@ -208,3 +218,105 @@ class step_sequencer(_Patch): @property def step(self): return self.seqs[0].signals.step + + +class fuzz(_Patch): + def __init__(self, chan): + self.buds = _PatchBudList() + self.signals = _PatchSignalList() + self.buds.dist = chan.new(bl00mbox.plugins.distortion) + self.signals.input = self.buds.dist.signals.input + self.signals.output = self.buds.dist.signals.output + self._intensity = 2 + self._volume = 32767 + self._gate = 0 + + @property + def intensity(self): + return self._intensity + + @intensity.setter + def intensity(self, val): + self._intensity = val + self._update_table() + + @property + def volume(self): + return self._volume + + @volume.setter + def volume(self, val): + self._volume = val + self._update_table() + + @property + def gate(self): + return self._gate + + @gate.setter + def gate(self, val): + self._gate = val + self._update_table() + + def _update_table(self): + table = list(range(129)) + for num in table: + if num < 64: + ret = num / 64 # scale to [0..1[ range + ret = ret**self._intensity + if ret > 1: + ret = 1 + table[num] = int(self._volume * (ret - 1)) + else: + ret = (128 - num) / 64 # scale to [0..1] range + ret = ret**self._intensity + table[num] = int(self._volume * (1 - ret)) + gate = self.gate >> 9 + for i in range(64 - gate, 64 + gate): + table[i] = 0 + self.buds.dist.table = table + + +class karplus_strong(_Patch): + def __init__(self, chan): + self.buds = _PatchBudList() + self.signals = _PatchSignalList() + self.buds.noise = chan.new_bud(bl00mbox.plugins.noise) + self.buds.flanger = chan.new_bud(bl00mbox.plugins.flanger) + self.buds.amp = chan.new_bud(bl00mbox.plugins.ampliverter) + self.buds.env = chan.new_bud(bl00mbox.plugins.env_adsr) + # self.buds.slew = chan.new_bud(bl00mbox.plugins.slew_rate_limiter) + + self.buds.flanger.signals.resonance = 32500 + self.buds.flanger.signals.manual.tone = "A2" + + self.buds.env.signals.sustain = 0 + self.buds.env.signals.decay = 20 + self.buds.env.signals.attack = 5 + + self.buds.amp.signals.output = self.buds.flanger.signals.input + self.buds.amp.signals.input = self.buds.noise.signals.output + # self.buds.amp.signals.input = self.buds.slew.signals.output + # self.buds.slew.signals.input = self.buds.noise.signals.output + # self.buds.slew.signals.slew_rate = 10000 + self.buds.amp.signals.gain = self.buds.env.signals.output + + self.signals.trigger = self.buds.env.signals.trigger + self.signals.pitch = self.buds.flanger.signals.manual + self.signals.output = self.buds.flanger.signals.output + # self.signals.slew_rate = self.buds.slew.signals.slew_rate + self.signals.level = self.buds.flanger.signals.level + self.decay = 1000 + + @property + def decay(self): + return self._decay + + @decay.setter + def decay(self, val): + tone = self.buds.flanger.signals.manual.tone + loss = (50 * (2 ** (-tone / 12))) // (val / 1000) + if loss < 2: + loss = 2 + self.buds.flanger.signals.resonance = 32767 - loss + self._decay = val diff --git a/python_payload/bl00mbox/_user.py b/python_payload/bl00mbox/_user.py new file mode 100644 index 0000000000000000000000000000000000000000..34593ad8f38a36d3d37afe005db372bfe3c39bdf --- /dev/null +++ b/python_payload/bl00mbox/_user.py @@ -0,0 +1,522 @@ +# SPDX-License-Identifier: CC0-1.0 + +import sys_bl00mbox + +# note: consider the 'sys_bl00mbox' api super unstable for now pls :3 + +import math +import bl00mbox +from bl00mbox import _helpers as helpers + + +class Bl00mboxError(Exception): + pass + + +def _makeSignal(bud, signal_num): + hints = sys_bl00mbox.channel_bud_get_signal_hints( + bud.channel_num, bud.bud_num, signal_num + ) + if hints & 2: + signal = SignalOutput(bud, signal_num) + signal._hints = "output" + elif hints & 1: + if hints & 4: + signal = SignalInputTrigger(bud, signal_num) + signal._hints = "input/trigger" + elif hints & 32: + signal = SignalInputPitch(bud, signal_num) + signal._hints = "input/pitch" + else: + signal = SignalInput(bud, signal_num) + signal._hints = "input" + return signal + + +class ChannelMixer: + def __init__(self, channel): + self._channel = channel + pass + + def __repr__(self): + ret = "[channel mixer]" + ret += " (" + str(len(self.connections)) + " connections)" + for con in self.connections: + ret += "\n " + con.name + ret += " in [bud " + str(con._bud.bud_num) + "] " + con._bud.name + return ret + + def _unplug_all(self): + # TODO + pass + + @property + def connections(self): + ret = [] + for i in range(sys_bl00mbox.channel_mixer_num(self._channel.channel_num)): + b = sys_bl00mbox.channel_get_bud_by_mixer_list_pos( + self._channel.channel_num, i + ) + s = sys_bl00mbox.channel_get_signal_by_mixer_list_pos( + self._channel.channel_num, i + ) + sig = Signal(Bud(self._channel, 0, bud_num=b), s) + ret += [sig] + return ret + + +class Signal: + def __init__(self, bud, signal_num): + self._bud = bud + self._signal_num = signal_num + self._name = sys_bl00mbox.channel_bud_get_signal_name( + bud.channel_num, bud.bud_num, signal_num + ) + mpx = sys_bl00mbox.channel_bud_get_signal_name_multiplex( + bud.channel_num, bud.bud_num, signal_num + ) + if mpx != (-1): + self._name += str(mpx) + self._description = sys_bl00mbox.channel_bud_get_signal_description( + bud.channel_num, bud.bud_num, signal_num + ) + self._unit = sys_bl00mbox.channel_bud_get_signal_unit( + bud.channel_num, bud.bud_num, signal_num + ) + self._hints = "" + + def __repr__(self): + self._bud._check_existence() + + ret = self.name + if len(self.unit): + ret += " [" + self.unit + "]" + ret += " [" + self.hints + "]: " + conret = [] + direction = " <?> " + if isinstance(self, SignalInput): + direction = " <= " + if len(self.connections): + ret += str(self.connections[0].value) + else: + ret += str(self.value) + elif isinstance(self, SignalOutput): + direction = " => " + ret += str(self.value) + + for con in self.connections: + if isinstance(con, Signal): + conret += [ + direction + + con.name + + " in [bud " + + str(con._bud.bud_num) + + "] " + + con._bud.name + ] + if isinstance(con, ChannelMixer): + conret += [" ==> [channel mixer]"] + nl = "\n" + if len(conret) > 1: + ret += "\n" + nl += " " + ret += nl.join(conret) + return ret + + @property + def name(self): + return self._name + + @property + def description(self): + return self._description + + @property + def unit(self): + return self._unit + + @property + def hints(self): + return self._hints + + @property + def connections(self): + return [] + + @property + def value(self): + self._bud._check_existence() + return sys_bl00mbox.channel_bud_get_signal_value( + self._bud.channel_num, self._bud.bud_num, self._signal_num + ) + + +class SignalOutput(Signal): + @Signal.value.setter + def value(self, val): + if val == None: + sys_bl00mbox.channel_disconnect_signal_tx( + self._bud.channel_num, self._bud.bud_num, self._signal_num + ) + elif isinstance(val, SignalInput): + val.value = self + elif isinstance(val, ChannelMixer): + if val._channel.channel_num == self._bud.channel_num: + sys_bl00mbox.channel_connect_signal_to_output_mixer( + self._bud.channel_num, self._bud.bud_num, self._signal_num + ) + + @property + def connections(self): + cons = [] + chan = self._bud.channel_num + bud = self._bud.bud_num + sig = self._signal_num + for i in range(sys_bl00mbox.channel_subscriber_num(chan, bud, sig)): + b = sys_bl00mbox.channel_get_bud_by_subscriber_list_pos(chan, bud, sig, i) + s = sys_bl00mbox.channel_get_signal_by_subscriber_list_pos( + chan, bud, sig, i + ) + if (s >= 0) and (b > 0): + cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)] + elif s == -1: + cons += [ChannelMixer(Channel(chan))] + return cons + + +class SignalInput(Signal): + @Signal.value.setter + def value(self, val): + self._bud._check_existence() + if isinstance(val, SignalOutput): + if len(self.connections): + if not sys_bl00mbox.channel_disconnect_signal_rx( + self._bud.channel_num, self._bud.bud_num, self._signal_num + ): + return + sys_bl00mbox.channel_connect_signal( + self._bud.channel_num, + self._bud.bud_num, + self._signal_num, + val._bud.bud_num, + val._signal_num, + ) + elif isinstance(val, SignalInput): + # TODO + pass + elif (type(val) == int) or (type(val) == float): + if len(self.connections): + if not sys_bl00mbox.channel_disconnect_signal_rx( + self._bud.channel_num, self._bud.bud_num, self._signal_num + ): + return + sys_bl00mbox.channel_bud_set_signal_value( + self._bud.channel_num, self._bud.bud_num, self._signal_num, int(val) + ) + + @property + def connections(self): + cons = [] + chan = self._bud.channel_num + bud = self._bud.bud_num + sig = self._signal_num + b = sys_bl00mbox.channel_get_source_bud(chan, bud, sig) + s = sys_bl00mbox.channel_get_source_signal(chan, bud, sig) + if (s >= 0) and (b > 0): + cons += [_makeSignal(Bud(Channel(chan), 0, bud_num=b), s)] + return cons + + +class SignalInputTrigger(SignalInput): + def start(self, velocity=32767): + if self.value > 0: + self.value = -velocity + else: + self.value = velocity + + def stop(self): + self.value = 0 + + +class SignalInputPitch(SignalInput): + def __init__(self, bud, signal_num): + SignalInput.__init__(self, bud, signal_num) + + @property + def tone(self): + return (self.value - (32767 - 2400 * 6)) / 200 + + @tone.setter + def tone(self, val): + 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) + + @property + def freq(self): + tone = (self.value - (32767 - 2400 * 6)) / 200 + return 440 * (2 ** (tone / 12)) + + @freq.setter + def freq(self, val): + tone = 12 * math.log(val / 440, 2) + self.value = (32767 - 2400 * 6) + 200 * tone + + def __repr__(self): + ret = SignalInput.__repr__(self) + ret += " / " + str(self.tone) + " semitones / " + str(self.freq) + "Hz" + return ret + + +class SignalList: + def __init__(self, bud): + self._list = [] + for signal_num in range( + sys_bl00mbox.channel_bud_get_num_signals(bud.channel_num, bud.bud_num) + ): + hints = sys_bl00mbox.channel_bud_get_signal_hints( + bud.channel_num, bud.bud_num, signal_num + ) + if hints & 2: + signal = SignalOutput(bud, signal_num) + signal._hints = "output" + elif hints & 1: + if hints & 4: + signal = SignalInputTrigger(bud, signal_num) + signal._hints = "input/trigger" + elif hints & 32: + signal = SignalInputPitch(bud, signal_num) + signal._hints = "input/pitch" + else: + signal = SignalInput(bud, signal_num) + signal._hints = "input" + self._list += [signal] + setattr(self, signal.name.split(" ")[0], signal) + + def __setattr__(self, key, value): + current_value = getattr(self, key, None) + if isinstance(current_value, Signal): + current_value.value = value + return + super().__setattr__(key, value) + + +class Bud: + def __init__(self, channel, plugin_id, init_var=0, bud_num=None): + self._channel_num = channel.channel_num + if bud_num == None: + self._plugin_id = plugin_id + self._bud_num = sys_bl00mbox.channel_new_bud( + self.channel_num, self.plugin_id, init_var + ) + if self._bud_num == None: + raise Bl00mboxError("bud init failed") + else: + self._bud_num = bud_num + self._check_existence() + self._name = sys_bl00mbox.channel_bud_get_plugin_id( + self.channel_num, self.bud_num + ) + self._name = sys_bl00mbox.channel_bud_get_name(self.channel_num, self.bud_num) + self._signals = SignalList(self) + + def __repr__(self): + self._check_existence() + ret = "[bud " + str(self.bud_num) + "] " + self.name + for sig in self.signals._list: + ret += "\n " + "\n ".join(repr(sig).split("\n")) + return ret + + def __del__(self): + self._check_existence() + sys_bl00mbox.channel_delete_bud(self.channel_num, self.bud_num) + + def _check_existence(self): + if not sys_bl00mbox.channel_bud_exists(self.channel_num, self.bud_num): + raise Bl00mboxError("bud has been deleted") + + @property + def channel_num(self): + return self._channel_num + + @property + def plugin_id(self): + return self._plugin_id + + @property + def name(self): + return self._name + + @property + def bud_num(self): + return self._bud_num + + @property + def signals(self): + return self._signals + + @property + def table(self): + ret = [] + for x in range( + sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) + ): + ret += [ + sys_bl00mbox.channel_bud_get_table_value( + self.channel_num, self.bud_num, x + ) + ] + return ret + + @table.setter + def table(self, stuff): + max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) + if len(stuff) > max_len: + stuff = stuff[:max_len] + for x, y in enumerate(stuff): + sys_bl00mbox.channel_bud_set_table_value( + self.channel_num, self.bud_num, x, y + ) + + +class ChannelOverview: + def __init__(self, nonempty=False): + self._nonempty = nonempty + + def __repr__(self): + nonempty = self._nonempty + ret = ( + "[channel list]\n foreground: [channel " + + str(sys_bl00mbox.channel_get_foreground()) + + "]" + ) + for i in range(sys_bl00mbox.NUM_CHANNELS): + c = Channel(i) + if nonempty: + if not len(c.buds): + continue + ret += "\n" + repr(c) + return ret + + +class Channel: + show_all = ChannelOverview() + show_nonempty = ChannelOverview(nonempty=True) + + def __init__(self, num=None): + if num == None: + self._channel_num = sys_bl00mbox.channel_get_free() + elif (int(num) < sys_bl00mbox.NUM_CHANNELS) and (int(num >= 0)): + self._channel_num = int(num) + + def __repr__(self): + ret = "[channel " + str(self.channel_num) + "]" + if self.foreground: + ret += " (foreground)" + if self.background_mute_override: + ret += " (background mute override)" + ret += "\n volume: " + str(self.volume) + b = sys_bl00mbox.channel_buds_num(self.channel_num) + ret += "\n buds: " + str(b) + if len(self.buds) != b: + ret += " (desync" + str(len(self.buds)) + ")" + ret += "\n " + "\n ".join(repr(self.mixer).split("\n")) + return ret + + def clear(self): + sys_bl00mbox.channel_clear(self.channel_num) + + def new_bud(self, thing, init_var=None): + bud_init_var = 0 + if (type(init_var) == int) or (type(init_var) == float): + bud_init_var = int(init_var) + bud = None + if isinstance(thing, bl00mbox._plugins._Plugin): + bud = Bud(self, thing.plugin_id, bud_init_var) + if type(thing) == int: + bud = Bud(self, thing, bud_init_var) + return bud + + def new_patch(self, patch, init_var=None): + if init_var == None: + return patch(self) + else: + return patch(self, init_var) + + @staticmethod + def all(): + ret = ( + "[channel list]\n foreground: [channel " + + str(sys_bl00mbox.channel_get_foreground()) + + "]" + ) + for i in range(sys_bl00mbox.NUM_CHANNELS): + c = Channel(i) + ret += "\n" + repr(c) + return ret + + def new(self, thing, init_var=None): + if type(thing) == type: + if issubclass(thing, bl00mbox.patches._Patch): + return self.new_patch(thing, init_var) + if isinstance(thing, bl00mbox._plugins._Plugin) or (type(thing) == int): + return self.new_bud(thing, init_var) + + @property + def buds(self): + buds = [] + + for i in range(sys_bl00mbox.channel_buds_num(self.channel_num)): + b = sys_bl00mbox.channel_get_bud_by_list_pos(self.channel_num, i) + bud = Bud(self, 0, bud_num=b) + buds += [bud] + return buds + + @property + def channel_num(self): + return self._channel_num + + @property + def connections(self): + return sys_bl00mbox.channel_conns_num(self.channel_num) + + @property + def volume(self): + return sys_bl00mbox.channel_get_volume(self.channel_num) + + @volume.setter + def volume(self, value): + sys_bl00mbox.channel_set_volume(self.channel_num, value) + + @property + def mixer(self): + return ChannelMixer(self) + + @mixer.setter + def mixer(self, val): + if isinstance(val, SignalOutput): + val.value = self.mixer + elif val is None: + ChannelMixer(self)._unplug_all + else: + raise Bl00mboxError("can't connect this") + + @property + def background_mute_override(self): + return sys_bl00mbox.channel_get_background_mute_override(self.channel_num) + + @background_mute_override.setter + def background_mute_override(self, val): + sys_bl00mbox.channel_set_background_mute_override(self.channel_num, val) + + @property + def foreground(self): + return sys_bl00mbox.channel_get_foreground() == self.channel_num + + @foreground.setter + def foreground(self, val): + if val: + sys_bl00mbox.channel_set_foreground(self.channel_num) + elif sys_bl00mbox.channel_get_foreground() == self.channel_num: + sys_bl00mbox.channel_set_foreground(0) diff --git a/python_payload/mypystubs/bl00mbox/__init__.pyi b/python_payload/mypystubs/bl00mbox/__init__.pyi index b1369856a863068b53c2bb9df422a108f767f5c5..241a70dc60d32c251f53c335e63ab5aa768a2c7d 100644 --- a/python_payload/mypystubs/bl00mbox/__init__.pyi +++ b/python_payload/mypystubs/bl00mbox/__init__.pyi @@ -1,23 +1,14 @@ -from bl00mbox import patches - -from typing import Optional, TypeVar, Any, Type - -patches = patches - -class Signal: - value: int - -class SignalList: - def __setattr__(self, name: str, value: Signal) -> None: ... - def __getattr__(self, name: str) -> Signal: ... - -class Bud: - signals: SignalList - -T = TypeVar("T") -P = TypeVar("P", bound=patches._Patch) - -class Channel: - background_mute_override: bool - def new(self, thing: Type[T], init_var: Optional[Any] = None) -> T: ... - def new_patch(self, patch: Type[T], init_var: Optional[Any] = None) -> T: ... +from bl00mbox._user import * +import bl00mbox._patches as patches +import bl00mbox._helpers as helpers +from bl00mbox._plugins import plugins + +__all__ = [ + "Signal", + "SignalList", + "Bud", + "Channel", + "patches", + "helpers", + "plugins", +] diff --git a/python_payload/mypystubs/bl00mbox/_helpers.pyi b/python_payload/mypystubs/bl00mbox/_helpers.pyi new file mode 100644 index 0000000000000000000000000000000000000000..6226fca1b33388b7befe15e912a5ed509e9ba37c --- /dev/null +++ b/python_payload/mypystubs/bl00mbox/_helpers.pyi @@ -0,0 +1,15 @@ +import bl00mbox +from typing import Optional, Callable + +def terminal_scope( + signal: bl00mbox.Signal, + signal_min: int, + signal_max: int, + delay_ms: int, + width: int, + fun: Optional[Callable[[], None]], + fun_ms: Optional[int], +) -> None: ... +def sct_to_note_name(sct: int) -> str: ... +def note_name_to_sct(name: str) -> int: ... +def sct_to_freq(sct: int) -> int: ... diff --git a/python_payload/mypystubs/bl00mbox/patches.pyi b/python_payload/mypystubs/bl00mbox/_patches.pyi similarity index 67% rename from python_payload/mypystubs/bl00mbox/patches.pyi rename to python_payload/mypystubs/bl00mbox/_patches.pyi index 4094d0c47f7319a5145af1f589d7bfdb939d190d..69c1d88e4d3e3c2796a2a42cccc92e8b5f49a0b0 100644 --- a/python_payload/mypystubs/bl00mbox/patches.pyi +++ b/python_payload/mypystubs/bl00mbox/_patches.pyi @@ -4,6 +4,12 @@ from typing import List class _Patch: ... +class _PatchSignalList: + def __getattr__(self, name: str) -> bl00mbox.Signal: ... + +class _PatchBudList: + def __getattr__(self, name: str) -> bl00mbox.Bud: ... + class tinysynth(_Patch): def decay(self, v: float) -> None: ... def waveform(self, v: int) -> None: ... @@ -28,3 +34,15 @@ class step_sequencer(_Patch): class sampler(_Patch): sampler: bl00mbox.Bud + +class fuzz(_Patch): + buds: _PatchBudList + signals: _PatchSignalList + volume: int + intensity: float + gate: int + +class karplus_strong(_Patch): + buds: _PatchBudList + signals: _PatchSignalList + decay: int diff --git a/python_payload/mypystubs/bl00mbox/_plugins.pyi b/python_payload/mypystubs/bl00mbox/_plugins.pyi new file mode 100644 index 0000000000000000000000000000000000000000..b0fec0fba98a623ee8549abfa959e84c41d77701 --- /dev/null +++ b/python_payload/mypystubs/bl00mbox/_plugins.pyi @@ -0,0 +1,10 @@ +class _Plugin: + index: int + plugin_id: int + name: str + description: str + +class _Plugins: + def __getattr__(self, name: str) -> _Plugin: ... + +plugins = _Plugins() diff --git a/python_payload/mypystubs/bl00mbox/_user.pyi b/python_payload/mypystubs/bl00mbox/_user.pyi new file mode 100644 index 0000000000000000000000000000000000000000..ed45a64a36f702819b8e259da612d23acdad17be --- /dev/null +++ b/python_payload/mypystubs/bl00mbox/_user.pyi @@ -0,0 +1,69 @@ +from typing import Optional, TypeVar, Any, Type, List, overload +import bl00mbox + +class Signal: + name: str + description: str + unit: str + hints: str + connections: List["Signal | ChannelMixer"] + + # mypy doesn't know how to handle asymmetric setters + # https://github.com/python/mypy/issues/3004 + value: Any + # value: None | int | float | "Signal" | "ChannelMixer" + + def __init__(self, bud: "Bud", signal_num: int): ... + + # SignalInputTrigger functions + def start(self, velocity: int = 32767) -> None: ... + def stop(self) -> None: ... + + # SignalInputPitch shorthands + freq: int + tone: Any + # tone: int | float | str + +class SignalList: + def __setattr__(self, name: str, value: Signal | int | "ChannelMixer") -> None: ... + def __getattr__(self, name: str) -> Signal: ... + +class Bud: + signals: SignalList + + def __init__( + self, + channel: "Channel", + plugin_id: int, + init_var: Optional[int] = 0, + bud_num: Optional[int] = None, + ): ... + +T = TypeVar("T") +P = TypeVar("P", bound=bl00mbox.patches._Patch) + +class Channel: + background_mute_override: bool + mixer: "ChannelMixer" + + def clear(self) -> None: ... + def new_patch(self, patch: Type[T], init_var: Optional[Any] = None) -> T: ... + def new_bud( + self, + thing: bl00mbox._plugins._Plugin | int, + init_var: Optional[int | float] = None, + ) -> Bud: ... + @overload + def new(self, thing: Type[P], init_var: Optional[Any] = None) -> P: ... + @overload + def new( + self, + thing: bl00mbox._plugins._Plugin | int, + init_var: Optional[int | float] = None, + ) -> Bud: ... + +class ChannelMixer: + _channel: Channel + connections: List[Signal] + + def _unplug_all(self) -> None: ...