249 lines
9.2 KiB
C
249 lines
9.2 KiB
C
#include "cli.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
|
|
// 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 <index> - Trigger a clip\n");
|
|
printf(" trigger scene <index> - Trigger a scene\n");
|
|
printf(" reset clip <index> - Reset a clip\n");
|
|
printf(" reset transport - Reset transport\n");
|
|
printf(" quantize off|beat|bar - Set quantize mode\n");
|
|
printf(" threshold <samples> - 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 <value> - Set BPM (1.0-999.0)\n");
|
|
printf(" load <clip> <file> - Load WAV file into clip\n");
|
|
printf(" save <clip> - Save clip to samples/clip_<N>.wav\n");
|
|
printf(" grid audio|midi - Switch between Audio and MIDI grid\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 <index>\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 <samples>\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 <clip_index> <filename>\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 <clip_index>\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, "grid") == 0) {
|
|
char *mode_str = strtok(NULL, " \t");
|
|
if (!mode_str) {
|
|
printf("Usage: grid audio|midi\n");
|
|
return 1;
|
|
}
|
|
bool show_midi = (strcasecmp(mode_str, "midi") == 0);
|
|
Action action = { .type = ACTION_SET_SHOW_MIDI_GRID, .data.set_show_midi_grid = { .show = show_midi } };
|
|
engine->dispatch(action);
|
|
printf("Grid: %s\n", show_midi ? "MIDI" : "AUDIO");
|
|
}
|
|
else if (strcasecmp(token, "bpm") == 0) {
|
|
char *bpm_str = strtok(NULL, " \t");
|
|
if (!bpm_str) {
|
|
printf("Usage: bpm <value>\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;
|
|
}
|
|
}
|
|
}
|