From 53501a7294bd7ef2f564a2446a7e17c5beb20591 Mon Sep 17 00:00:00 2001 From: Zack Scholl Date: Mon, 4 May 2026 11:18:31 -0700 Subject: [PATCH] fix: triggering first when restarting from clock --- lib/ectocore.h | 12 ++++- lib/ectocore_loopstart_trig.h | 4 +- lib/globals.h | 3 ++ lib/test/ectocore_loopstart_trig/main.c | 68 +++++++++++++++++++------ main.c | 8 ++- 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/lib/ectocore.h b/lib/ectocore.h index 2794f1bf..8fcba777 100644 --- a/lib/ectocore.h +++ b/lib/ectocore.h @@ -713,6 +713,8 @@ void __not_in_flash_func(input_handling)() { uint8_t knob_selector = 0; EctoLoopstartTrigState loopstart_trig_state; ecto_loopstart_trig_state_init(&loopstart_trig_state); + uint32_t loopstart_trig_seen_transport_start_generation = + ecto_loopstart_transport_start_generation; while (1) { #ifdef INCLUDE_MIDI @@ -1793,11 +1795,19 @@ void __not_in_flash_func(input_handling)() { uint8_t current_slice = sample_info != NULL ? sample_info->slice_current : 0; uint8_t slice_num = sample_info != NULL ? sample_info->slice_num : 0; + uint32_t transport_start_generation = + ecto_loopstart_transport_start_generation; + bool transport_started = + transport_start_generation != + loopstart_trig_seen_transport_start_generation; + loopstart_trig_seen_transport_start_generation = + transport_start_generation; EctoLoopstartTrigEvent loopstart_event = ecto_loopstart_trig_step( &loopstart_trig_state, (uintptr_t)sample_info, playback_stopped, current_slice, slice_num, ecto_selected_mode_has_loop_start_transient(ectocore_trigger_mode, - sample_info)); + sample_info), + transport_started); if (loopstart_event != ECTO_LOOPSTART_TRIG_NONE) { if (ecto_trig_out_last == 0 || diff --git a/lib/ectocore_loopstart_trig.h b/lib/ectocore_loopstart_trig.h index 54afb429..d6e6acc2 100644 --- a/lib/ectocore_loopstart_trig.h +++ b/lib/ectocore_loopstart_trig.h @@ -43,7 +43,7 @@ static inline bool ecto_loopstart_trig_transient_pos_is_start( static inline EctoLoopstartTrigEvent ecto_loopstart_trig_step( EctoLoopstartTrigState *state, uintptr_t sample_info_id, bool playback_stopped, uint8_t current_slice, uint8_t slice_num, - bool selected_mode_has_loop_start_transient) { + bool selected_mode_has_loop_start_transient, bool transport_started) { if (state == NULL) { return ECTO_LOOPSTART_TRIG_NONE; } @@ -64,7 +64,7 @@ static inline EctoLoopstartTrigEvent ecto_loopstart_trig_step( bool playback_started_now = state->prev_playback_stopped && !playback_stopped; bool strict_loop_wrap = false; - if (playback_started_now) { + if (!playback_stopped && (playback_started_now || transport_started)) { state->pending = true; } diff --git a/lib/globals.h b/lib/globals.h index 3429b87f..3325c5f1 100644 --- a/lib/globals.h +++ b/lib/globals.h @@ -209,6 +209,9 @@ uint8_t grimoire_rune = 0; bool clock_out_do = false; bool clock_out_ready = false; uint32_t ecto_trig_out_last = 0; +#ifdef INCLUDE_ECTOCORE +volatile uint32_t ecto_loopstart_transport_start_generation = 0; +#endif volatile bool clock_in_do = false; bool clock_input_absent_zeptocore = false; bool clock_in_ready = false; diff --git a/lib/test/ectocore_loopstart_trig/main.c b/lib/test/ectocore_loopstart_trig/main.c index 2018ef99..210707b5 100644 --- a/lib/test/ectocore_loopstart_trig/main.c +++ b/lib/test/ectocore_loopstart_trig/main.c @@ -8,17 +8,17 @@ static void test_playback_start_waits_for_slice_zero(void) { EctoLoopstartTrigState state; ecto_loopstart_trig_state_init(&state); - assert(ecto_loopstart_trig_step(&state, 1, false, 3, 8, true) == + assert(ecto_loopstart_trig_step(&state, 1, false, 3, 8, true, false) == ECTO_LOOPSTART_TRIG_NONE); assert(state.pending); EctoLoopstartTrigEvent event = - ecto_loopstart_trig_step(&state, 1, false, 0, 8, true); + ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, false); assert(event == ECTO_LOOPSTART_TRIG_PLAYBACK_START); ecto_loopstart_trig_mark_emitted(&state, event); assert(!state.pending); - assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, true) == + assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, false) == ECTO_LOOPSTART_TRIG_NONE); } @@ -27,18 +27,54 @@ static void test_wrap_from_last_slice_to_zero(void) { ecto_loopstart_trig_state_init(&state); EctoLoopstartTrigEvent event = - ecto_loopstart_trig_step(&state, 1, false, 0, 8, true); + ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, false); assert(event == ECTO_LOOPSTART_TRIG_PLAYBACK_START); ecto_loopstart_trig_mark_emitted(&state, event); - assert(ecto_loopstart_trig_step(&state, 1, false, 7, 8, true) == + assert(ecto_loopstart_trig_step(&state, 1, false, 7, 8, true, false) == ECTO_LOOPSTART_TRIG_NONE); - assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, true) == + assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, false) == ECTO_LOOPSTART_TRIG_WRAP); - assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, true) == + assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, false) == ECTO_LOOPSTART_TRIG_NONE); } +static void test_transport_restart_emits_while_already_running(void) { + EctoLoopstartTrigState state; + ecto_loopstart_trig_state_init(&state); + + EctoLoopstartTrigEvent event = + ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, false); + assert(event == ECTO_LOOPSTART_TRIG_PLAYBACK_START); + ecto_loopstart_trig_mark_emitted(&state, event); + assert(!state.pending); + + event = ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, true); + assert(event == ECTO_LOOPSTART_TRIG_PLAYBACK_START); + ecto_loopstart_trig_mark_emitted(&state, event); + assert(!state.pending); +} + +static void test_transport_restart_waits_for_slice_zero(void) { + EctoLoopstartTrigState state; + ecto_loopstart_trig_state_init(&state); + + EctoLoopstartTrigEvent event = + ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, false); + assert(event == ECTO_LOOPSTART_TRIG_PLAYBACK_START); + ecto_loopstart_trig_mark_emitted(&state, event); + assert(!state.pending); + + assert(ecto_loopstart_trig_step(&state, 1, false, 3, 8, true, true) == + ECTO_LOOPSTART_TRIG_NONE); + assert(state.pending); + + event = ecto_loopstart_trig_step(&state, 1, false, 0, 8, true, false); + assert(event == ECTO_LOOPSTART_TRIG_PLAYBACK_START); + ecto_loopstart_trig_mark_emitted(&state, event); + assert(!state.pending); +} + static void test_transient_start_window(void) { assert(ecto_loopstart_trig_transient_pos_is_start(0)); assert(ecto_loopstart_trig_transient_pos_is_start(1)); @@ -52,11 +88,11 @@ static void test_random_mode_does_not_emit(void) { EctoLoopstartTrigState state; ecto_loopstart_trig_state_init(&state); - assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, false) == + assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, false, false) == ECTO_LOOPSTART_TRIG_NONE); - assert(ecto_loopstart_trig_step(&state, 1, false, 7, 8, false) == + assert(ecto_loopstart_trig_step(&state, 1, false, 7, 8, false, false) == ECTO_LOOPSTART_TRIG_NONE); - assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, false) == + assert(ecto_loopstart_trig_step(&state, 1, false, 0, 8, false, false) == ECTO_LOOPSTART_TRIG_NONE); } @@ -64,21 +100,21 @@ static void test_sample_change_and_invalid_state_clear_pending(void) { EctoLoopstartTrigState state; ecto_loopstart_trig_state_init(&state); - assert(ecto_loopstart_trig_step(&state, 1, false, 3, 8, true) == + assert(ecto_loopstart_trig_step(&state, 1, false, 3, 8, true, false) == ECTO_LOOPSTART_TRIG_NONE); assert(state.pending); - assert(ecto_loopstart_trig_step(&state, 2, false, 0, 8, true) == + assert(ecto_loopstart_trig_step(&state, 2, false, 0, 8, true, false) == ECTO_LOOPSTART_TRIG_NONE); assert(!state.pending); - assert(ecto_loopstart_trig_step(&state, 2, true, 0, 8, true) == + assert(ecto_loopstart_trig_step(&state, 2, true, 0, 8, true, false) == ECTO_LOOPSTART_TRIG_NONE); - assert(ecto_loopstart_trig_step(&state, 2, false, 3, 8, true) == + assert(ecto_loopstart_trig_step(&state, 2, false, 3, 8, true, false) == ECTO_LOOPSTART_TRIG_NONE); assert(state.pending); - assert(ecto_loopstart_trig_step(&state, 0, false, 0, 0, true) == + assert(ecto_loopstart_trig_step(&state, 0, false, 0, 0, true, false) == ECTO_LOOPSTART_TRIG_NONE); assert(!state.pending); } @@ -86,6 +122,8 @@ static void test_sample_change_and_invalid_state_clear_pending(void) { int main(void) { test_playback_start_waits_for_slice_zero(); test_wrap_from_last_slice_to_zero(); + test_transport_restart_emits_while_already_running(); + test_transport_restart_waits_for_slice_zero(); test_transient_start_window(); test_random_mode_does_not_emit(); test_sample_change_and_invalid_state_clear_pending(); diff --git a/main.c b/main.c index c9aca075..b1f84ebf 100644 --- a/main.c +++ b/main.c @@ -28,8 +28,13 @@ bool __not_in_flash_func(timer_step)() { cancel_repeating_timer(&timer); update_repeating_timer_to_bpm(sf->bpm_tempo); } + bool transport_restarted_this_step = false; if (do_restart_playback) { do_restart_playback = false; +#ifdef INCLUDE_ECTOCORE + ecto_loopstart_transport_start_generation++; +#endif + transport_restarted_this_step = true; playback_restarted = true; bpm_timer_counter = -1; bpm_timer_counter_last = bpm_timer_counter; @@ -515,7 +520,8 @@ bool __not_in_flash_func(timer_step)() { // Only update if beat_current actually changed static int last_beat_current = -1; - if (new_beat_current != last_beat_current) { + if (new_beat_current != last_beat_current || + transport_restarted_this_step) { beat_current = new_beat_current; last_beat_current = beat_current; beat_did_activate = true; // Set flag for LED display update