#include #include #include #include #include #include "engine.h" #include "tui.h" #include "cli.h" #include "gui.h" #include "dispatcher.h" #include "carla.h" #include "fs.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 JACK client name (default: jack-looper)\n"); printf(" -c 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 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(); return 0; }