diff --git a/components/bl00mbox/bl00mbox_user.c b/components/bl00mbox/bl00mbox_user.c index 584b01cb02ce554f849d6639371ec808945a2358..3c31abad16e7f5f277c92d68fb1ea5cd5b8e4524 100644 --- a/components/bl00mbox/bl00mbox_user.c +++ b/components/bl00mbox/bl00mbox_user.c @@ -750,10 +750,10 @@ int16_t * bl00mbox_channel_bud_get_table_pointer(uint8_t channel, uint32_t bud_i return bud->plugin->plugin_table; } -int16_t bl00mbox_channel_bud_get_table_len(uint8_t channel, uint32_t bud_index){ +uint32_t bl00mbox_channel_bud_get_table_len(uint8_t channel, uint32_t bud_index){ bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); - if(chan == NULL) return false; + if(chan == NULL) return 0; bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); - if(bud == NULL) return false; + if(bud == NULL) return 0; return bud->plugin->plugin_table_len; } diff --git a/components/bl00mbox/include/bl00mbox_user.h b/components/bl00mbox/include/bl00mbox_user.h index 745d27607c7a449fb5d5eda9d0954d8128c509dc..9239408ff87f6b3541e54ac6855caeef180fe867 100644 --- a/components/bl00mbox/include/bl00mbox_user.h +++ b/components/bl00mbox/include/bl00mbox_user.h @@ -52,5 +52,5 @@ uint16_t bl00mbox_channel_get_source_signal(uint8_t channel, uint64_t bud_index, bool bl00mbox_channel_bud_set_table_value(uint8_t channel, uint32_t bud_index, uint32_t table_index, int16_t value); int16_t bl00mbox_channel_bud_get_table_value(uint8_t channel, uint32_t bud_index, uint32_t table_index); -int16_t bl00mbox_channel_bud_get_table_len(uint8_t channel, uint32_t bud_index); +uint32_t bl00mbox_channel_bud_get_table_len(uint8_t channel, uint32_t bud_index); int16_t * bl00mbox_channel_bud_get_table_pointer(uint8_t channel, uint32_t bud_index); diff --git a/components/bl00mbox/plugins/sequencer.c b/components/bl00mbox/plugins/sequencer.c index c137271430281f5430c86c4c32ee020d54df9766..5345e2d96e2b0d1d58b54880066147ecb25d1680 100644 --- a/components/bl00mbox/plugins/sequencer.c +++ b/components/bl00mbox/plugins/sequencer.c @@ -11,14 +11,17 @@ radspa_descriptor_t sequencer_desc = { .destroy_plugin_instance = radspa_standard_plugin_destroy }; -#define SEQUENCER_NUM_SIGNALS 6 +#define SEQUENCER_NUM_SIGNALS 7 #define SEQUENCER_STEP 0 -#define SEQUENCER_STEP_LEN 1 -#define SEQUENCER_SYNC_OUT 2 -#define SEQUENCER_SYNC_IN 3 -#define SEQUENCER_BPM 4 -#define SEQUENCER_BEAT_DIV 5 -#define SEQUENCER_OUTPUT 6 +#define SEQUENCER_SYNC_OUT 1 +#define SEQUENCER_SYNC_IN 2 +#define SEQUENCER_START_STEP 3 +#define SEQUENCER_END_STEP 4 +#define SEQUENCER_BPM 5 +#define SEQUENCER_BEAT_DIV 6 + +// mpx'd +#define SEQUENCER_OUTPUT 7 static uint64_t target(uint64_t step_len, uint64_t bpm, uint64_t beat_div){ if(bpm == 0) return 0; @@ -37,20 +40,22 @@ void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_p radspa_signal_t * step_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_STEP); if(step_sig->buffer != NULL) output_request = true; - radspa_signal_t * step_len_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_STEP_LEN); radspa_signal_t * sync_out_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_SYNC_OUT); if(sync_out_sig->buffer != NULL) output_request = true; if(!output_request) return; radspa_signal_t * sync_in_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_SYNC_IN); + radspa_signal_t * start_step_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_START_STEP); + radspa_signal_t * end_step_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_END_STEP); radspa_signal_t * bpm_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_BPM); radspa_signal_t * beat_div_sig = radspa_signal_get_by_index(sequencer, SEQUENCER_BEAT_DIV); int16_t * table = sequencer->plugin_table; - int16_t s1 = radspa_signal_get_value(step_len_sig, 0, num_samples, render_pass_id); - int16_t s2 = data->track_step_len; - data->step_target = s1 > 0 ? (s1 > s2 ? s2 : s1) : 1; + int16_t s1 = radspa_signal_get_value(end_step_sig, 0, num_samples, render_pass_id); + int16_t s2 = data->track_step_len - 1; + data->step_end = s1 > 0 ? (s1 > s2 ? s2 : s1) : 1; + data->step_start = radspa_signal_get_value(start_step_sig, 0, num_samples, render_pass_id); int16_t bpm = bpm_sig->get_value(bpm_sig, 0, num_samples, render_pass_id); int16_t beat_div = beat_div_sig->get_value(beat_div_sig, 0, num_samples, render_pass_id); @@ -65,8 +70,8 @@ void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_p if(data->counter >= data->counter_target){ data->counter = 0; data->step++; - if(data->step >= data->step_target){ - data->step = 0; + if(data->step > data->step_end){ + data->step = data->step_start; data->sync_out = -data->sync_out; } } @@ -74,7 +79,7 @@ void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_p int16_t sync_in = sync_in_sig->get_value(sync_in_sig, i, num_samples, render_pass_id); if(((sync_in > 0) && (data->sync_in_prev <= 0)) || ((sync_in > 0) && (data->sync_in_prev <= 0))){ data->counter = 0; - data->step = 0; + data->step = data->step_start; data->sync_out = -data->sync_out; } data->sync_in_prev = sync_in; @@ -131,9 +136,10 @@ radspa_t * sequencer_create(uint32_t init_var){ data->beat_div_prev = 16; data->counter_target = target(data->track_step_len, data->bpm_prev, data->beat_div_prev); radspa_signal_set(sequencer, SEQUENCER_STEP, "step", RADSPA_SIGNAL_HINT_OUTPUT, 0); - radspa_signal_set(sequencer, SEQUENCER_STEP_LEN, "step_len", RADSPA_SIGNAL_HINT_INPUT, num_pixels); radspa_signal_set(sequencer, SEQUENCER_SYNC_OUT, "sync_out", RADSPA_SIGNAL_HINT_OUTPUT, 0); radspa_signal_set(sequencer, SEQUENCER_SYNC_IN, "sync_in", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + radspa_signal_set(sequencer, SEQUENCER_START_STEP, "step_start", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(sequencer, SEQUENCER_END_STEP, "step_end", RADSPA_SIGNAL_HINT_INPUT, num_pixels-1); radspa_signal_set(sequencer, SEQUENCER_BPM, "bpm", RADSPA_SIGNAL_HINT_INPUT, data->bpm_prev); radspa_signal_set(sequencer, SEQUENCER_BEAT_DIV, "beat_div", RADSPA_SIGNAL_HINT_INPUT, data->beat_div_prev); radspa_signal_set_group(sequencer, data->num_tracks, 1, SEQUENCER_OUTPUT, "track", diff --git a/components/bl00mbox/plugins/sequencer.h b/components/bl00mbox/plugins/sequencer.h index bed48c15a79788bbb53ff5dc9ce8c24df14ba9ee..57b478896be9da1869c5a18e0f093c4d05e58ca3 100644 --- a/components/bl00mbox/plugins/sequencer.h +++ b/components/bl00mbox/plugins/sequencer.h @@ -11,7 +11,8 @@ typedef struct { typedef struct { uint8_t num_tracks; uint16_t track_step_len; - uint8_t step_target; + uint8_t step_end; + uint8_t step_start; uint8_t step; uint64_t counter; uint64_t counter_target; diff --git a/python_payload/apps/gay_drums/__init__.py b/python_payload/apps/gay_drums/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9e5b9b08b8804cef09db7cec710f8abbeea53447 --- /dev/null +++ b/python_payload/apps/gay_drums/__init__.py @@ -0,0 +1,458 @@ +import bl00mbox +import captouch +import leds + +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 + +if TYPE_CHECKING: + Number = float | int + Color = Tuple[Number, Number, Number] + ColorLeds = Tuple[int, int, int] + Rendee = Callable[[Context, Any], None] +else: + Number = Color = ColorLeds = Rendee = None + + +class GayDrums(Application): + def __init__(self, app_ctx: ApplicationContext) -> None: + super().__init__(app_ctx) + self.blm = bl00mbox.Channel("gay drums") + self.num_samplers = 6 + self.seq = self.blm.new(bl00mbox.patches.sequencer, num_tracks=8, num_steps=32) + self.bar = self.seq.signals.step.value // 16 + self.set_bar_mode(0) + + self.kick: Optional[bl00mbox.patches._Patch] = None + self.hat: Optional[bl00mbox.patches._Patch] = None + self.close: Optional[bl00mbox.patches._Patch] = None + self.open: Optional[bl00mbox.patches._Patch] = None + self.crash: Optional[bl00mbox.patches._Patch] = None + self.snare: Optional[bl00mbox.patches._Patch] = None + + self.seq.signals.bpm.value = 80 + + self.track_names = ["kick", "hihat", "snare", "crash", "nya", "woof"] + self.ct_prev = captouch.read() + self.track = 1 + self.blm.background_mute_override = True + self.tap_tempo_press_counter = 0 + self.track_back_press_counter = 0 + self.delta_acc = 0 + self.stopped = False + self.track_back = False + self.bpm = self.seq.signals.bpm.value + + self.samples_loaded = 0 + self.samples_total = len(self.track_names) + self.loading_text = "" + self.init_complete = False + self.load_iter = self.iterate_loading() + self._render_list_2: List[Tuple[Rendee, Any]] = [] + self._render_list_1: List[Tuple[Rendee, Any]] = [] + + self._group_highlight_on = [False] * 4 + self._group_highlight_redraw = [False] * 4 + self._redraw_background = 2 + self._group_gap = 5 + self.background_col: Color = (0, 0, 0) + self.highlight_col: Color = (0.15, 0.15, 0.15) + + def set_bar_mode(self, mode: int) -> None: + # TODO: figure out how to speed up re-render + if mode == 0: + self.seq.signals.step_start = 0 + self.seq.signals.step_end = 15 + if mode == 1: + self.seq.signals.step_start = 16 + self.seq.signals.step_end = 31 + if mode == 2: + self.seq.signals.step_start = 0 + self.seq.signals.step_end = 31 + + def iterate_loading(self) -> Iterator[Tuple[int, str]]: + yield 0, "kick.wav" + self.nya = self.blm.new(bl00mbox.patches.sampler, "nya.wav") + self.nya.signals.output = self.blm.mixer + self.nya.signals.trigger = self.seq.plugins.seq.signals.track6 + self.kick = self.blm.new(bl00mbox.patches.sampler, "kick.wav") + self.kick.signals.output = self.blm.mixer + self.kick.signals.trigger = self.seq.plugins.seq.signals.track0 + yield 1, "hihat.wav" + self.woof = self.blm.new(bl00mbox.patches.sampler, "bark.wav") + self.woof.signals.output = self.blm.mixer + self.woof.signals.trigger = self.seq.plugins.seq.signals.track7 + self.hat = self.blm.new(bl00mbox.patches.sampler, "hihat.wav") + self.hat.signals.output = self.blm.mixer + self.hat.signals.trigger = self.seq.plugins.seq.signals.track1 + yield 2, "close.wav" + self.close = self.blm.new(bl00mbox.patches.sampler, "close.wav") + self.close.signals.output = self.blm.mixer + self.close.signals.trigger = self.seq.plugins.seq.signals.track2 + yield 3, "open.wav" + self.open = self.blm.new(bl00mbox.patches.sampler, "open.wav") + self.open.signals.output = self.blm.mixer + self.open.signals.trigger = self.seq.plugins.seq.signals.track3 + yield 4, "snare.wav" + self.snare = self.blm.new(bl00mbox.patches.sampler, "snare.wav") + self.snare.signals.output = self.blm.mixer + self.snare.signals.trigger = self.seq.plugins.seq.signals.track4 + yield 5, "crash.wav" + self.crash = self.blm.new(bl00mbox.patches.sampler, "crash.wav") + self.crash.signals.output = self.blm.mixer + self.crash.signals.trigger = self.seq.plugins.seq.signals.track5 + yield 6, "" + + def _highlight_petal(self, num: int, r: int, g: int, b: int) -> None: + for i in range(5): + leds.set_rgb((4 * num - i + 2) % 40, r, g, b) + + def _track_col(self, track: int) -> ColorLeds: + rgb = (20, 20, 20) + if track == 0: + rgb = (120, 0, 137) + elif track == 1: + rgb = (0, 75, 255) + elif track == 2: + rgb = (0, 130, 27) + elif track == 3: + rgb = (255, 239, 0) + elif track == 4: + rgb = (255, 142, 0) + elif track == 5: + rgb = (230, 0, 0) + return rgb + + def draw_background(self, ctx: Context, data: None) -> None: + ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() + + # group bars + bar_len = 10 * (1 + self.num_samplers) + bar_pos = 4 * 12 + self._group_gap + self.ctx_draw_centered_rect(ctx, 0, 0, 1, bar_len, (0.5, 0.5, 0.5)) + self.ctx_draw_centered_rect(ctx, bar_pos, 0, 1, bar_len, (0.5, 0.5, 0.5)) + self.ctx_draw_centered_rect(ctx, -bar_pos, 0, 1, bar_len, (0.5, 0.5, 0.5)) + + ctx.font = ctx.get_font_name(1) + + ctx.font_size = 15 + ctx.rgb(0.6, 0.6, 0.6) + + ctx.move_to(0, -85) + ctx.text("(hold) stop") + + ctx.move_to(0, -100) + ctx.text("tap tempo") + + self.draw_track_name(ctx, None) + self.draw_bpm(ctx, None) + + for track in range(self.num_samplers): + for step in range(16): + self.draw_track_step_marker(ctx, (track, step)) + + def track_get_state(self, track: int, step: int) -> int: + sequencer_track = track + if track > 1: + sequencer_track += 2 + if track == 1: + if self.seq.trigger_state(1, step): + return 1 + elif self.seq.trigger_state(2, step): + return 2 + elif self.seq.trigger_state(3, step): + return 3 + else: + return 0 + else: + state = self.seq.trigger_state(sequencer_track, step) + if state == 0: + return 0 + elif state == 32767: + return 3 + elif state < 16384: + return 1 + else: + return 2 + + def track_incr_state(self, track: int, step: int) -> None: + sequencer_track = track + if track > 1: + sequencer_track += 2 + if track == 1: + state = self.track_get_state(track, step) + if state == 0: + self.seq.trigger_start(1, step) + self.seq.trigger_clear(2, step) + self.seq.trigger_clear(3, step) + if state == 1: + self.seq.trigger_clear(1, step) + self.seq.trigger_start(2, step) + self.seq.trigger_clear(3, step) + if state == 2: + self.seq.trigger_clear(1, step) + self.seq.trigger_clear(2, step) + self.seq.trigger_start(3, step) + if state == 3: + self.seq.trigger_clear(1, step) + self.seq.trigger_clear(2, step) + self.seq.trigger_clear(3, step) + else: + state = self.seq.trigger_state(sequencer_track, step) + if state == 0: + new_state = 16000 + elif state == 32767: + new_state = 0 + else: + new_state = 32767 + self.seq.trigger_start(sequencer_track, step, new_state) + + def draw_track_step_marker(self, ctx: Context, data: Tuple[int, int]) -> None: + track, step = data + self._group_gap = 4 + rgb = self._track_col(track) + rgbf = (rgb[0] / 256, rgb[1] / 256, rgb[2] / 256) + y = -int(12 * (track - (self.num_samplers - 1) / 2)) + trigger_state = self.track_get_state(track, step) + size = 2 + x = 12 * (7.5 - step) + x += self._group_gap * (1.5 - (step // 4)) + x = int(-x) + group = step // 4 + bg = self.background_col + if self._group_highlight_on[group]: + bg = self.highlight_col + if trigger_state == 3: + self.ctx_draw_centered_rect(ctx, x, y, 8, 8, rgbf) + elif trigger_state == 2: + self.ctx_draw_centered_rect(ctx, x, y, 8, 8, bg) + self.ctx_draw_centered_rect(ctx, x, y - 2, 8, 4, rgbf) + elif trigger_state == 1: + self.ctx_draw_centered_rect(ctx, x, y, 8, 8, bg) + self.ctx_draw_centered_rect(ctx, x, y + 2, 8, 4, rgbf) + elif trigger_state == 0: + self.ctx_draw_centered_rect(ctx, x, y, 8, 8, bg) + self.ctx_draw_centered_rect(ctx, x, y, 2, 2, rgbf) + + def ctx_draw_centered_rect( + self, ctx: Context, posx: int, posy: int, sizex: int, sizey: int, col: Color + ) -> None: + ctx.rgb(*col) + nosx = int(posx - (sizex / 2)) + nosy = int(posy - (sizey / 2)) + ctx.rectangle(nosx, nosy, int(sizex), int(sizey)).fill() + + def draw_group_highlight(self, ctx: Context, data: int) -> None: + i = data + col = self.background_col + if self._group_highlight_on[i]: + col = self.highlight_col + sizex = 48 + self._group_gap - 2 + sizey = 10 * (1 + self.num_samplers) + posx = -int((12 * 4 + 1 + self._group_gap) * (1.5 - i)) + posy = 0 + self.ctx_draw_centered_rect(ctx, posx, posy, sizex, sizey, col) + + for x in range(self.num_samplers): + for y in range(4): + self.draw_track_step_marker(ctx, (x, y + 4 * i)) + + def draw_bpm(self, ctx: Context, data: None) -> None: + self.ctx_draw_centered_rect(ctx, 0, -65, 200, 22, (0, 0, 0)) + bpm = self.seq.signals.bpm.value + ctx.font = ctx.get_font_name(1) + ctx.font_size = 20 + + ctx.move_to(0, -65) + ctx.rgb(255, 255, 255) + ctx.text(str(bpm) + " bpm") + + def draw(self, ctx: Context) -> None: + if not self.init_complete: + ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() + + ctx.font = ctx.get_font_name(0) + ctx.font_size = 24 + ctx.move_to(0, -10) + ctx.rgb(0.8, 0.8, 0.8) + if self.samples_loaded == self.samples_total: + ctx.text("Loading complete") + else: + ctx.text("Loading samples...") + + ctx.font_size = 16 + ctx.move_to(0, 10) + ctx.text(self.loading_text) + + progress = self.samples_loaded / self.samples_total + bar_len = 120 / self.samples_total + for x in range(self.samples_loaded): + rgb = self._track_col(x) + rgbf = (rgb[0] / 256, rgb[1] / 256, rgb[2] / 256) + ctx.rgb(*rgbf) + ctx.rectangle(x * bar_len - 60, 30, bar_len, 10).fill() + return + + for i in range(4): + if self._group_highlight_redraw[i]: + self._group_highlight_redraw[i] = False + self._render_list_1 += [(self.draw_group_highlight, i)] + + for rendee in self._render_list_1: + fun, param = rendee + fun(ctx, param) + for rendee in self._render_list_2: + fun, param = rendee + fun(ctx, param) + self._render_list_2 = self._render_list_1.copy() + self._render_list_1 = [] + + size = 4 + + st = self.seq.signals.step.value + + stepx = -12 * (7.5 - st) + stepx -= self._group_gap * (1.5 - (st // 4)) + stepy = -12 - 5 * self.num_samplers + + trigger_state = self.track_get_state(self.track, st) + dotsize = 1 + if trigger_state: + dotsize = 4 + self.ctx_draw_centered_rect(ctx, 0, stepy, 200, 4, self.background_col) + self.ctx_draw_centered_rect(ctx, stepx, stepy, dotsize, dotsize, (1, 1, 1)) + + def draw_track_name(self, ctx: Context, data: None) -> None: + self.ctx_draw_centered_rect(ctx, 0, 60, 200, 30, self.background_col) + ctx.font = ctx.get_font_name(1) + + ctx.font_size = 20 + ctx.rgb(1, 1, 1) + + ctx.move_to(45, 60) + ctx.text(">") + + ctx.move_to(-45, 60) + ctx.text(">") + + ctx.font_size = 15 + ctx.rgb(0.6, 0.6, 0.6) + ctx.move_to(-45, 75) + ctx.text("(hold)") + + ctx.font_size = 28 + + track = self.track + ctx.move_to(0, 60) + col = [x / 255 for x in self._track_col(track)] + ctx.rgb(*col) + ctx.text(self.track_names[track]) + + ctx.font_size = 15 + + track = (track - 1) % self.num_samplers + ctx.move_to(75, 60) + col = [x / 255 for x in self._track_col(track)] + ctx.rgb(*col) + ctx.text(self.track_names[track]) + + track = (track + 2) % self.num_samplers + ctx.move_to(-75, 60) + col = [x / 255 for x in self._track_col(track)] + ctx.rgb(*col) + ctx.text(self.track_names[track]) + + def think(self, ins: InputState, delta_ms: int) -> None: + super().think(ins, delta_ms) + + if not self.init_complete: + try: + self.samples_loaded, self.loading_text = next(self.load_iter) + except StopIteration: + self.init_complete = True + self._render_list_1 += [(self.draw_background, None)] + return + + st = self.seq.signals.step.value + rgb = self._track_col(self.track) + leds.set_all_rgb(0, 0, 0) + self._highlight_petal(10 - (4 - (st // 4)), *rgb) + self._highlight_petal(10 - (6 + (st % 4)), *rgb) + leds.update() + + if self.bar != (st // 16): + self.bar = st // 16 + self._group_highlight_redraw = [True] * 4 + + ct = captouch.read() + for i in range(4): + if ct.petals[4 - i].pressed: + if not self._group_highlight_on[i]: + self._group_highlight_redraw[i] = True + self._group_highlight_on[i] = True + for j in range(4): + if ct.petals[6 + j].pressed and not ( + self.ct_prev.petals[6 + j].pressed + ): + self.track_incr_state(self.track, self.bar * 16 + i * 4 + j) + + self._render_list_1 += [ + (self.draw_track_step_marker, (self.track, i * 4 + j)) + ] + else: + if self._group_highlight_on[i]: + self._group_highlight_redraw[i] = True + self._group_highlight_on[i] = False + + if not ct.petals[5].pressed and (self.ct_prev.petals[5].pressed): + if self.track_back: + self.track_back = False + else: + self.track = (self.track - 1) % self.num_samplers + self._render_list_1 += [(self.draw_track_name, None)] + + if ct.petals[0].pressed and not (self.ct_prev.petals[0].pressed): + if self.stopped: + self.seq.signals.bpm = self.bpm + self._render_list_1 += [(self.draw_bpm, None)] + self.blm.background_mute_override = True + self.stopped = False + elif self.delta_acc < 3000 and self.delta_acc > 10: + bpm = int(60000 / self.delta_acc) + if bpm > 40 and bpm < 500: + self.seq.signals.bpm = bpm + self._render_list_1 += [(self.draw_bpm, None)] + self.bpm = bpm + self.delta_acc = 0 + + if ct.petals[0].pressed: + if self.tap_tempo_press_counter > 500: + self.seq.signals.bpm = 0 + self._render_list_1 += [(self.draw_bpm, None)] + self.stopped = True + self.blm.background_mute_override = False + else: + self.tap_tempo_press_counter += delta_ms + else: + self.tap_tempo_press_counter = 0 + + if ct.petals[5].pressed: + if (self.track_back_press_counter > 500) and not self.track_back: + self.track = (self.track + 1) % self.num_samplers + self._render_list_1 += [(self.draw_track_name, None)] + self.track_back = True + else: + self.track_back_press_counter += delta_ms + else: + self.track_back_press_counter = 0 + + if self.delta_acc < 3000: + self.delta_acc += delta_ms + self.ct_prev = ct + + def on_enter(self, vm: Optional[ViewManager]) -> None: + self._render_list_1 += [(self.draw_background, None)] + self._render_list_1 += [(self.draw_background, None)] # nice diff --git a/python_payload/apps/simple_drums/flow3r.toml b/python_payload/apps/gay_drums/flow3r.toml similarity index 78% rename from python_payload/apps/simple_drums/flow3r.toml rename to python_payload/apps/gay_drums/flow3r.toml index 6d2308c5ddaa060695b701b3384fac7529c02779..278293587e166f382c7f495c04b1be52af045e98 100644 --- a/python_payload/apps/simple_drums/flow3r.toml +++ b/python_payload/apps/gay_drums/flow3r.toml @@ -1,9 +1,9 @@ [app] -name = "Simple Drums" +name = "gay drums" menu = "Music" [entry] -class = "SimpleDrums" +class = "GayDrums" [metadata] author = "Flow3r Badge Authors" diff --git a/python_payload/apps/simple_drums/__init__.py b/python_payload/apps/simple_drums/__init__.py deleted file mode 100644 index 6ee84938b2286aefb44a52dc744698980fcfb64d..0000000000000000000000000000000000000000 --- a/python_payload/apps/simple_drums/__init__.py +++ /dev/null @@ -1,210 +0,0 @@ -import bl00mbox -import captouch -import leds - -from st3m.application import Application, ApplicationContext -from st3m.input import InputState -from st3m.goose import Tuple -from ctx import Context - - -class Dot: - def __init__( - self, - sizex: float, - sizey: float, - imag: float, - real: float, - col: Tuple[float, float, float], - ) -> None: - self.sizex = sizex - self.sizey = sizey - self.imag = imag - self.real = real - self.col = col - - def draw(self, i: int, ctx: Context) -> None: - imag = self.imag - real = self.real - sizex = self.sizex - sizey = self.sizey - col = self.col - - ctx.rgb(*col).rectangle( - -int(imag + (sizex / 2)), -int(real + (sizey / 2)), sizex, sizey - ).fill() - - -class SimpleDrums(Application): - def __init__(self, app_ctx: ApplicationContext) -> None: - super().__init__(app_ctx) - # ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() - self.blm = bl00mbox.Channel("simple drums") - self.seq = self.blm.new(bl00mbox.patches.sequencer) - self.hat = self.blm.new(bl00mbox.patches.sampler, "hihat.wav") - # Dot(10, 10, -30, 0, self._track_col(0)).draw(0,ctx) - self.kick = self.blm.new(bl00mbox.patches.sampler, "kick.wav") - # Dot(20, 20, 0, 40, self._track_col(1)).draw(0,ctx) - self.snare = self.blm.new(bl00mbox.patches.sampler, "snare.wav") - # Dot(30, 30, 2, -20, self._track_col(2)).draw(0,ctx) - self.kick.signals.output = self.blm.mixer - self.snare.signals.output = self.blm.mixer - self.hat.signals.output = self.blm.mixer - self.kick.signals.trigger = self.seq.plugins.seq.signals.track0 - self.hat.signals.trigger = self.seq.plugins.seq.signals.track1 - self.snare.signals.trigger = self.seq.plugins.seq.signals.track2 - self.seq.signals.bpm.value = 80 - - self.track_names = ["kick", "hihat", "snare"] - self.track = 0 - self.blm.background_mute_override = True - self.tap_tempo_press_counter = 0 - self.delta_acc = 0 - self.stopped = False - self.bpm = self.seq.signals.bpm.value - - # True if a given group should be highlighted, when a corresponding - # petal is pressed. - self._group_highlight = [False for _ in range(4)] - - # Disable repeat functionality as we want to detect long holds. - for i in range(10): - self.input.captouch.petals[i].whole.repeat_disable() - - def _highlight_petal(self, num: int, r: int, g: int, b: int) -> None: - for i in range(5): - leds.set_rgb((4 * num - i + 2) % 40, r, g, b) - - def _track_col(self, track: int) -> Tuple[int, int, int]: - rgb = (20, 20, 20) - if track == 0: - rgb = (0, 255, 0) - elif track == 1: - rgb = (0, 0, 255) - elif track == 2: - rgb = (255, 0, 0) - return rgb - - def draw(self, ctx: Context) -> None: - dots = [] - groupgap = 4 - for i in range(4): - if self._group_highlight[i]: - dots.append( - Dot( - 48 + groupgap, - 40, - int((12 * 4 + groupgap) * (1.5 - i)), - 0, - (0.15, 0.15, 0.15), - ) - ) - st = self.seq.signals.step.value - - for track in range(3): - rgb = self._track_col(track) - rgbf = (rgb[0] / 256, rgb[1] / 256, rgb[2] / 256) - y = 12 * (track - 1) - for i in range(16): - trigger_state = self.seq.trigger_state(track, i) - size = 2 - if trigger_state: - size = 8 - x = 12 * (7.5 - i) - x += groupgap * (1.5 - (i // 4)) - x = int(x) - dots.append(Dot(size, size, x, y, rgbf)) - if (i == st) and (track == 0): - dots.append(Dot(size / 2, size / 2, x, 24, (1, 1, 1))) - - dots.append(Dot(1, 40, 0, 0, (0.5, 0.5, 0.5))) - dots.append(Dot(1, 40, 4 * 12 + groupgap, 0, (0.5, 0.5, 0.5))) - dots.append(Dot(1, 40, -4 * 12 - groupgap, 0, (0.5, 0.5, 0.5))) - - ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() - for i, dot in enumerate(dots): - dot.draw(i, ctx) - - ctx.font = ctx.get_font_name(4) - ctx.font_size = 30 - - ctx.move_to(0, 65) - col = [x / 255 for x in self._track_col(self.track)] - ctx.rgb(*col) - ctx.text(self.track_names[self.track]) - - ctx.font_size = 18 - - ctx.move_to(0, 102) - next_track = (self.track + 1) % 3 - col = [x / 255 for x in self._track_col(next_track)] - ctx.rgb(*col) - ctx.text(self.track_names[next_track]) - - ctx.font = ctx.get_font_name(1) - ctx.font_size = 20 - - ctx.move_to(0, -65) - ctx.rgb(255, 255, 255) - ctx.text(str(self.seq.signals.bpm) + " bpm") - - ctx.font_size = 15 - - ctx.move_to(0, -85) - - ctx.rgb(0.6, 0.6, 0.6) - ctx.text("(hold) stop") - - ctx.move_to(0, -100) - ctx.text("tap tempo") - - ctx.move_to(0, 85) - ctx.text("next:") - - def think(self, ins: InputState, delta_ms: int) -> None: - super().think(ins, delta_ms) - st = self.seq.signals.step.value - rgb = self._track_col(self.track) - if st == 0: - leds.set_all_rgb(*[int(x / 4) for x in rgb]) - else: - leds.set_all_rgb(0, 0, 0) - self._highlight_petal(10 - (4 - (st // 4)), *rgb) - self._highlight_petal(10 - (6 + (st % 4)), *rgb) - leds.update() - - petals = self.input.captouch.petals - - self._group_highlight = [False for _ in range(4)] - for i in range(4): - if petals[4 - i].whole.down: - self._group_highlight[i] = True - for j in range(4): - if petals[6 + j].whole.pressed: - self.seq.trigger_toggle(self.track, i * 4 + j) - if petals[5].whole.pressed: - self.track = (self.track - 1) % 3 - if petals[0].whole.pressed: - if self.stopped: - self.seq.signals.bpm = self.bpm - self.blm.background_mute_override = True - self.stopped = False - elif self.delta_acc < 3000 and self.delta_acc > 10: - bpm = int(60000 / self.delta_acc) - if bpm > 40 and bpm < 500: - self.seq.signals.bpm = bpm - self.bpm = bpm - self.delta_acc = 0 - - if petals[0].whole.down: - if self.tap_tempo_press_counter > 500: - self.seq.signals.bpm = 0 - self.stopped = True - self.blm.background_mute_override = False - else: - self.tap_tempo_press_counter += delta_ms - else: - self.tap_tempo_press_counter = 0 - - if self.delta_acc < 3000: - self.delta_acc += delta_ms diff --git a/python_payload/bl00mbox/_patches.py b/python_payload/bl00mbox/_patches.py index be8affe4ba906eab06a13ce889b721fc8888b2f7..0a03d9660b6b4e89b78d5a4e0f187ce288a11ee6 100644 --- a/python_payload/bl00mbox/_patches.py +++ b/python_payload/bl00mbox/_patches.py @@ -1,12 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 import math - import bl00mbox - -try: - import cpython.wave as wave -except ImportError: - wave = None +import cpython.wave as wave class _Patch: @@ -105,15 +100,15 @@ class tinysynth_fm(tinysynth): class sampler(_Patch): """ - needs a wave file with path relative to /flash/sys/samples/ + requires a wave file. default path: /sys/samples/ """ def __init__(self, chan, filename): super().__init__(chan) - if wave is None: - pass - # raise Bl00mboxError("wave library not found") - f = wave.open("/flash/sys/samples/" + filename, "r") + if 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) @@ -146,23 +141,21 @@ class sampler(_Patch): class sequencer(_Patch): - def __init__(self, chan, num=4): + def __init__(self, chan, num_tracks, num_steps): super().__init__(chan) - if num > 32: - num = 32 - if num < 0: - num = 0 - self.seqs = [] - prev_seq = None - self.num_pixels = 16 - self.num_tracks = num - init_var = (self.num_pixels * 256) + (self.num_tracks) # magic + self.num_steps = num_steps + self.num_tracks = num_tracks + init_var = (self.num_steps * 256) + (self.num_tracks) # magic + self.plugins.seq = chan.new(bl00mbox.plugins._sequencer, init_var) self.signals.bpm = self.plugins.seq.signals.bpm self.signals.beat_div = self.plugins.seq.signals.beat_div self.signals.step = self.plugins.seq.signals.step - self.signals.step_len = self.plugins.seq.signals.step_len - tracktable = [-32767] + ([0] * self.num_pixels) + self.signals.step_end = self.plugins.seq.signals.step_end + self.signals.step_start = self.plugins.seq.signals.step_start + self.signals.step_start = self.plugins.seq.signals.step_start + + tracktable = [-32767] + ([0] * self.num_steps) self.plugins.seq.table = tracktable * self.num_tracks def __repr__(self): @@ -193,14 +186,19 @@ class sequencer(_Patch): return ret def _get_table_index(self, track, step): - return step + 1 + track * (self.num_pixels + 1) + return step + 1 + track * (self.num_steps + 1) + + def trigger_start(self, track, step, val=32767): + a = self.plugins.seq.table + a[self._get_table_index(track, step)] = val + self.plugins.seq.table = a - def trigger_start(self, track, step): + def trigger_stop(self, track, step, val=32767): a = self.plugins.seq.table - a[self._get_table_index(track, step)] = 32767 + a[self._get_table_index(track, step)] = -1 self.plugins.seq.table = a - def trigger_stop(self, track, step): + def trigger_clear(self, track, step): a = self.plugins.seq.table a[self._get_table_index(track, step)] = 0 self.plugins.seq.table = a @@ -213,7 +211,7 @@ class sequencer(_Patch): if self.trigger_state(track, step) == 0: self.trigger_start(track, step) else: - self.trigger_stop(track, step) + self.trigger_clear(track, step) class fuzz(_Patch): diff --git a/python_payload/bl00mbox/_user.py b/python_payload/bl00mbox/_user.py index b9c9bf9aaa441b6addae046ea222d1f59b348f19..72150e0b1b0e368a1e20fd58f588d299acdfb457 100644 --- a/python_payload/bl00mbox/_user.py +++ b/python_payload/bl00mbox/_user.py @@ -517,12 +517,6 @@ class Channel: plugin = Plugin(self, thing, plugin_init_var) return plugin - def _new_patch(self, patch, init_var=None): - if init_var == None: - return patch(self) - else: - return patch(self, init_var) - @staticmethod def print_overview(): ret = [] @@ -540,13 +534,13 @@ class Channel: for plugin in self.plugins: print(repr(plugin)) - def new(self, thing, init_var=None): + def new(self, thing, *args, **kwargs): self.free = False if type(thing) == type: if issubclass(thing, bl00mbox.patches._Patch): - return self._new_patch(thing, init_var) + return thing(self, *args, **kwargs) if isinstance(thing, bl00mbox._plugins._Plugin) or (type(thing) == int): - return self._new_plugin(thing, init_var) + return self._new_plugin(thing, *args, **kwargs) @property def plugins(self): diff --git a/python_payload/mypystubs/bl00mbox/_patches.pyi b/python_payload/mypystubs/bl00mbox/_patches.pyi index 8ae4f41df8e1a2d9b4ffd5682a1a3519b6913768..596aea53f4a891b1fecaa4f9b5ce9ff958559b94 100644 --- a/python_payload/mypystubs/bl00mbox/_patches.pyi +++ b/python_payload/mypystubs/bl00mbox/_patches.pyi @@ -28,8 +28,11 @@ class tinysynth_fm(tinysynth): ... class sequencer(_Patch): bpm: int - def trigger_state(self, track: int, i: int) -> bool: ... - def trigger_toggle(self, track: int, i: int) -> None: ... + def trigger_start(self, track: int, step: int, val: int = 32767) -> None: ... + def trigger_stop(self, track: int, step: int, val: int = 32767) -> None: ... + def trigger_clear(self, track: int, step: int) -> None: ... + def trigger_state(self, track: int, step: int) -> int: ... + def trigger_toggle(self, track: int, step: int) -> None: ... class sampler(_Patch): ... diff --git a/python_payload/mypystubs/bl00mbox/_user.pyi b/python_payload/mypystubs/bl00mbox/_user.pyi index 054bb681cba785d1f423051efb5d208163932cf2..d10d4db5b901f1a214b64b22e15f1ffb4cae0e32 100644 --- a/python_payload/mypystubs/bl00mbox/_user.pyi +++ b/python_payload/mypystubs/bl00mbox/_user.pyi @@ -50,14 +50,13 @@ class Channel: def __init__(self, name: str): ... def clear(self) -> None: ... - def _new_patch(self, patch: Type[T], init_var: Optional[Any] = None) -> T: ... def _new_plugin( self, thing: bl00mbox._plugins._Plugin | int, init_var: Optional[int | float] = None, ) -> Plugin: ... @overload - def new(self, thing: Type[P], init_var: Optional[Any] = None) -> P: ... + def new(self, thing: Type[P], *args: Any, **kwargs: Any) -> P: ... @overload def new( self, diff --git a/python_payload/samples/README.md b/python_payload/samples/README.md index ad43546b016fb477a237328c7729492d21589035..46c4c5f88a89ef967242f1d25fc9ff5ae850dded 100644 --- a/python_payload/samples/README.md +++ b/python_payload/samples/README.md @@ -2,3 +2,8 @@ Credits: https://freesound.org/people/sandyrb/sounds/35633/ https://freesound.org/people/kaonaya/sounds/131363/ https://freesound.org/people/Vrezerino/sounds/16709/ +https://freesound.org/people/collinb1000/sounds/634854/ +https://freesound.org/people/exotonestudio/sounds/416249/ +https://freesound.org/people/TheEndOfACycle/sounds/674293/ +https://freesound.org/people/KurireeVA/sounds/476588/ +https://freesound.org/people/boop_7/sounds/684369/ diff --git a/python_payload/samples/bark.wav b/python_payload/samples/bark.wav new file mode 100644 index 0000000000000000000000000000000000000000..a041d36cb105b7858559582ac02bdb5056d1466d Binary files /dev/null and b/python_payload/samples/bark.wav differ diff --git a/python_payload/samples/close.wav b/python_payload/samples/close.wav new file mode 100644 index 0000000000000000000000000000000000000000..5ddb60e4096528298b9f89e1abfe344276376d68 Binary files /dev/null and b/python_payload/samples/close.wav differ diff --git a/python_payload/samples/crash.wav b/python_payload/samples/crash.wav new file mode 100644 index 0000000000000000000000000000000000000000..55e79b487ac8c4773f8bbf4d2e8f7d92425ef367 Binary files /dev/null and b/python_payload/samples/crash.wav differ diff --git a/python_payload/samples/kick.wav b/python_payload/samples/kick.wav index 47232f348dfc7f21bd8f6179ae4281d6c4b42bfa..cf33913a55a20dfc97c3277eadc1ef325b713156 100644 Binary files a/python_payload/samples/kick.wav and b/python_payload/samples/kick.wav differ diff --git a/python_payload/samples/nya.wav b/python_payload/samples/nya.wav new file mode 100644 index 0000000000000000000000000000000000000000..a9a38f9d4575e7e7d23bacb48e92c38693eba7fc Binary files /dev/null and b/python_payload/samples/nya.wav differ diff --git a/python_payload/samples/open.wav b/python_payload/samples/open.wav new file mode 100644 index 0000000000000000000000000000000000000000..4b4640dff73767e473e88030ada9b3f5e2833504 Binary files /dev/null and b/python_payload/samples/open.wav differ diff --git a/python_payload/samples/snare.wav b/python_payload/samples/snare.wav index 696c806a22ae191e82d418fb43a9a99dbda7bc2c..b3fad121ed8fc2221ef3d2bd47af20356ecbd6f8 100644 Binary files a/python_payload/samples/snare.wav and b/python_payload/samples/snare.wav differ diff --git a/python_payload/st3m/goose.py b/python_payload/st3m/goose.py index 51b32bf5c3d21123e10a8d613ebf5872c867da06..f9ee01f20dd577d440b147baa590f6cce2090422 100644 --- a/python_payload/st3m/goose.py +++ b/python_payload/st3m/goose.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: class ABCBase(metaclass=ABCMeta): pass - from typing import List, Optional, Tuple, Dict, Any, Callable + from typing import List, Optional, Tuple, Dict, Any, Callable, Iterator from enum import Enum else: # We're in CPython or Micropython. @@ -31,7 +31,7 @@ else: return _fail try: - from typing import List, Optional, Tuple, Dict, Any, Callable + from typing import List, Optional, Tuple, Dict, Any, Callable, Iterator from enum import Enum except ImportError: # We're in Micropython. @@ -41,6 +41,7 @@ else: Dict = None Any = None Callable = None + Iterator = None class Enum: pass @@ -57,4 +58,5 @@ __all__ = [ "Dict", "Any", "Callable", + "Iterator", ]