#include #include #include #include #include #include "pattern_store.h" #include "sequencer.h" #include "socket_server.h" #include "../generated/protocol.h" /* ── Application context ──────────────────────────────────────────── */ typedef struct { Pattern_Store ps; Sequencer seq; Socket_Server srv; } App; static App g_app; /* ── Send callback for sequencer ─────────────────────────────────── */ static void seq_send(void *ctx, const uint8_t *buf, int len) { App *app = (App *)ctx; socket_server_send(&app->srv, buf, len); } /* ── Frame handler ────────────────────────────────────────────────── */ static void send_ack(App *app, uint8_t acked_type) { uint8_t buf[16]; Msg_Ack ack = { .acked_type = acked_type }; int len = proto_encode_ack(buf, &ack); socket_server_send(&app->srv, buf, len); } static void send_error(App *app, uint8_t code, uint8_t context_type) { uint8_t buf[16]; Msg_Error err = { .code = code, .context_type = context_type }; int len = proto_encode_error(buf, &err); socket_server_send(&app->srv, buf, len); } static void on_frame(uint8_t rt, const uint8_t *payload, uint16_t payload_len, void *ctx) { App *app = (App *)ctx; switch (rt) { case RT_HELLO: { Msg_Hello msg; if (proto_decode_hello(payload, payload_len, &msg) < 0) goto bad_payload; fprintf(stderr, "main: HELLO from client, version=%u\n", msg.version); /* Reply with our HELLO */ uint8_t buf[16]; Msg_Hello reply = { .version = PROTOCOL_VERSION }; int len = proto_encode_hello(buf, &reply); socket_server_send(&app->srv, buf, len); return; } case RT_DEFINE_PATTERN: { Msg_Define_Pattern msg; if (proto_decode_define_pattern(payload, payload_len, &msg) < 0) goto bad_payload; pattern_store_define(&app->ps, msg.pattern_id, msg.steps, msg.channel); break; } case RT_CLEAR_PATTERN: { Msg_Clear_Pattern msg; if (proto_decode_clear_pattern(payload, payload_len, &msg) < 0) goto bad_payload; pattern_store_clear(&app->ps, msg.pattern_id); break; } case RT_ADD_NOTE: { Msg_Add_Note msg; if (proto_decode_add_note(payload, payload_len, &msg) < 0) goto bad_payload; if (pattern_store_add_note(&app->ps, msg.pattern_id, msg.step, msg.note, msg.velocity, msg.duration_steps) < 0) { send_error(app, 0x01, rt); return; } break; } case RT_ADD_SUB_PATTERN: { Msg_Add_Sub_Pattern msg; if (proto_decode_add_sub_pattern(payload, payload_len, &msg) < 0) goto bad_payload; if (pattern_store_add_sub_ref(&app->ps, msg.pattern_id, msg.step, msg.sub_pattern_id) < 0) { send_error(app, 0x02, rt); return; } break; } case RT_PLAY: { Msg_Play msg; if (proto_decode_play(payload, payload_len, &msg) < 0) goto bad_payload; sequencer_stop(&app->seq); sequencer_add_track(&app->seq, msg.pattern_id); sequencer_play(&app->seq); break; } case RT_STOP: { sequencer_stop(&app->seq); break; } case RT_SET_TEMPO: { Msg_Set_Tempo msg; if (proto_decode_set_tempo(payload, payload_len, &msg) < 0) goto bad_payload; sequencer_set_tempo(&app->seq, msg.bpm_x10); break; } case RT_PREVIEW_NOTE: { Msg_Preview_Note msg; if (proto_decode_preview_note(payload, payload_len, &msg) < 0) goto bad_payload; sequencer_preview_note(&app->seq, msg.channel, msg.note, msg.velocity, msg.duration_ms); break; } case RT_ADD_TRACK: { Msg_Add_Track msg; if (proto_decode_add_track(payload, payload_len, &msg) < 0) goto bad_payload; sequencer_add_track(&app->seq, msg.pattern_id); break; } case RT_REMOVE_TRACK: { Msg_Remove_Track msg; if (proto_decode_remove_track(payload, payload_len, &msg) < 0) goto bad_payload; sequencer_remove_track(&app->seq, msg.pattern_id); break; } case RT_PLAY_TRACKS: { sequencer_play(&app->seq); break; } case RT_SET_TRACK_MUTE: { Msg_Set_Track_Mute msg; if (proto_decode_set_track_mute(payload, payload_len, &msg) < 0) goto bad_payload; sequencer_set_track_mute(&app->seq, msg.pattern_id, msg.muted); break; } default: fprintf(stderr, "main: unknown record type 0x%02x\n", rt); return; } send_ack(app, rt); return; bad_payload: fprintf(stderr, "main: bad payload for record type 0x%02x\n", rt); send_error(app, 0xFF, rt); } /* ── Connect / disconnect callbacks ──────────────────────────────── */ static void on_connect(int fd, void *ctx) { (void)fd; (void)ctx; fprintf(stderr, "main: node client connected\n"); } static void on_disconnect(void *ctx) { App *app = (App *)ctx; sequencer_stop(&app->seq); fprintf(stderr, "main: node client disconnected, playback stopped\n"); } /* ── Signal handler ───────────────────────────────────────────────── */ static volatile int g_running = 1; static void on_signal(int sig) { (void)sig; g_running = 0; } /* ── Entry point ──────────────────────────────────────────────────── */ int main(int argc, char *argv[]) { const char *socket_path = argc > 1 ? argv[1] : "/tmp/midi-sequencer.sock"; const char *alsa_name = argc > 2 ? argv[2] : "midi-sequencer"; fprintf(stderr, "midi-sequencer starting\n"); fprintf(stderr, " socket : %s\n", socket_path); fprintf(stderr, " ALSA : %s\n", alsa_name); pattern_store_init(&g_app.ps); if (sequencer_init(&g_app.seq, &g_app.ps, alsa_name, seq_send, &g_app) < 0) { fprintf(stderr, "main: sequencer init failed\n"); return 1; } if (socket_server_start(&g_app.srv, socket_path, on_frame, on_connect, on_disconnect, &g_app) < 0) { fprintf(stderr, "main: socket server failed to start\n"); return 1; } signal(SIGINT, on_signal); signal(SIGTERM, on_signal); fprintf(stderr, "midi-sequencer ready, waiting for node client\n"); while (g_running) { sleep(1); } fprintf(stderr, "midi-sequencer shutting down\n"); socket_server_stop(&g_app.srv); sequencer_destroy(&g_app.seq); pattern_store_destroy(&g_app.ps); return 0; }