/* Core 1: owns time. Renders a bar ahead with the engine, sends notes and clock when they're due. Port of the scheduler in sim/app.js (start, stop, pump, track gating, setBpm). */ #include "app.h" #include "midi_uart.h" #include "pico/stdlib.h" #include "pico/flash.h" #include #define LOOK_US 30000 /* render the next bar this far ahead */ #define START_US 5000 /* gap between Start (0xFA) and the first tick */ #define QMAX 512 #define SMAX 64 typedef struct { int64_t tick; uint8_t on, tr, lane, v, vel; int16_t len; } qev_t; typedef struct { uint8_t port, ch, note, tr; int64_t until; } sound_t; static qev_t q[QMAX]; static int qn; static sound_t snd[SMAX]; static int sn; static sb_bar rbar; static int running, bpm, bar; static int64_t anchor_tick, next_bar_tick, next_clock; static uint64_t anchor_us; static struct { int bar; int64_t start, len; } played[4]; /* recently rendered bars, for g_cur_bar */ static int clampi(int x, int a, int b){ return x < a ? a : x > b ? b : x; } int learned(const live_t *L, int tr, int *ch, int *note){ if(tr < 0 || tr >= NKIT || !L->learn[tr].used) return 0; *ch = L->learn[tr].ch & 15; *note = L->learn[tr].note & 127; return 1; } static void snapshot(live_t *out){ critical_section_enter_blocking(&g_live_lock); *out = g_live; critical_section_exit(&g_live_lock); } /* tick <-> time at the current tempo: 60e6 / (bpm * 840) µs per tick */ static uint64_t time_of(int64_t t){ return anchor_us + (uint64_t)(((t - anchor_tick) * 60000000LL) / ((int64_t)bpm * SB_TPB)); } static int64_t tick_at(uint64_t us){ return anchor_tick + ((int64_t)(us - anchor_us) * bpm * SB_TPB) / 60000000LL; } static void send3(int port, int a, int b, int c){ uint8_t m[3] = {(uint8_t)a, (uint8_t)b, (uint8_t)c}; midi_send(port, m, 3); } static void send1_both(int a){ uint8_t m = (uint8_t)a; midi_send(MIDI_KIT, &m, 1); midi_send(MIDI_SYNTH, &m, 1); } /* queue sorted by (tick, on); a new event goes after equal keys, like a stable sort of push order */ static void enqueue(int64_t tick, int on, const sb_event *e){ int i = qn; if(qn >= QMAX) return; while(i > 0 && (q[i - 1].tick > tick || (q[i - 1].tick == tick && q[i - 1].on > on))) i--; memmove(&q[i + 1], &q[i], (size_t)(qn - i) * sizeof q[0]); q[i].tick = tick; q[i].on = (uint8_t)on; q[i].tr = e->tr; q[i].lane = e->lane; q[i].v = e->v; q[i].vel = e->vel; q[i].len = e->len; qn++; } static int find_sound(int port, int ch, int note){ int i; for(i = 0; i < sn; i++) if(snd[i].port == port && snd[i].ch == ch && snd[i].note == note) return i; return -1; } static void drop_sound(int i){ snd[i] = snd[--sn]; } /* chFor / noteFor: returns 0 when a kit track isn't learned (note === null) */ static int resolve(const live_t *L, const qev_t *e, int *ch, int *note){ if(e->lane == 1){ *ch = clampi(L->mono_ch, 1, 16) - 1; *note = clampi(e->v + 12 * L->mono_oct, 0, 127); return 1; } return learned(L, e->tr, ch, note); } static void dispatch(const qev_t *e){ live_t L; int ch, note, port = e->lane ? MIDI_SYNTH : MIDI_KIT, i; snapshot(&L); if(!resolve(&L, e, &ch, ¬e)) return; if(e->on){ if(!(L.on[e->tr] ^ L.inv[e->tr])) return; send3(port, 0x90 | ch, note, e->vel); i = find_sound(port, ch, note); if(i < 0 && sn < SMAX){ i = sn++; snd[i].port = (uint8_t)port; snd[i].ch = (uint8_t)ch; snd[i].note = (uint8_t)note; } if(i >= 0){ snd[i].tr = e->tr; snd[i].until = e->tick + e->len; } g_hit_until_ms[e->tr] = to_ms_since_boot(get_absolute_time()) + 80; } else { i = find_sound(port, ch, note); if(i >= 0){ send3(port, 0x80 | ch, note, 0); if(snd[i].until <= e->tick) drop_sound(i); } } } static void release_where(int track){ /* track < 0: any */ int i = 0; while(i < sn){ int match = track < 0 || snd[i].tr == track; if(match){ send3(snd[i].port, 0x80 | snd[i].ch, snd[i].note, 0); drop_sound(i); } else i++; } } static void start(void){ live_t L; if(running) return; snapshot(&L); bpm = L.bpm; anchor_us = time_us_64() + START_US; anchor_tick = 0; next_bar_tick = 0; bar = 0; next_clock = 0; qn = 0; sn = 0; memset(played, 0xFF, sizeof played); if(L.clock) send1_both(0xFA); running = 1; g_running = 1; } static void stop(void){ live_t L; int t, i, ch, note, used[16] = {0}; if(!running) return; running = 0; snapshot(&L); release_where(-1); for(t = 0; t < NKIT; t++) if(learned(&L, t, &ch, ¬e)) used[ch] = 1; for(i = 0; i < 16; i++) if(used[i]) send3(MIDI_KIT, 0xB0 | i, 120, 0); /* All Sound Off on every kit channel */ i = clampi(L.mono_ch, 1, 16) - 1; send3(MIDI_SYNTH, 0xB0 | i, 120, 0); send3(MIDI_SYNTH, 0xB0 | i, 123, 0); if(L.clock) send1_both(0xFC); qn = 0; sn = 0; g_running = 0; g_cur_bar = -1; } static void set_bpm(int b){ if(running){ uint64_t now = time_us_64(); int64_t tk = tick_at(now); anchor_us = now; anchor_tick = tk; } bpm = b; } static void render_ahead(uint64_t now){ while(time_of(next_bar_tick) < now + LOOK_US){ live_t L; sb_params p; int i; snapshot(&L); p.gen = L.gen; p.keys = L.keys; p.energy = L.energy; p.drift = L.drift; p.fill = 0; p.scale = L.scale; p.root = L.root; /* no FILL button: bar-4 fills happen on their own */ sb_render_bar(&L.state, &p, bar, &rbar); for(i = 0; i < rbar.n; i++){ enqueue(next_bar_tick + rbar.ev[i].t, 1, &rbar.ev[i]); enqueue(next_bar_tick + rbar.ev[i].t + rbar.ev[i].len, 0, &rbar.ev[i]); } played[bar & 3].bar = bar; played[bar & 3].start = next_bar_tick; played[bar & 3].len = rbar.barTicks; next_bar_tick += rbar.barTicks; bar++; } } static void update_cur_bar(uint64_t now){ int64_t tk = tick_at(now); int i, cur = -1; for(i = 0; i < 4; i++) if(played[i].bar >= 0 && tk >= played[i].start && tk < played[i].start + played[i].len) cur = played[i].bar; g_cur_bar = cur; } void seq_core1_main(void){ flash_safe_execute_core_init(); /* lets core 0 pause this core while it writes flash */ g_cur_bar = -1; for(;;){ cmd_t c; uint64_t now; while(queue_try_remove(&g_cmdq, &c)){ if(c.cmd == CMD_START) start(); else if(c.cmd == CMD_STOP) stop(); else if(c.cmd == CMD_RELEASE_TRACK) release_where(c.arg); else if(c.cmd == CMD_BPM) set_bpm(c.arg); } if(!running) continue; now = time_us_64(); render_ahead(now); if(g_live.clock){ while(time_of(next_clock) <= now){ send1_both(0xF8); next_clock += SB_CLOCK_TICKS; } } else next_clock = tick_at(now) / SB_CLOCK_TICKS * SB_CLOCK_TICKS + SB_CLOCK_TICKS; while(qn && time_of(q[0].tick) <= now){ qev_t e = q[0]; qn--; memmove(&q[0], &q[1], (size_t)qn * sizeof q[0]); dispatch(&e); } update_cur_bar(now); } }