204 lines
6.4 KiB
C
204 lines
6.4 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include "engine.h"
|
|
#include "tui.h"
|
|
#include "cli.h"
|
|
#include "gui.h"
|
|
#include "dispatcher.h"
|
|
#include "carla.h"
|
|
#include "fs.h"
|
|
#include "logging.h"
|
|
|
|
static Engine engine;
|
|
static volatile int keep_running = 1;
|
|
static DispatchFn dispatch = NULL;
|
|
|
|
void signal_handler(int sig) {
|
|
(void)sig;
|
|
keep_running = 0;
|
|
if (dispatch) {
|
|
Action action = { .type = ACTION_QUIT };
|
|
dispatch(action);
|
|
}
|
|
}
|
|
|
|
void print_usage(const char *program) {
|
|
printf("Usage: %s [options]\n", program);
|
|
printf("Options:\n");
|
|
printf(" -n <name> JACK client name (default: jack-looper)\n");
|
|
printf(" -c <channel> MIDI control channel (default: 0)\n");
|
|
printf(" -t Use TUI frontend (default)\n");
|
|
printf(" -i Use CLI frontend (interactive)\n");
|
|
printf(" -g Use GUI frontend\n");
|
|
printf(" -h Show this help\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
const char *client_name = "jack-looper";
|
|
int control_channel = 0;
|
|
enum { FRONTEND_TUI, FRONTEND_CLI, FRONTEND_GUI } frontend = FRONTEND_TUI;
|
|
int opt;
|
|
|
|
while ((opt = getopt(argc, argv, "n:c:tigh")) != -1) {
|
|
switch (opt) {
|
|
case 'n':
|
|
client_name = optarg;
|
|
break;
|
|
case 'c':
|
|
control_channel = atoi(optarg);
|
|
if (control_channel < 0 || control_channel > 15) {
|
|
fprintf(stderr, "Control channel must be 0-15\n");
|
|
return 1;
|
|
}
|
|
break;
|
|
case 't':
|
|
frontend = FRONTEND_TUI;
|
|
break;
|
|
case 'i':
|
|
frontend = FRONTEND_CLI;
|
|
break;
|
|
case 'g':
|
|
frontend = FRONTEND_GUI;
|
|
break;
|
|
case 'h':
|
|
print_usage(argv[0]);
|
|
return 0;
|
|
default:
|
|
print_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Create initial state (heap-allocated to avoid stack overflow)
|
|
AppState *initial_state = calloc(1, sizeof(AppState));
|
|
if (!initial_state) {
|
|
fprintf(stderr, "Failed to allocate initial state\n");
|
|
return 1;
|
|
}
|
|
initial_state->transport_state = TRANSPORT_STOPPED;
|
|
initial_state->clock_source = CLOCK_SOURCE_INTERNAL;
|
|
initial_state->bpm = 120.0;
|
|
initial_state->quantize_mode = QUANTIZE_OFF;
|
|
initial_state->quantize_threshold = 0;
|
|
initial_state->running = true;
|
|
|
|
for (int i = 0; i < MAX_CLIPS; i++) {
|
|
initial_state->clips[i].state = CLIP_EMPTY;
|
|
initial_state->clips[i].buffer = (float *)calloc(MAX_BUFFER_SIZE, sizeof(float));
|
|
if (!initial_state->clips[i].buffer) {
|
|
fprintf(stderr, "Failed to allocate buffer for clip %d\n", i);
|
|
// Cleanup previously allocated buffers
|
|
for (int j = 0; j < i; j++) {
|
|
free(initial_state->clips[j].buffer);
|
|
}
|
|
free(initial_state);
|
|
return 1;
|
|
}
|
|
initial_state->clips[i].buffer_size = 0;
|
|
initial_state->clips[i].write_position = 0;
|
|
initial_state->clips[i].read_position = 0;
|
|
|
|
// Initialize MIDI clips
|
|
initial_state->midi_clips[i].state = CLIP_EMPTY;
|
|
initial_state->midi_clips[i].event_count = 0;
|
|
initial_state->midi_clips[i].read_index = 0;
|
|
initial_state->midi_clips[i].events = (MidiEvent *)calloc(MAX_MIDI_EVENTS, sizeof(MidiEvent));
|
|
if (!initial_state->midi_clips[i].events) {
|
|
fprintf(stderr, "Failed to allocate MIDI events for clip %d\n", i);
|
|
// Cleanup previously allocated buffers and events
|
|
for (int j = 0; j < i; j++) {
|
|
free(initial_state->clips[j].buffer);
|
|
free(initial_state->midi_clips[j].events);
|
|
}
|
|
free(initial_state);
|
|
return 1;
|
|
}
|
|
initial_state->midi_clips[i].max_events = MAX_MIDI_EVENTS;
|
|
}
|
|
|
|
// Initialize channel names
|
|
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
|
|
snprintf(initial_state->channel_names[ch], 64, "Channel %d", ch);
|
|
}
|
|
initial_state->show_midi_grid = false;
|
|
|
|
// Carla host is now initialized in engine_init
|
|
// Initialize dispatcher
|
|
dispatch = dispatcher_init(initial_state);
|
|
free(initial_state);
|
|
|
|
// Initialize logging
|
|
log_init("jack-looper.log", LOG_LEVEL_DEBUG);
|
|
LOG_INFO("JACK Looper starting...");
|
|
|
|
// Initialize filesystem module (auto-save thread)
|
|
fs_init();
|
|
|
|
// Initialize engine
|
|
if (engine_init(&engine, client_name, dispatch) != 0) {
|
|
fprintf(stderr, "Failed to initialize engine\n");
|
|
return 1;
|
|
}
|
|
|
|
// Set up signal handler
|
|
signal(SIGINT, signal_handler);
|
|
signal(SIGTERM, signal_handler);
|
|
|
|
// Start dispatcher
|
|
dispatcher_start();
|
|
|
|
// Start engine
|
|
if (engine_start(&engine) != 0) {
|
|
fprintf(stderr, "Failed to start engine\n");
|
|
engine_cleanup(&engine);
|
|
dispatcher_stop();
|
|
return 1;
|
|
}
|
|
|
|
printf("JACK Looper started\n");
|
|
printf("Client name: %s\n", client_name);
|
|
printf("Control channel: %d\n", control_channel);
|
|
printf("Sample rate: %u Hz\n", engine.sample_rate);
|
|
printf("Press Ctrl+C to stop\n\n");
|
|
|
|
// Load project file if specified
|
|
if (optind < argc) {
|
|
const char *wheel_file = argv[optind];
|
|
size_t len = strlen(wheel_file);
|
|
if (len > 6 && strcmp(wheel_file + len - 6, ".wheel") == 0) {
|
|
Action action = { .type = ACTION_LOAD_PROJECT };
|
|
strncpy(action.data.load_project.filename, wheel_file, sizeof(action.data.load_project.filename) - 1);
|
|
action.data.load_project.filename[sizeof(action.data.load_project.filename) - 1] = '\0';
|
|
dispatch(action);
|
|
}
|
|
}
|
|
|
|
// Run selected frontend
|
|
switch (frontend) {
|
|
case FRONTEND_TUI:
|
|
tui_init(&engine);
|
|
tui_run(&engine);
|
|
tui_cleanup();
|
|
break;
|
|
case FRONTEND_CLI:
|
|
cli_run(&engine);
|
|
break;
|
|
case FRONTEND_GUI:
|
|
gui_main(&engine);
|
|
break;
|
|
}
|
|
|
|
// Cleanup
|
|
printf("\nShutting down...\n");
|
|
engine_stop(&engine);
|
|
engine_cleanup(&engine);
|
|
fs_cleanup();
|
|
dispatcher_stop();
|
|
log_cleanup();
|
|
|
|
return 0;
|
|
}
|