Protocol: - Add PREVIEW_NOTE record (id 0x09, node→C): channel, note, velocity, duration_ms — plays a note immediately without touching sequencer state C backend: - sequencer_preview_note(): fires ALSA note-on, spawns detached thread for note-off after duration_ms - RT_PREVIEW_NOTE handler in on_frame() Node server: - encode_preview_note imported from generated protocol - POST /api/preview route forwards to backend Frontend (app.mjs): - Remove pending_notes / is_dirty / Save Notes button; every step-button toggle now calls PUT /api/patterns/:id/notes immediately (auto-save) - Single delegated click handler on #content — no per-render listener accumulation - Row-level ▶ play button per note row → POST /api/preview - Click-to-rename on note/percussion labels: inline <input>, saves to state.custom_labels (Map keyed by "patternId:note"), Escape to cancel - pattern_updated SSE no longer guarded by is_dirty CSS (index.html): - .note-label: cursor pointer + hover highlight - .note-label-input for inline rename field - .row-play-btn and .row-play-spacer for per-row preview buttons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
236 lines
6.4 KiB
C
236 lines
6.4 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_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_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;
|
|
}
|