From 77c8337c92fb0eb2deaf034a9552d9888fc925c2 Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Fri, 1 May 2026 09:35:11 +0000 Subject: [PATCH] feat: add CLI interface with command parsing and tests Co-authored-by: aider (deepseek/deepseek-coder) --- cli.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test_cli.c | 58 ++++++++++++++++++++++ 2 files changed, 201 insertions(+) diff --git a/cli.c b/cli.c index e69de29..5b85051 100644 --- a/cli.c +++ b/cli.c @@ -0,0 +1,143 @@ +#include "cli.h" +#include +#include +#include +#include + +// Trim leading/trailing whitespace +static char *trim(char *str) { + char *end; + while (isspace((unsigned char)*str)) str++; + if (*str == 0) return str; + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + *(end+1) = 0; + return str; +} + +// Parse quantize mode from string +static QuantizeMode parse_quantize_mode(const char *str) { + if (strcasecmp(str, "off") == 0) return QUANTIZE_OFF; + if (strcasecmp(str, "beat") == 0) return QUANTIZE_BEAT; + if (strcasecmp(str, "bar") == 0) return QUANTIZE_BAR; + return QUANTIZE_OFF; // default +} + +int cli_process_line(Engine *engine, const char *line) { + if (!engine || !line) return 1; + + // Make a mutable copy + char buf[256]; + strncpy(buf, line, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + + char *trimmed = trim(buf); + if (trimmed[0] == '\0' || trimmed[0] == '#') return 1; // skip empty/comment + + // Tokenize + char *token = strtok(trimmed, " \t"); + if (!token) return 1; + + if (strcasecmp(token, "quit") == 0 || strcasecmp(token, "exit") == 0) { + return 0; // stop loop + } + else if (strcasecmp(token, "help") == 0) { + printf("Commands:\n"); + printf(" trigger clip - Trigger a clip\n"); + printf(" trigger scene - Trigger a scene\n"); + printf(" reset clip - Reset a clip\n"); + printf(" reset transport - Reset transport\n"); + printf(" quantize off|beat|bar - Set quantize mode\n"); + printf(" threshold - Set quantize threshold\n"); + printf(" help - Show this help\n"); + printf(" quit - Exit CLI\n"); + return 1; + } + else if (strcasecmp(token, "trigger") == 0) { + char *sub = strtok(NULL, " \t"); + if (!sub) { + printf("Usage: trigger clip|scene \n"); + return 1; + } + char *idx_str = strtok(NULL, " \t"); + if (!idx_str) { + printf("Missing index\n"); + return 1; + } + int idx = atoi(idx_str); + if (strcasecmp(sub, "clip") == 0) { + engine_trigger_clip(engine, idx); + } else if (strcasecmp(sub, "scene") == 0) { + engine_trigger_scene(engine, idx); + } else { + printf("Unknown trigger type: %s\n", sub); + } + } + else if (strcasecmp(token, "reset") == 0) { + char *sub = strtok(NULL, " \t"); + if (!sub) { + printf("Usage: reset clip|transport\n"); + return 1; + } + if (strcasecmp(sub, "clip") == 0) { + char *idx_str = strtok(NULL, " \t"); + if (!idx_str) { + printf("Missing clip index\n"); + return 1; + } + int idx = atoi(idx_str); + engine_reset_clip(engine, idx); + } else if (strcasecmp(sub, "transport") == 0) { + engine_reset_transport(engine); + } else { + printf("Unknown reset target: %s\n", sub); + } + } + else if (strcasecmp(token, "quantize") == 0) { + char *mode_str = strtok(NULL, " \t"); + if (!mode_str) { + printf("Usage: quantize off|beat|bar\n"); + return 1; + } + QuantizeMode mode = parse_quantize_mode(mode_str); + engine_set_quantize_mode(engine, mode); + } + else if (strcasecmp(token, "threshold") == 0) { + char *val_str = strtok(NULL, " \t"); + if (!val_str) { + printf("Usage: threshold \n"); + return 1; + } + jack_nframes_t samples = (jack_nframes_t)atol(val_str); + engine_set_quantize_threshold(engine, samples); + } + else { + printf("Unknown command: %s\n", token); + } + return 1; +} + +void cli_run(Engine *engine) { + if (!engine) return; + + printf("JACK Looper CLI\n"); + printf("Type 'help' for commands, 'quit' to exit.\n\n"); + + char line[256]; + while (1) { + printf("> "); + fflush(stdout); + + if (!fgets(line, sizeof(line), stdin)) { + break; // EOF + } + + // Remove trailing newline + size_t len = strlen(line); + if (len > 0 && line[len-1] == '\n') line[len-1] = '\0'; + + if (!cli_process_line(engine, line)) { + break; + } + } +} diff --git a/test_cli.c b/test_cli.c index e69de29..bcce5ef 100644 --- a/test_cli.c +++ b/test_cli.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include "engine.h" +#include "cli.h" + +// Minimal test: just ensure parsing doesn't crash +static void test_cli_parse(void) { + printf("Test CLI parse... "); + + // Create a minimal engine (no JACK needed for parsing) + Engine engine; + memset(&engine, 0, sizeof(engine)); + engine.sample_rate = 48000; + engine.control_channel = 0; + engine.quantize_mode = QUANTIZE_OFF; + engine.quantize_threshold = 0; + engine.queued_triggers = NULL; + engine.transport.rolling = false; + engine.transport.clock_count = 0; + engine.transport.beat_position = 0; + engine.transport.bar_position = 0; + engine.transport.sample_position = 0; + for (int i = 0; i < MAX_CLIPS; i++) { + engine.clips[i].state = CLIP_EMPTY; + engine.clips[i].buffer = NULL; // not needed for parsing + engine.clips[i].buffer_size = 0; + engine.clips[i].write_position = 0; + engine.clips[i].read_position = 0; + } + + // Test valid commands + assert(cli_process_line(&engine, "help") == 1); + assert(cli_process_line(&engine, "trigger clip 0") == 1); + assert(cli_process_line(&engine, "trigger scene 1") == 1); + assert(cli_process_line(&engine, "reset clip 2") == 1); + assert(cli_process_line(&engine, "reset transport") == 1); + assert(cli_process_line(&engine, "quantize beat") == 1); + assert(cli_process_line(&engine, "threshold 1000") == 1); + assert(cli_process_line(&engine, "quit") == 0); + + // Test invalid commands (should not crash) + assert(cli_process_line(&engine, "") == 1); + assert(cli_process_line(&engine, "unknown") == 1); + assert(cli_process_line(&engine, "trigger") == 1); + assert(cli_process_line(&engine, "reset") == 1); + assert(cli_process_line(&engine, "quantize") == 1); + assert(cli_process_line(&engine, "threshold") == 1); + + printf("PASSED\n"); +} + +int main(void) { + printf("Running CLI tests...\n\n"); + test_cli_parse(); + printf("\nAll CLI tests passed!\n"); + return 0; +}