#include "tui.h" #include #include #include #include #include #include #include #include #include #include #include "carla_host.h" #include "client_cmd.h" #include "plugins.h" #include /* ---------- FIFO command helper ---------- */ int send_command(const char *cmd) { const char *fifo_path = getenv("LOOPER_CMD_FIFO"); if (!fifo_path) fifo_path = "/tmp/looper_cmd"; int fd = open(fifo_path, O_WRONLY | O_NONBLOCK); if (fd < 0) return -1; size_t len = strlen(cmd); int n = write(fd, cmd, len); if (n == (int)len && cmd[len-1] != '\n') write(fd, "\n", 1); close(fd); return (n >= 0) ? 0 : -1; } /* ---------- Stub functions (no engine) ---------- */ // Clip states – dummy values used as placeholders typedef enum { CLIP_EMPTY, CLIP_RECORDING, CLIP_LOOPING, CLIP_STOPPED } ClipState; static const char *clip_state_string(ClipState s) { (void)s; return "?"; } /* Grid dimensions */ #define GRID_ROWS 8 #define GRID_COLS 8 #define NUM_GRIDS 8 #define CELL_WIDTH 6 #define CELL_HEIGHT 3 /* status FIFO path */ #define STATUS_FIFO "/tmp/looper_status" #define CMD_FIFO "/tmp/looper_cmd" /* Per‑cell state array (indexed by row*GRID_COLS+col) */ typedef enum { STATE_IDLE, STATE_RECORD, STATE_LOOPING, STATE_PAUSED } ChannelState; static ChannelState cell_state[GRID_ROWS * GRID_COLS]; /* Color pairs */ enum { COLOR_EMPTY=1, COLOR_RECORDING, COLOR_LOOPING, COLOR_STOPPED, COLOR_SELECTED, COLOR_HELP }; static int selected_row = 0, selected_col = 0; static int selected_grid = 0; static bool show_help = false; static bool rack_mode = false; static int rack_selected = 0; /* Visual mode, marks, yank buffer – keep but only local state */ static int marks[26]; typedef struct { int *clip_indices; int count; } YankBuffer; static YankBuffer yank_buffer = {NULL, 0}; typedef enum { MODE_NORMAL, MODE_VISUAL, MODE_MOVE } UIMode; static UIMode current_mode = MODE_NORMAL; static int visual_start_row, visual_start_col, visual_end_row, visual_end_col; /* Fuzzy search – keep struct but stub carla calls */ typedef struct { char query[256]; int query_len, selected_index, num_results; int result_indices[256]; bool active; char prompt[64]; void (*callback)(const char *); const char **items; int num_items; bool free_items; } FuzzySearch; static FuzzySearch fuzzy_search = {0}; /* ---------- Parse status line from engine status FIFO ---------- */ bool parse_status_line(const char *line, int *ch, int *scene, ChannelState *state) { int sta; if (sscanf(line, "CH=%d SC=%d STATE=%d", ch, scene, &sta) == 3) { if (sta >= 0 && sta <= 3) { *state = (ChannelState)sta; return true; } } /* try text-based format */ char state_str[32]; if (sscanf(line, "CH=%d SC=%d STATE=%31s", ch, scene, state_str) != 3) return false; if (strcmp(state_str, "IDLE") == 0) { *state = STATE_IDLE; return true; } if (strcmp(state_str, "RECORD") == 0) { *state = STATE_RECORD; return true; } if (strcmp(state_str, "LOOPING") == 0) { *state = STATE_LOOPING; return true; } if (strcmp(state_str, "PAUSED") == 0) { *state = STATE_PAUSED; return true; } return false; } /* ---------- State to color (uses cell_state array) ---------- */ static int state_to_color(ChannelState s) { switch (s) { case STATE_IDLE: return COLOR_EMPTY; case STATE_RECORD: return COLOR_RECORDING; case STATE_LOOPING: return COLOR_LOOPING; case STATE_PAUSED: return COLOR_STOPPED; default: return COLOR_EMPTY; } } /* ---------- Draw cell (no AppState) ---------- */ static void draw_cell(int grid, int row, int col, bool selected) { int y = row * CELL_HEIGHT + 3; int x = col * CELL_WIDTH + 1; int idx = row * GRID_COLS + col; ChannelState s = cell_state[idx]; int color = selected ? COLOR_SELECTED : state_to_color(s); attron(COLOR_PAIR(color)); for (int dy=0; dyname ? info->name : "(unnamed)"); if ((int)i == rack_selected) attroff(A_REVERSE); } mvprintw(2+count+1,0,"[B] bypass [D] delete [X] disconnect [R] grid [Esc] back"); refresh(); } static void draw_grid(void) { if (rack_mode) { draw_rack(); return; } clear(); attron(A_BOLD); mvprintw(0,0,"JACK Looper - Client (FIFO only)"); attroff(A_BOLD); for (int r=0; r= 0) { char buf[256]; int n = read(fd, buf, sizeof(buf)-1); if (n > 0) { buf[n] = '\0'; char *line = buf; while (*line) { char *nl = strchr(line, '\n'); if (nl) *nl = '\0'; int ch, sc; ChannelState st; if (parse_status_line(line, &ch, &sc, &st)) { if (ch >= 0 && ch < GRID_ROWS * GRID_COLS) cell_state[ch] = st; } if (nl) { *nl = '\n'; line = nl + 1; } else break; } } close(fd); } if (in_colon) { int chc = getch(); if (chc == '\n') { colon_buf[colon_len] = '\0'; colon_len = 0; in_colon = false; // Check first token before calling handle_client_command char cmd_copy[256]; strncpy(cmd_copy, colon_buf, sizeof(cmd_copy)-1); cmd_copy[sizeof(cmd_copy)-1] = '\0'; char *first = strtok(cmd_copy, " "); if (first) { if (strcmp(first, "rack") == 0) { rack_mode = true; rack_selected = 0; } else if (strcmp(first, "grid") == 0) { rack_mode = false; } } int dummy_id; handle_client_command(colon_buf, &dummy_id); draw_grid(); continue; } else if (chc == 27) { colon_len = 0; in_colon = false; draw_grid(); continue; } else if (chc == KEY_BACKSPACE || chc == 127) { if (colon_len > 0) colon_len--; } else if (chc >= 32 && chc < 127 && colon_len < 255) { colon_buf[colon_len++] = chc; } mvprintw(LINES-1, 0, ":%s", colon_buf); clrtoeol(); move(LINES-1, colon_len+1); refresh(); continue; } int chc = getch(); if (chc == ':') { in_colon = true; colon_len = 0; colon_buf[0] = '\0'; mvprintw(LINES-1, 0, ":"); clrtoeol(); move(LINES-1, 1); refresh(); continue; } switch (chc) { case 'h': case KEY_LEFT: selected_col = (selected_col-1+GRID_COLS)%GRID_COLS; break; case 'j': case KEY_DOWN: selected_row = (selected_row+1)%GRID_ROWS; break; case 'k': case KEY_UP: selected_row = (selected_row-1+GRID_ROWS)%GRID_ROWS; break; case 'l': case KEY_RIGHT: selected_col = (selected_col+1)%GRID_COLS; break; case 't': { char cmd[32]; snprintf(cmd, sizeof(cmd), "record %d\n", selected_col); send_command(cmd); break; } case 's': send_command("scene_next\n"); break; case 'S': send_command("scene_prev\n"); break; case 'd': case 'D': send_command("stop\n"); break; case 'a': send_command("add\n"); break; case 'A': send_command("add_midi\n"); break; case 'r': send_command("remove\n"); break; case 'b': { char cmd[16]; snprintf(cmd, sizeof(cmd), "bind %d\n", selected_col); send_command(cmd); break; } case 'u': send_command("unbind\n"); break; case '?': show_help = !show_help; break; case 'R': rack_mode = !rack_mode; rack_selected = 0; break; case 27: case 'Q': if (rack_mode) { rack_mode = false; break; } return; default: if (rack_mode) { switch (chc) { case 'j': case KEY_DOWN: { CarlaHostHandle h = carla_get_handle(); uint32_t cnt = h ? carla_get_current_plugin_count(h) : 0; if (cnt > 0) rack_selected = (rack_selected + 1) % cnt; } break; case 'k': case KEY_UP: { CarlaHostHandle h = carla_get_handle(); uint32_t cnt = h ? carla_get_current_plugin_count(h) : 0; if (cnt > 0) rack_selected = (rack_selected - 1 + cnt) % cnt; } break; case 'b': case 'B': plugin_set_bypass(rack_selected, true); // toggle would be better, but for now just enable bypass break; case 'd': case 'D': plugin_unload(rack_selected); rack_selected = 0; break; case 'x': case 'X': carla_disconnect_plugin(rack_selected); mvprintw(LINES-1,0,"Disconnected plugin %d", rack_selected); clrtoeol(); refresh(); napms(500); break; } } break; } draw_grid(); } } void tui_cleanup(void) { if (yank_buffer.clip_indices) free(yank_buffer.clip_indices); /* delete FIFOs */ unlink(STATUS_FIFO); unlink(CMD_FIFO); /* close the Carla JACK client */ carla_cleanup_jack(); curs_set(1); endwin(); }