Files
mikael-lovqvists-claude-agent 9b45905d80 Add multi-track playback with mute/solo support
Replaces the single-root-pattern sequencer with a Track[] array that
allows multiple patterns to loop independently. Adds ADD_TRACK (0x0A),
REMOVE_TRACK (0x0B), PLAY_TRACKS (0x0C), and SET_TRACK_MUTE (0x0D)
protocol records. The C backend gains per-track pending_subs and a
tracks_mutex. The Node server gains track-state APIs (/api/tracks/:id/
active, mute, solo) and the frontend shows per-pattern track/mute/solo
buttons in the sidebar list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 04:16:06 +00:00

289 lines
7.8 KiB
C

/* AUTO-GENERATED by codegen/gen.mjs — DO NOT EDIT */
#include "protocol.h"
/* ── serialization helpers ───────────────────────────────────── */
static void put_u8(uint8_t *buf, int *off, uint8_t v) {
buf[(*off)++] = v;
}
static void put_u16(uint8_t *buf, int *off, uint16_t v) {
buf[(*off)++] = (uint8_t)(v & 0xFF);
buf[(*off)++] = (uint8_t)(v >> 8);
}
static uint8_t get_u8(const uint8_t *buf, int *off) {
return buf[(*off)++];
}
static uint16_t get_u16(const uint8_t *buf, int *off) {
uint16_t v = (uint16_t)((uint16_t)buf[*off] | ((uint16_t)buf[*off + 1] << 8));
*off += 2;
return v;
}
static int write_frame(uint8_t *buf, uint8_t record_type, int payload_len) {
buf[0] = record_type;
buf[1] = (uint8_t)(payload_len & 0xFF);
buf[2] = (uint8_t)(payload_len >> 8);
return FRAME_HEADER_SIZE + payload_len;
}
/* ── encode ──────────────────────────────────────────────────── */
int proto_encode_hello(uint8_t *buf, const Msg_Hello *r) {
int off = FRAME_HEADER_SIZE;
put_u8(buf, &off, r->version);
return write_frame(buf, RT_HELLO, 1);
}
int proto_encode_define_pattern(uint8_t *buf, const Msg_Define_Pattern *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
put_u8(buf, &off, r->steps);
put_u8(buf, &off, r->channel);
return write_frame(buf, RT_DEFINE_PATTERN, 4);
}
int proto_encode_clear_pattern(uint8_t *buf, const Msg_Clear_Pattern *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
return write_frame(buf, RT_CLEAR_PATTERN, 2);
}
int proto_encode_add_note(uint8_t *buf, const Msg_Add_Note *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
put_u8(buf, &off, r->step);
put_u8(buf, &off, r->note);
put_u8(buf, &off, r->velocity);
put_u8(buf, &off, r->duration_steps);
return write_frame(buf, RT_ADD_NOTE, 6);
}
int proto_encode_add_sub_pattern(uint8_t *buf, const Msg_Add_Sub_Pattern *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
put_u8(buf, &off, r->step);
put_u16(buf, &off, r->sub_pattern_id);
return write_frame(buf, RT_ADD_SUB_PATTERN, 5);
}
int proto_encode_play(uint8_t *buf, const Msg_Play *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
return write_frame(buf, RT_PLAY, 2);
}
int proto_encode_stop(uint8_t *buf) {
return write_frame(buf, RT_STOP, 0);
}
int proto_encode_set_tempo(uint8_t *buf, const Msg_Set_Tempo *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->bpm_x10);
return write_frame(buf, RT_SET_TEMPO, 2);
}
int proto_encode_preview_note(uint8_t *buf, const Msg_Preview_Note *r) {
int off = FRAME_HEADER_SIZE;
put_u8(buf, &off, r->channel);
put_u8(buf, &off, r->note);
put_u8(buf, &off, r->velocity);
put_u16(buf, &off, r->duration_ms);
return write_frame(buf, RT_PREVIEW_NOTE, 5);
}
int proto_encode_add_track(uint8_t *buf, const Msg_Add_Track *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
return write_frame(buf, RT_ADD_TRACK, 2);
}
int proto_encode_remove_track(uint8_t *buf, const Msg_Remove_Track *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
return write_frame(buf, RT_REMOVE_TRACK, 2);
}
int proto_encode_play_tracks(uint8_t *buf) {
return write_frame(buf, RT_PLAY_TRACKS, 0);
}
int proto_encode_set_track_mute(uint8_t *buf, const Msg_Set_Track_Mute *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
put_u8(buf, &off, r->muted);
return write_frame(buf, RT_SET_TRACK_MUTE, 3);
}
int proto_encode_ack(uint8_t *buf, const Msg_Ack *r) {
int off = FRAME_HEADER_SIZE;
put_u8(buf, &off, r->acked_type);
return write_frame(buf, RT_ACK, 1);
}
int proto_encode_error(uint8_t *buf, const Msg_Error *r) {
int off = FRAME_HEADER_SIZE;
put_u8(buf, &off, r->code);
put_u8(buf, &off, r->context_type);
return write_frame(buf, RT_ERROR, 2);
}
int proto_encode_beat_tick(uint8_t *buf, const Msg_Beat_Tick *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
put_u8(buf, &off, r->step);
put_u8(buf, &off, r->beat);
return write_frame(buf, RT_BEAT_TICK, 4);
}
int proto_encode_pattern_end(uint8_t *buf, const Msg_Pattern_End *r) {
int off = FRAME_HEADER_SIZE;
put_u16(buf, &off, r->pattern_id);
return write_frame(buf, RT_PATTERN_END, 2);
}
/* ── decode ──────────────────────────────────────────────────── */
int proto_decode_hello(const uint8_t *p, uint16_t len, Msg_Hello *r) {
if (len < 1) return -1;
int off = 0;
r->version = get_u8(p, &off);
(void)off;
return 0;
}
int proto_decode_define_pattern(const uint8_t *p, uint16_t len, Msg_Define_Pattern *r) {
if (len < 4) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
r->steps = get_u8(p, &off);
r->channel = get_u8(p, &off);
(void)off;
return 0;
}
int proto_decode_clear_pattern(const uint8_t *p, uint16_t len, Msg_Clear_Pattern *r) {
if (len < 2) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
(void)off;
return 0;
}
int proto_decode_add_note(const uint8_t *p, uint16_t len, Msg_Add_Note *r) {
if (len < 6) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
r->step = get_u8(p, &off);
r->note = get_u8(p, &off);
r->velocity = get_u8(p, &off);
r->duration_steps = get_u8(p, &off);
(void)off;
return 0;
}
int proto_decode_add_sub_pattern(const uint8_t *p, uint16_t len, Msg_Add_Sub_Pattern *r) {
if (len < 5) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
r->step = get_u8(p, &off);
r->sub_pattern_id = get_u16(p, &off);
(void)off;
return 0;
}
int proto_decode_play(const uint8_t *p, uint16_t len, Msg_Play *r) {
if (len < 2) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
(void)off;
return 0;
}
int proto_decode_stop(const uint8_t *p, uint16_t len, Msg_Stop *r) {
(void)p; (void)len; (void)r;
return 0;
}
int proto_decode_set_tempo(const uint8_t *p, uint16_t len, Msg_Set_Tempo *r) {
if (len < 2) return -1;
int off = 0;
r->bpm_x10 = get_u16(p, &off);
(void)off;
return 0;
}
int proto_decode_preview_note(const uint8_t *p, uint16_t len, Msg_Preview_Note *r) {
if (len < 5) return -1;
int off = 0;
r->channel = get_u8(p, &off);
r->note = get_u8(p, &off);
r->velocity = get_u8(p, &off);
r->duration_ms = get_u16(p, &off);
(void)off;
return 0;
}
int proto_decode_add_track(const uint8_t *p, uint16_t len, Msg_Add_Track *r) {
if (len < 2) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
(void)off;
return 0;
}
int proto_decode_remove_track(const uint8_t *p, uint16_t len, Msg_Remove_Track *r) {
if (len < 2) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
(void)off;
return 0;
}
int proto_decode_play_tracks(const uint8_t *p, uint16_t len, Msg_Play_Tracks *r) {
(void)p; (void)len; (void)r;
return 0;
}
int proto_decode_set_track_mute(const uint8_t *p, uint16_t len, Msg_Set_Track_Mute *r) {
if (len < 3) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
r->muted = get_u8(p, &off);
(void)off;
return 0;
}
int proto_decode_ack(const uint8_t *p, uint16_t len, Msg_Ack *r) {
if (len < 1) return -1;
int off = 0;
r->acked_type = get_u8(p, &off);
(void)off;
return 0;
}
int proto_decode_error(const uint8_t *p, uint16_t len, Msg_Error *r) {
if (len < 2) return -1;
int off = 0;
r->code = get_u8(p, &off);
r->context_type = get_u8(p, &off);
(void)off;
return 0;
}
int proto_decode_beat_tick(const uint8_t *p, uint16_t len, Msg_Beat_Tick *r) {
if (len < 4) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
r->step = get_u8(p, &off);
r->beat = get_u8(p, &off);
(void)off;
return 0;
}
int proto_decode_pattern_end(const uint8_t *p, uint16_t len, Msg_Pattern_End *r) {
if (len < 2) return -1;
int off = 0;
r->pattern_id = get_u16(p, &off);
(void)off;
return 0;
}