#include "cli.h" #include #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(" play - Start transport\n"); printf(" pause - Pause transport\n"); printf(" stop - Stop transport\n"); printf(" toggle - Toggle play/pause\n"); printf(" clock internal|midi - Set clock source\n"); printf(" bpm - Set BPM (1.0-999.0)\n"); printf(" load - Load WAV file into clip\n"); printf(" save - Save clip to samples/clip_.wav\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) { Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip = { .clip_index = idx } }; engine->dispatch(action); } else if (strcasecmp(sub, "scene") == 0) { Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene = { .scene_index = idx } }; engine->dispatch(action); } 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); Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip = { .clip_index = idx } }; engine->dispatch(action); } else if (strcasecmp(sub, "transport") == 0) { Action action = { .type = ACTION_RESET_TRANSPORT }; engine->dispatch(action); } 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); Action action = { .type = ACTION_SET_QUANTIZE_MODE, .data.set_quantize_mode = { .mode = mode } }; engine->dispatch(action); } 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); Action action = { .type = ACTION_SET_QUANTIZE_THRESHOLD, .data.set_quantize_threshold = { .threshold = samples } }; engine->dispatch(action); } else if (strcasecmp(token, "play") == 0) { Action action = { .type = ACTION_TRANSPORT_PLAY }; engine->dispatch(action); printf("Transport: Playing\n"); } else if (strcasecmp(token, "pause") == 0) { Action action = { .type = ACTION_TRANSPORT_PAUSE }; engine->dispatch(action); printf("Transport: Paused\n"); } else if (strcasecmp(token, "stop") == 0) { Action action = { .type = ACTION_TRANSPORT_STOP }; engine->dispatch(action); printf("Transport: Stopped\n"); } else if (strcasecmp(token, "toggle") == 0) { Action action = { .type = ACTION_TRANSPORT_TOGGLE_PLAY }; engine->dispatch(action); printf("Transport: Toggled\n"); } else if (strcasecmp(token, "clock") == 0) { char *source_str = strtok(NULL, " \t"); if (!source_str) { printf("Usage: clock internal|midi\n"); return 1; } if (strcasecmp(source_str, "internal") == 0) { Action action = { .type = ACTION_SET_CLOCK_SOURCE, .data.set_clock_source = { .source = CLOCK_SOURCE_INTERNAL } }; engine->dispatch(action); printf("Clock source: Internal\n"); } else if (strcasecmp(source_str, "midi") == 0) { Action action = { .type = ACTION_SET_CLOCK_SOURCE, .data.set_clock_source = { .source = CLOCK_SOURCE_MIDI } }; engine->dispatch(action); printf("Clock source: MIDI\n"); } else { printf("Unknown clock source: %s\n", source_str); } } else if (strcasecmp(token, "load") == 0) { char *clip_str = strtok(NULL, " \t"); char *filename = strtok(NULL, " \t"); if (!clip_str || !filename) { printf("Usage: load \n"); return 1; } int clip_idx = atoi(clip_str); Action action = { .type = ACTION_LOAD_CLIP, .data.load_clip = { .clip_index = clip_idx } }; strncpy(action.data.load_clip.filename, filename, 255); action.data.load_clip.filename[255] = '\0'; engine->dispatch(action); printf("Loading %s into clip %d...\n", filename, clip_idx); } else if (strcasecmp(token, "save") == 0) { char *clip_str = strtok(NULL, " \t"); if (!clip_str) { printf("Usage: save \n"); return 1; } int clip_idx = atoi(clip_str); Action action = { .type = ACTION_SAVE_CLIP, .data.save_clip = { .clip_index = clip_idx } }; engine->dispatch(action); printf("Saving clip %d...\n", clip_idx); } else if (strcasecmp(token, "bpm") == 0) { char *bpm_str = strtok(NULL, " \t"); if (!bpm_str) { printf("Usage: bpm \n"); return 1; } double bpm = atof(bpm_str); if (bpm >= 1.0 && bpm <= 999.0) { Action action = { .type = ACTION_SET_BPM, .data.set_bpm = { .bpm = bpm } }; engine->dispatch(action); printf("BPM set to: %.1f\n", bpm); } else { printf("BPM must be between 1.0 and 999.0\n"); } } 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; } } }