diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..70b215e --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +# Top‑level Makefile – delegates build/clean/test to subdirectories + +SUBDIRS = engine client + +.PHONY: all build clean test $(SUBDIRS) + +all: build + +build: $(SUBDIRS) + @echo "Build complete." + +$(SUBDIRS): + $(MAKE) -C $@ + +test: + $(MAKE) -C engine test + +clean: + @for dir in $(SUBDIRS); do \ + echo "Cleaning $$dir..."; \ + $(MAKE) -C $$dir clean; \ + done diff --git a/client/main.c b/client/main.c new file mode 100644 index 0000000..97a85c5 --- /dev/null +++ b/client/main.c @@ -0,0 +1,8 @@ +#include "tui.h" + +int main(void) { + tui_init(); + tui_run(); + tui_cleanup(); + return 0; +} diff --git a/client/makefile b/client/makefile new file mode 100644 index 0000000..16ec8a7 --- /dev/null +++ b/client/makefile @@ -0,0 +1,10 @@ +CC ?= gcc +CFLAGS ?= -Wall -Wextra -g -Isrc +LDFLAGS ?= -lncurses -lm + +looper-client: src/tui.c main.c + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +.PHONY: clean +clean: + rm -f looper-client diff --git a/client/src/tui.c b/client/src/tui.c index 75558c8..e3aa640 100644 --- a/client/src/tui.c +++ b/client/src/tui.c @@ -1,1137 +1,105 @@ #include "tui.h" -#include "wav_io.h" -#include "transport.h" -#include "carla.h" #include #include -#include #include -#include -#include +#include +#include +#include #include #include #include -#include -#include #include -#include +/* ---------- FIFO command helper ---------- */ +static int send_command(const char *cmd) { + int fd = open("/tmp/looper_cmd", O_WRONLY); + 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 -// Color pairs +/* Color pairs */ enum { - COLOR_EMPTY = 1, - COLOR_RECORDING, - COLOR_LOOPING, - COLOR_STOPPED, - COLOR_SELECTED, - COLOR_HELP + COLOR_EMPTY=1, COLOR_RECORDING, COLOR_LOOPING, COLOR_STOPPED, + COLOR_SELECTED, COLOR_HELP }; -static Engine *g_engine = NULL; -static DispatchFn g_dispatch = NULL; -static int selected_row = 0; -static int selected_col = 0; +static int selected_row = 0, selected_col = 0; +static int selected_grid = 0; static bool show_help = false; -// Grid of grids state -static int selected_grid = 0; // Which grid we're viewing (0-7) -static bool zoom_mode = false; // Whether we're in zoom mode +/* 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; -// View modes -typedef enum { - VIEW_GRID, - VIEW_RACK -} UIView; - -static UIView current_view = VIEW_GRID; -static int rack_selected_channel = 0; -static int rack_selected_plugin = -1; -static int rack_selected_param = -1; - -// Fuzzy search state +/* Fuzzy search – keep struct but stub carla calls */ typedef struct { - char query[256]; - int query_len; - int selected_index; - int num_results; - int result_indices[256]; - bool active; - char prompt[64]; - void (*callback)(const char *selection); - const char **items; // custom list of strings (NULL if using plugins) - int num_items; // number of items in custom list - bool free_items; // whether to free items when done + 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}; -// Modes -typedef enum { - MODE_NORMAL, - MODE_VISUAL, - MODE_MOVE -} UIMode; +/* ---------- State to color (dummy: all white) ---------- */ +static int state_to_color(ClipState s) { (void)s; return COLOR_EMPTY; } -// Mark storage -#define MAX_MARKS 26 // a-z -static int marks[MAX_MARKS]; // stores clip_index for each mark - -// Visual mode state -static int visual_start_row = 0; -static int visual_start_col = 0; -static int visual_end_row = 0; -static int visual_end_col = 0; - -// Yank buffer -typedef struct { - int *clip_indices; - int count; -} YankBuffer; -static YankBuffer yank_buffer = {NULL, 0}; - -// Current mode -static UIMode current_mode = MODE_NORMAL; - -// Convert clip state to color pair -static int state_to_color(ClipState state) { - switch (state) { - case CLIP_EMPTY: return COLOR_EMPTY; - case CLIP_RECORDING: return COLOR_RECORDING; - case CLIP_LOOPING: return COLOR_LOOPING; - case CLIP_STOPPED: return COLOR_STOPPED; - default: return COLOR_EMPTY; - } -} - -// Get clip index from grid position -static int grid_to_clip_index(int grid, int row, int col) { - return grid * GRID_ROWS * GRID_COLS + row * GRID_COLS + col; -} - -// Check if a cell is in the visual selection -static bool is_in_visual_selection(int row, int col) { - if (current_mode != MODE_VISUAL) return false; - - int min_row = (visual_start_row < visual_end_row) ? visual_start_row : visual_end_row; - int max_row = (visual_start_row > visual_end_row) ? visual_start_row : visual_end_row; - int min_col = (visual_start_col < visual_end_col) ? visual_start_col : visual_end_col; - int max_col = (visual_start_col > visual_end_col) ? visual_start_col : visual_end_col; - - return (row >= min_row && row <= max_row && col >= min_col && col <= max_col); -} - -// Get all clip indices in the visual selection -static int* get_selected_clips(int *count) { - int min_row = (visual_start_row < visual_end_row) ? visual_start_row : visual_end_row; - int max_row = (visual_start_row > visual_end_row) ? visual_start_row : visual_end_row; - int min_col = (visual_start_col < visual_end_col) ? visual_start_col : visual_end_col; - int max_col = (visual_start_col > visual_end_col) ? visual_start_col : visual_end_col; - - int num_rows = max_row - min_row + 1; - int num_cols = max_col - min_col + 1; - *count = num_rows * num_cols; - - int *clips = (int *)malloc(*count * sizeof(int)); - if (!clips) { - *count = 0; - return NULL; - } - - int idx = 0; - for (int r = min_row; r <= max_row; r++) { - for (int c = min_col; c <= max_col; c++) { - clips[idx++] = grid_to_clip_index(selected_grid, r, c); - } - } - - return clips; -} - -// Delete (reset) clips via dispatch -static void delete_clips(int *clip_indices, int count) { - for (int i = 0; i < count; i++) { - Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip = { .clip_index = clip_indices[i] } }; - g_dispatch(action); - } -} - -// Yank clips (store their indices) -static void yank_clips(int *clip_indices, int count) { - if (yank_buffer.clip_indices) { - free(yank_buffer.clip_indices); - } - - yank_buffer.clip_indices = (int *)malloc(count * sizeof(int)); - if (yank_buffer.clip_indices) { - memcpy(yank_buffer.clip_indices, clip_indices, count * sizeof(int)); - yank_buffer.count = count; - } -} - -// Paste clips (trigger them via dispatch) -static void paste_clips(void) { - if (!yank_buffer.clip_indices || yank_buffer.count == 0) return; - - int first_yanked_grid = yank_buffer.clip_indices[0] / (GRID_ROWS * GRID_COLS); - int first_yanked_row = (yank_buffer.clip_indices[0] % (GRID_ROWS * GRID_COLS)) / GRID_COLS; - int first_yanked_col = (yank_buffer.clip_indices[0] % (GRID_ROWS * GRID_COLS)) % GRID_COLS; - int grid_offset = selected_grid - first_yanked_grid; - int row_offset = selected_row - first_yanked_row; - int col_offset = selected_col - first_yanked_col; - - for (int i = 0; i < yank_buffer.count; i++) { - int orig_grid = yank_buffer.clip_indices[i] / (GRID_ROWS * GRID_COLS); - int remainder = yank_buffer.clip_indices[i] % (GRID_ROWS * GRID_COLS); - int orig_row = remainder / GRID_COLS; - int orig_col = remainder % GRID_COLS; - - int new_grid = orig_grid + grid_offset; - int new_row = orig_row + row_offset; - int new_col = orig_col + col_offset; - - if (new_grid >= 0 && new_grid < NUM_GRIDS && - new_row >= 0 && new_row < GRID_ROWS && - new_col >= 0 && new_col < GRID_COLS) { - int new_clip_idx = grid_to_clip_index(new_grid, new_row, new_col); - // Trigger three times to cycle: empty -> recording -> looping -> stopped - for (int j = 0; j < 3; j++) { - Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip = { .clip_index = new_clip_idx } }; - g_dispatch(action); - } - } - } -} - -// Set a mark at current position -static void set_mark(char mark_char) { - if (mark_char >= 'a' && mark_char <= 'z') { - int idx = mark_char - 'a'; - marks[idx] = grid_to_clip_index(selected_grid, selected_row, selected_col); - } -} - -// Go to a mark -static void go_to_mark(char mark_char) { - if (mark_char >= 'a' && mark_char <= 'z') { - int idx = mark_char - 'a'; - int clip_idx = marks[idx]; - if (clip_idx >= 0 && clip_idx < NUM_GRIDS * GRID_ROWS * GRID_COLS) { - selected_grid = clip_idx / (GRID_ROWS * GRID_COLS); - int remainder = clip_idx % (GRID_ROWS * GRID_COLS); - selected_row = remainder / GRID_COLS; - selected_col = remainder % GRID_COLS; - } - } -} - -// Play next scene (next row) -static void play_next_scene(void) { - int next_row = (selected_row + 1) % GRID_ROWS; - Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene = { .scene_index = next_row + selected_grid * GRID_ROWS } }; - g_dispatch(action); - selected_row = next_row; -} - -// Play previous scene (previous row) -static void play_prev_scene(void) { - int prev_row = (selected_row - 1 + GRID_ROWS) % GRID_ROWS; - Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene = { .scene_index = prev_row + selected_grid * GRID_ROWS } }; - g_dispatch(action); - selected_row = prev_row; -} - -// Callback for adding a plugin via fuzzy search -static void rack_add_plugin_callback(const char *selection) { - if (selection) { - // Parse URI from selection (format: "uri - name") - char uri[512]; - const char *space = strchr(selection, ' '); - if (space) { - size_t len = space - selection; - strncpy(uri, selection, len); - uri[len] = '\0'; - } else { - strncpy(uri, selection, sizeof(uri) - 1); - } - - Action action = { - .type = ACTION_RACK_ADD_PLUGIN, - .data.rack_add_plugin = { - .channel = rack_selected_channel, - .type = PLUGIN_TYPE_INTERNAL - } - }; - strncpy(action.data.rack_add_plugin.uri, uri, sizeof(action.data.rack_add_plugin.uri) - 1); - action.data.rack_add_plugin.uri[sizeof(action.data.rack_add_plugin.uri) - 1] = '\0'; - g_dispatch(action); - } -} - -// Callback for MIDI from selection -static void midi_from_callback(const char *selection) { - if (selection) { - printf("Selected MIDI input: %s\n", selection); - // In a real implementation, this would connect JACK ports - } -} - -// Callback for MIDI to selection -static void midi_to_callback(const char *selection) { - if (selection) { - printf("Selected MIDI output: %s\n", selection); - // In a real implementation, this would connect JACK ports - } -} - -// List WAV files in a directory (returns malloc'd array of strings, count via *count) -static const char **list_wav_files(const char *dir, int *count) { - DIR *d = opendir(dir); - if (!d) { - *count = 0; - return NULL; - } - - int capacity = 64; - const char **files = malloc(capacity * sizeof(char*)); - int n = 0; - - struct dirent *entry; - while ((entry = readdir(d)) != NULL) { - const char *name = entry->d_name; - size_t len = strlen(name); - if (len > 4 && strcasecmp(name + len - 4, ".wav") == 0) { - // Check if it's a regular file - char fullpath[1024]; - snprintf(fullpath, sizeof(fullpath), "%s/%s", dir, name); - struct stat st; - if (stat(fullpath, &st) == 0 && S_ISREG(st.st_mode)) { - if (n >= capacity) { - capacity *= 2; - files = realloc(files, capacity * sizeof(char*)); - } - files[n] = strdup(name); - n++; - } - } - } - closedir(d); - - *count = n; - return files; -} - -// Callback for loading a sample via fuzzy search -static void load_sample_callback(const char *selection) { - if (selection) { - // Build full path (assume samples directory) - char fullpath[1024]; - const char *samples_dir = getenv("JACK_LOOPER_SAMPLES_DIR"); - if (!samples_dir) samples_dir = "."; - snprintf(fullpath, sizeof(fullpath), "%s/%s", samples_dir, selection); - - int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col); - Action action = { .type = ACTION_LOAD_CLIP, .data.load_clip = { .clip_index = clip_idx } }; - strncpy(action.data.load_clip.filename, fullpath, 255); - action.data.load_clip.filename[255] = '\0'; - g_dispatch(action); - } -} - -// Fuzzy matching function -static bool fuzzy_match(const char *pattern, const char *text) { - if (!pattern || !*pattern) return true; - if (!text) return false; - - while (*pattern) { - char p = *pattern; - while (*text && tolower((unsigned char)*text) != tolower((unsigned char)p)) text++; - if (!*text) return false; - text++; - pattern++; - } - return true; -} - -// Draw fuzzy search -static void draw_fuzzy_search(void) { - int start_y = LINES / 3; - int start_x = COLS / 4; - int width = COLS / 2; - int height = LINES / 3; - - // Create a temporary window for the border - WINDOW *win = newwin(height + 2, width + 2, start_y - 1, start_x - 1); - if (!win) return; - wclear(win); - box(win, 0, 0); - wrefresh(win); - delwin(win); - - // Draw title (inside the border) - attron(A_BOLD); - mvprintw(start_y, start_x, "%s", fuzzy_search.prompt); - attroff(A_BOLD); - - // Draw search box - mvprintw(start_y + 1, start_x, "> %s", fuzzy_search.query); - - // Draw results - for (int i = 0; i < height - 2 && i < fuzzy_search.num_results; i++) { - int idx = fuzzy_search.result_indices[i]; - if (i == fuzzy_search.selected_index) { - attron(A_REVERSE); - } - const char *item = NULL; - if (fuzzy_search.items) { - if (idx >= 0 && idx < fuzzy_search.num_items) { - item = fuzzy_search.items[idx]; - } - } else { - int count; - const char **plugins = carla_get_available_plugins(&g_engine->carla_host, &count); - if (plugins && idx >= 0 && idx < count) { - item = plugins[idx]; - } - } - if (item) { - mvprintw(start_y + 2 + i, start_x, "%s", item); - } - if (i == fuzzy_search.selected_index) { - attroff(A_REVERSE); - } - } - - // Draw help line - mvprintw(start_y + height + 2, start_x, "j/k: navigate, h/l: page, Enter: select, Esc: cancel"); -} - -// Handle fuzzy search input -static bool handle_fuzzy_search(int ch) { - if (!fuzzy_search.active) return false; - - switch (ch) { - case '\n': case '\r': { - if (fuzzy_search.num_results > 0 && fuzzy_search.selected_index >= 0) { - int idx = fuzzy_search.result_indices[fuzzy_search.selected_index]; - const char *selection = NULL; - if (fuzzy_search.items) { - if (idx >= 0 && idx < fuzzy_search.num_items) { - selection = fuzzy_search.items[idx]; - } - } else { - int count; - const char **plugins = carla_get_available_plugins(NULL, &count); - if (plugins && idx >= 0 && idx < count) { - selection = plugins[idx]; - } - } - if (selection && fuzzy_search.callback) { - fuzzy_search.callback(selection); - } - } - // Free custom items if needed - if (fuzzy_search.free_items && fuzzy_search.items) { - for (int i = 0; i < fuzzy_search.num_items; i++) { - free((void*)fuzzy_search.items[i]); - } - free(fuzzy_search.items); - fuzzy_search.items = NULL; - fuzzy_search.num_items = 0; - fuzzy_search.free_items = false; - } - fuzzy_search.active = false; - return true; - } - case 27: { // ESC - // Free custom items if needed - if (fuzzy_search.free_items && fuzzy_search.items) { - for (int i = 0; i < fuzzy_search.num_items; i++) { - free((void*)fuzzy_search.items[i]); - } - free(fuzzy_search.items); - fuzzy_search.items = NULL; - fuzzy_search.num_items = 0; - fuzzy_search.free_items = false; - } - fuzzy_search.active = false; - return true; - } - case KEY_BACKSPACE: case 127: { - if (fuzzy_search.query_len > 0) { - fuzzy_search.query[--fuzzy_search.query_len] = '\0'; - // Update results - fuzzy_search.num_results = 0; - if (fuzzy_search.items) { - for (int i = 0; i < fuzzy_search.num_items; i++) { - if (fuzzy_match(fuzzy_search.query, fuzzy_search.items[i])) { - fuzzy_search.result_indices[fuzzy_search.num_results++] = i; - } - } - } else { - int count; - const char **plugins = carla_get_available_plugins(&g_engine->carla_host, &count); - for (int i = 0; i < count; i++) { - if (fuzzy_match(fuzzy_search.query, plugins[i])) { - fuzzy_search.result_indices[fuzzy_search.num_results++] = i; - } - } - } - if (fuzzy_search.selected_index >= fuzzy_search.num_results) { - fuzzy_search.selected_index = fuzzy_search.num_results - 1; - } - } - return true; - } - case 'k': case KEY_UP: { - if (fuzzy_search.selected_index > 0) { - fuzzy_search.selected_index--; - } - return true; - } - case 'j': case KEY_DOWN: { - if (fuzzy_search.selected_index < fuzzy_search.num_results - 1) { - fuzzy_search.selected_index++; - } - return true; - } - case 'h': { - // Page up - move up by 10 items - fuzzy_search.selected_index -= 10; - if (fuzzy_search.selected_index < 0) { - fuzzy_search.selected_index = 0; - } - return true; - } - case 'l': { - // Page down - move down by 10 items - fuzzy_search.selected_index += 10; - if (fuzzy_search.selected_index >= fuzzy_search.num_results) { - fuzzy_search.selected_index = fuzzy_search.num_results - 1; - } - return true; - } - default: { - if (ch >= 32 && ch <= 126 && fuzzy_search.query_len < 255) { - fuzzy_search.query[fuzzy_search.query_len++] = (char)ch; - fuzzy_search.query[fuzzy_search.query_len] = '\0'; - // Update results - fuzzy_search.num_results = 0; - if (fuzzy_search.items) { - for (int i = 0; i < fuzzy_search.num_items; i++) { - if (fuzzy_match(fuzzy_search.query, fuzzy_search.items[i])) { - fuzzy_search.result_indices[fuzzy_search.num_results++] = i; - } - } - } else { - int count; - const char **plugins = carla_get_available_plugins(&g_engine->carla_host, &count); - for (int i = 0; i < count; i++) { - if (fuzzy_match(fuzzy_search.query, plugins[i])) { - fuzzy_search.result_indices[fuzzy_search.num_results++] = i; - } - } - } - fuzzy_search.selected_index = 0; - } - return true; - } - } -} - -// Start fuzzy search with custom items list (or NULL for plugins) -static void start_fuzzy_search_items(const char *prompt, void (*callback)(const char *), - const char **items, int num_items, bool free_items) { - fuzzy_search.active = true; - fuzzy_search.query_len = 0; - fuzzy_search.query[0] = '\0'; - fuzzy_search.selected_index = 0; - strncpy(fuzzy_search.prompt, prompt, sizeof(fuzzy_search.prompt) - 1); - fuzzy_search.prompt[sizeof(fuzzy_search.prompt) - 1] = '\0'; - fuzzy_search.callback = callback; - fuzzy_search.items = items; - fuzzy_search.num_items = num_items; - fuzzy_search.free_items = free_items; - - // Initialize results with all items - fuzzy_search.num_results = 0; - if (items) { - for (int i = 0; i < num_items; i++) { - fuzzy_search.result_indices[fuzzy_search.num_results++] = i; - } - } else { - int count = 0; - carla_get_available_plugins(&g_engine->carla_host, &count); - for (int i = 0; i < count; i++) { - fuzzy_search.result_indices[fuzzy_search.num_results++] = i; - } - } -} - -// Start fuzzy search (using plugins) -static void start_fuzzy_search(const char *prompt, void (*callback)(const char *)) { - start_fuzzy_search_items(prompt, callback, NULL, 0, false); -} - -// Draw rack view -static void draw_rack_view(void) { - clear(); - - attron(A_BOLD); - mvprintw(0, 0, "JACK Looper - Rack View (Channel %d)", rack_selected_channel); - attroff(A_BOLD); - - AppState state; - dispatcher_get_state(&state); - ChannelRack *rack = &state.carla_host.channel_racks[rack_selected_channel]; - - // Draw channel selector - mvprintw(1, 0, "Channel: "); - for (int ch = 0; ch < 8; ch++) { - if (ch == rack_selected_channel) { - attron(A_REVERSE); - } - mvprintw(1, 10 + ch * 4, "Ch%d", ch); - if (ch == rack_selected_channel) { - attroff(A_REVERSE); - } - } - - // Draw volume - mvprintw(2, 0, "Volume: %.2f [ - ] to decrease, [ = ] to increase", rack->volume); - - // Draw bypass toggle - mvprintw(3, 0, "Bypass: %s [b] to toggle", rack->bypassed ? "ON" : "OFF"); - - // Draw plugin chain - mvprintw(4, 0, "=== Plugin Chain ==="); - - int y = 5; - for (int p = 0; p < rack->num_plugins; p++) { - PluginInfo *plugin = &rack->plugins[p]; - - if (p == rack_selected_plugin) { - attron(A_REVERSE); - } - mvprintw(y++, 2, "[%d] %s", p, plugin->name); - if (p == rack_selected_plugin) { - attroff(A_REVERSE); - } - - // Draw parameters if this plugin is selected - if (p == rack_selected_plugin) { - for (int param = 0; param < plugin->num_parameters; param++) { - if (param == rack_selected_param) { - attron(A_REVERSE); - } - mvprintw(y++, 4, "%s: %.3f", - plugin->parameter_names[param], - plugin->parameters[param]); - if (param == rack_selected_param) { - attroff(A_REVERSE); - } - } - } - } - - // Draw controls help - mvprintw(y + 1, 0, "=== Controls ==="); - mvprintw(y + 2, 0, "h/l - Previous/Next channel (or page in fuzzy search)"); - mvprintw(y + 3, 0, "j/k - Navigate plugins (or items in fuzzy search)"); - mvprintw(y + 4, 0, "a - Add plugin to rack"); - mvprintw(y + 5, 0, "d - Remove selected plugin"); - mvprintw(y + 6, 0, "b - Toggle bypass"); - mvprintw(y + 7, 0, "-/= - Decrease/Increase volume"); - mvprintw(y + 8, 0, "Enter - Select parameter to edit"); - mvprintw(y + 9, 0, "Tab - Switch to grid view"); - mvprintw(y + 10, 0, "q/Esc - Quit"); - - refresh(); -} - -// Handle rack view input -static bool handle_rack_view(int ch) { - AppState state; - dispatcher_get_state(&state); - ChannelRack *rack = &state.carla_host.channel_racks[rack_selected_channel]; - - switch (ch) { - case 'h': case KEY_LEFT: - rack_selected_channel = (rack_selected_channel - 1 + 8) % 8; - rack_selected_plugin = -1; - rack_selected_param = -1; - break; - case 'l': case KEY_RIGHT: - rack_selected_channel = (rack_selected_channel + 1) % 8; - rack_selected_plugin = -1; - rack_selected_param = -1; - break; - case 'j': case KEY_DOWN: - if (rack_selected_plugin < rack->num_plugins - 1) { - rack_selected_plugin++; - rack_selected_param = -1; - } - break; - case 'k': case KEY_UP: - if (rack_selected_plugin > 0) { - rack_selected_plugin--; - rack_selected_param = -1; - } else if (rack_selected_plugin == 0) { - rack_selected_plugin = -1; - } - break; - case 'a': { - // Start fuzzy search for plugin selection - start_fuzzy_search("Add plugin:", rack_add_plugin_callback); - break; - } - case 'd': { - if (rack_selected_plugin >= 0 && rack_selected_plugin < rack->num_plugins) { - Action action = { - .type = ACTION_RACK_REMOVE_PLUGIN, - .data.rack_remove_plugin = { - .channel = rack_selected_channel, - .plugin_index = rack_selected_plugin - } - }; - g_dispatch(action); - rack_selected_plugin = -1; - rack_selected_param = -1; - } - break; - } - case 'b': { - Action action = { - .type = ACTION_RACK_BYPASS, - .data.rack_bypass = { - .channel = rack_selected_channel, - .bypass = !rack->bypassed - } - }; - g_dispatch(action); - break; - } - case '-': case '_': { - float new_vol = fmaxf(0.0f, rack->volume - 0.1f); - Action action = { - .type = ACTION_RACK_SET_VOLUME, - .data.rack_set_volume = { - .channel = rack_selected_channel, - .volume = new_vol - } - }; - g_dispatch(action); - break; - } - case '=': case '+': { - float new_vol = fminf(2.0f, rack->volume + 0.1f); - Action action = { - .type = ACTION_RACK_SET_VOLUME, - .data.rack_set_volume = { - .channel = rack_selected_channel, - .volume = new_vol - } - }; - g_dispatch(action); - break; - } - case '\t': { - current_view = VIEW_GRID; - break; - } - case 'q': case 27: - return true; // Quit - } - - return false; -} - -// Draw a single cell +/* ---------- Draw cell (no AppState) ---------- */ static void draw_cell(int grid, int row, int col, bool selected) { - int clip_idx = grid_to_clip_index(grid, row, col); - AppState state; - dispatcher_get_state(&state); - - int y = row * CELL_HEIGHT + 3; // Offset by 2 for grid selector + int y = row * CELL_HEIGHT + 3; int x = col * CELL_WIDTH + 1; - - int color; - if (state.show_midi_grid) { - color = state_to_color(state.midi_clips[clip_idx].state); - } else { - color = state_to_color(state.clips[clip_idx].state); - } - if (selected) { - color = COLOR_SELECTED; - } else if (current_mode == MODE_VISUAL && is_in_visual_selection(row, col)) { - color = COLOR_SELECTED; - } - + int color = selected ? COLOR_SELECTED : COLOR_EMPTY; attron(COLOR_PAIR(color)); - - for (int dy = 0; dy < CELL_HEIGHT; dy++) { - for (int dx = 0; dx < CELL_WIDTH; dx++) { - mvaddch(y + dy, x + dx, ' '); - } - } - - if (state.show_midi_grid) { - MidiClip *mclip = &state.midi_clips[clip_idx]; - mvprintw(y + 1, x + 1, "%2d", clip_idx); - char state_char; - switch (mclip->state) { - case CLIP_EMPTY: state_char = ' '; break; - case CLIP_RECORDING: state_char = 'R'; break; - case CLIP_LOOPING: state_char = 'L'; break; - case CLIP_STOPPED: state_char = 'S'; break; - default: state_char = '?'; break; - } - mvaddch(y + 1, x + 4, state_char); - } else { - Clip *clip = &state.clips[clip_idx]; - mvprintw(y + 1, x + 1, "%2d", clip_idx); - char state_char; - switch (clip->state) { - case CLIP_EMPTY: state_char = ' '; break; - case CLIP_RECORDING: state_char = 'R'; break; - case CLIP_LOOPING: state_char = 'L'; break; - case CLIP_STOPPED: state_char = 'S'; break; - default: state_char = '?'; break; - } - mvaddch(y + 1, x + 4, state_char); - } - + for (int dy=0; dystate); - attron(COLOR_PAIR(color)); - mvaddch(gy + 1 + r, gx + 1 + c, ' '); - attroff(COLOR_PAIR(color)); - } - } - } - - refresh(); - return; - } - - // Normal grid view attron(A_BOLD); - if (state.show_midi_grid) { - mvprintw(0, 0, "JACK Looper - MIDI Grid %d", selected_grid); - } else { - mvprintw(0, 0, "JACK Looper - Audio Grid %d", selected_grid); - } + mvprintw(0,0,"JACK Looper - Client (FIFO only)"); attroff(A_BOLD); - - // Draw grid selector bar at top - mvprintw(1, 0, "Grid: "); - for (int g = 0; g < NUM_GRIDS; g++) { - if (g == selected_grid) { - attron(A_REVERSE); - } - mvprintw(1, 6 + g * 4, "G%d", g); - if (g == selected_grid) { - attroff(A_REVERSE); - } - } - - for (int row = 0; row < GRID_ROWS; row++) { - for (int col = 0; col < GRID_COLS; col++) { - bool selected = (row == selected_row && col == selected_col); - draw_cell(selected_grid, row, col, selected); - } - } - - int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col); - - if (state.show_midi_grid) { - MidiClip *mclip = &state.midi_clips[clip_idx]; - mvprintw(GRID_ROWS * CELL_HEIGHT + 3, 0, - "Selected: Clip %d | Grid %d | State: %s | MIDI events: %d", - clip_idx, selected_grid, clip_state_to_string(mclip->state), mclip->event_count); - } else { - Clip *clip = &state.clips[clip_idx]; - mvprintw(GRID_ROWS * CELL_HEIGHT + 3, 0, - "Selected: Clip %d | Grid %d | State: %s | Buffer: %zu samples", - clip_idx, selected_grid, clip_state_to_string(clip->state), clip->buffer_size); - } - - mvprintw(GRID_ROWS * CELL_HEIGHT + 4, 0, - "Transport: %s | Clock: %s | BPM: %.1f | Quantize: %s | Grid: %s", - transport_state_to_string(state.transport_state), - clock_source_to_string(state.clock_source), - state.bpm, - quantize_mode_to_string(state.quantize_mode), - state.show_midi_grid ? "MIDI" : "AUDIO"); - - const char *mode_str; - switch (current_mode) { - case MODE_NORMAL: mode_str = "NORMAL"; break; - case MODE_VISUAL: mode_str = "VISUAL"; break; - case MODE_MOVE: mode_str = "MOVE"; break; - default: mode_str = "?"; break; - } - mvprintw(GRID_ROWS * CELL_HEIGHT + 5, 0, "Mode: %s", mode_str); - + for (int r=0; r - add plugin to selected channel's rack - const char *plugin_name = cmd_buffer + 5; - Action action = { - .type = ACTION_RACK_ADD_PLUGIN, - .data.rack_add_plugin = { - .channel = selected_row, - .type = PLUGIN_TYPE_INTERNAL - } - }; - strncpy(action.data.rack_add_plugin.uri, plugin_name, sizeof(action.data.rack_add_plugin.uri) - 1); - action.data.rack_add_plugin.uri[sizeof(action.data.rack_add_plugin.uri) - 1] = '\0'; - g_dispatch(action); - } else if (strcmp(cmd_buffer, "from") == 0) { - // :from - open fuzzy search for MIDI input source - start_fuzzy_search("Select MIDI input source:", midi_from_callback); - } else if (strcmp(cmd_buffer, "to") == 0) { - // :to - open fuzzy search for MIDI output destination - start_fuzzy_search("Select MIDI output destination:", midi_to_callback); - } else if (strcmp(cmd_buffer, "load") == 0) { - // Start fuzzy search for WAV files - const char *samples_dir = getenv("JACK_LOOPER_SAMPLES_DIR"); - if (!samples_dir) samples_dir = "."; - int count; - const char **files = list_wav_files(samples_dir, &count); - if (files && count > 0) { - start_fuzzy_search_items("Load sample:", load_sample_callback, - files, count, true); - } else { - // No files found, show message - mvprintw(LINES - 1, 0, "No WAV files found in %s", samples_dir); - refresh(); - napms(1500); - } - } else if (strncmp(cmd_buffer, "load ", 5) == 0) { - char *rest = cmd_buffer + 5; - int clip_idx = atoi(rest); - char *filename = rest; - while (*filename && *filename != ' ') filename++; - if (*filename) { - *filename = '\0'; - filename++; - while (*filename == ' ') filename++; - if (*filename) { - 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'; - g_dispatch(action); - } - } - } else if (strncmp(cmd_buffer, "save ", 5) == 0) { - const char *filename = cmd_buffer + 5; - while (*filename == ' ') filename++; - if (*filename) { - Action action = { .type = ACTION_SAVE_PROJECT }; - strncpy(action.data.save_project.filename, filename, sizeof(action.data.save_project.filename) - 1); - action.data.save_project.filename[sizeof(action.data.save_project.filename) - 1] = '\0'; - g_dispatch(action); - } - } else if (strncmp(cmd_buffer, "load ", 5) == 0) { - const char *filename = cmd_buffer + 5; - while (*filename == ' ') filename++; - if (*filename) { - Action action = { .type = ACTION_LOAD_PROJECT }; - strncpy(action.data.load_project.filename, filename, sizeof(action.data.load_project.filename) - 1); - action.data.load_project.filename[sizeof(action.data.load_project.filename) - 1] = '\0'; - g_dispatch(action); - } - } - - nodelay(stdscr, prev_nodelay); - return false; - } else if (ch == 27) { - mvprintw(LINES - 1, 0, " "); - refresh(); - nodelay(stdscr, prev_nodelay); - return false; - } else if (ch == KEY_BACKSPACE || ch == 127) { - if (cmd_pos > 0) { - cmd_pos--; - cmd_buffer[cmd_pos] = '\0'; - mvprintw(LINES - 1, 0, ":%s ", cmd_buffer); - refresh(); - } - } else if (cmd_pos < (int)sizeof(cmd_buffer) - 1) { - cmd_buffer[cmd_pos++] = (char)ch; - cmd_buffer[cmd_pos] = '\0'; - mvprintw(LINES - 1, 0, ":%s", cmd_buffer); - refresh(); - } - } - - nodelay(stdscr, prev_nodelay); - return false; -} - -// Handle mouse events -static void handle_mouse_event(MEVENT *event) { - int grid_row = (event->y - 3) / CELL_HEIGHT; // Offset for grid selector - int grid_col = (event->x - 1) / CELL_WIDTH; - - if (grid_row < 0 || grid_row >= GRID_ROWS || grid_col < 0 || grid_col >= GRID_COLS) { - return; - } - - if (event->bstate & BUTTON1_CLICKED) { - selected_row = grid_row; - selected_col = grid_col; - int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col); - AppState s; - dispatcher_get_state(&s); - if (s.show_midi_grid) { - Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = clip_idx } }; - g_dispatch(action); - } else { - Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip = { .clip_index = clip_idx } }; - g_dispatch(action); - } - } else if (event->bstate & BUTTON3_CLICKED) { - selected_row = grid_row; - selected_col = grid_col; - int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col); - AppState s; - dispatcher_get_state(&s); - if (s.show_midi_grid) { - Action action = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = clip_idx } }; - g_dispatch(action); - } else { - Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip = { .clip_index = clip_idx } }; - g_dispatch(action); - } - } else if (event->bstate & BUTTON2_CLICKED) { - selected_row = grid_row; - selected_col = grid_col; - Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene = { .scene_index = selected_row + selected_grid * GRID_ROWS } }; - g_dispatch(action); - } else if (event->bstate & BUTTON1_DOUBLE_CLICKED) { - selected_row = grid_row; - selected_col = grid_col; - Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene = { .scene_index = selected_row + selected_grid * GRID_ROWS } }; - g_dispatch(action); - } -} - -void tui_init(Engine *engine) { - g_engine = engine; - g_dispatch = engine->dispatch; - +/* ---------- TUI init ---------- */ +void tui_init(void) { initscr(); - if (!has_colors()) { - endwin(); - fprintf(stderr, "Terminal does not support colors\n"); - exit(1); - } - - cbreak(); - noecho(); - keypad(stdscr, TRUE); - curs_set(0); - - // Check terminal size - if (LINES < 20 || COLS < 40) { - endwin(); - fprintf(stderr, "Terminal too small (need at least 20x40)\n"); - exit(1); - } - - mousemask(BUTTON1_CLICKED | BUTTON3_CLICKED | BUTTON2_CLICKED | BUTTON1_DOUBLE_CLICKED, NULL); - mouseinterval(10); - + cbreak(); noecho(); keypad(stdscr, TRUE); curs_set(0); + if (!has_colors()) { endwin(); fprintf(stderr,"No colors\n"); exit(1); } start_color(); init_pair(COLOR_EMPTY, COLOR_WHITE, COLOR_BLACK); init_pair(COLOR_RECORDING, COLOR_RED, COLOR_BLACK); @@ -1139,411 +107,41 @@ void tui_init(Engine *engine) { init_pair(COLOR_STOPPED, COLOR_BLUE, COLOR_BLACK); init_pair(COLOR_SELECTED, COLOR_BLACK, COLOR_CYAN); init_pair(COLOR_HELP, COLOR_CYAN, COLOR_BLACK); + for (int i=0;i<26;i++) marks[i] = -1; } -void tui_run(Engine *engine) { - if (!engine) return; - - g_engine = engine; - g_dispatch = engine->dispatch; - - for (int i = 0; i < MAX_MARKS; i++) { - marks[i] = -1; - } - - // Carla is now initialized in main.c before dispatcher starts +/* ---------- TUI run ---------- */ +void tui_run(void) { draw_grid(); - while (1) { - // Handle fuzzy search first - if (fuzzy_search.active) { - draw_fuzzy_search(); - int ch = getch(); - if (handle_fuzzy_search(ch)) { - // Fuzzy search closed, redraw current view - if (current_view == VIEW_GRID) { - draw_grid(); - } else { - draw_rack_view(); - } - } else { - draw_fuzzy_search(); - } - continue; - } - - // Handle current view - if (current_view == VIEW_RACK) { - draw_rack_view(); - int ch = getch(); - if (handle_rack_view(ch)) { - return; // Quit - } - continue; - } - - // Grid view (existing code) int ch = getch(); - if (ch == ERR) { + switch (ch) { + 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; } - - if (ch == KEY_MOUSE) { - MEVENT event; - if (getmouse(&event) == OK) { - handle_mouse_event(&event); - draw_grid(); - continue; - } + case 's': { + send_command("scene_next\n"); + break; } - - // Handle zoom mode navigation - if (zoom_mode) { - switch (ch) { - case 'h': case KEY_LEFT: - selected_grid = (selected_grid - 1 + NUM_GRIDS) % NUM_GRIDS; - break; - case 'l': case KEY_RIGHT: - selected_grid = (selected_grid + 1) % NUM_GRIDS; - break; - case 'j': case KEY_DOWN: - selected_grid = (selected_grid + 4) % NUM_GRIDS; // Move down a row (4 grids per row) - break; - case 'k': case KEY_UP: - selected_grid = (selected_grid - 4 + NUM_GRIDS) % NUM_GRIDS; // Move up a row - break; - case '\n': case '\r': - // Select this grid and exit zoom mode - zoom_mode = false; - break; - case 'z': - zoom_mode = false; - break; - case 27: case 'Q': - return; - } - draw_grid(); - continue; + case 'd': case 'S': + send_command("stop\n"); + break; + case '?': show_help = !show_help; break; + case 27: case 'Q': return; + default: break; } - - if (current_mode == MODE_MOVE) { - switch (ch) { - 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 '\n': case '\r': case 27: - current_mode = MODE_NORMAL; - break; - } - draw_grid(); - continue; - } - - if (current_mode == MODE_VISUAL) { - switch (ch) { - case 'h': case KEY_LEFT: - visual_end_col = (visual_end_col - 1 + GRID_COLS) % GRID_COLS; - break; - case 'j': case KEY_DOWN: - visual_end_row = (visual_end_row + 1) % GRID_ROWS; - break; - case 'k': case KEY_UP: - visual_end_row = (visual_end_row - 1 + GRID_ROWS) % GRID_ROWS; - break; - case 'l': case KEY_RIGHT: - visual_end_col = (visual_end_col + 1) % GRID_COLS; - break; - case 'd': { - int count; - int *clips = get_selected_clips(&count); - if (clips) { - AppState s; - dispatcher_get_state(&s); - if (s.show_midi_grid) { - for (int i = 0; i < count; i++) { - Action action = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = clips[i] } }; - g_dispatch(action); - } - } else { - delete_clips(clips, count); - } - free(clips); - } - current_mode = MODE_NORMAL; - break; - } - case 'y': { - int count; - int *clips = get_selected_clips(&count); - if (clips) { - yank_clips(clips, count); - free(clips); - } - current_mode = MODE_NORMAL; - break; - } - case 27: - current_mode = MODE_NORMAL; - break; - } - draw_grid(); - continue; - } - - // Normal mode - switch (ch) { - 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': { - int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col); - AppState s; - dispatcher_get_state(&s); - if (s.show_midi_grid) { - Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = clip_idx } }; - g_dispatch(action); - } else { - Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip = { .clip_index = clip_idx } }; - g_dispatch(action); - } - break; - } - case 'd': { - int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col); - AppState s; - dispatcher_get_state(&s); - if (s.show_midi_grid) { - Action action = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = clip_idx } }; - g_dispatch(action); - } else { - Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip = { .clip_index = clip_idx } }; - g_dispatch(action); - } - break; - } - case 'y': { - int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col); - yank_clips(&clip_idx, 1); - break; - } - case 'p': - paste_clips(); - break; - case 's': { - Action action = { .type = ACTION_TRIGGER_SCENE, .data.trigger_scene = { .scene_index = selected_row + selected_grid * GRID_ROWS } }; - g_dispatch(action); - break; - } - case 'q': { - AppState state; - dispatcher_get_state(&state); - QuantizeMode modes[] = {QUANTIZE_OFF, QUANTIZE_BEAT, QUANTIZE_BAR}; - int num_modes = sizeof(modes) / sizeof(modes[0]); - int current = 0; - for (int i = 0; i < num_modes; i++) { - if (state.quantize_mode == modes[i]) { - current = i; - break; - } - } - QuantizeMode next = modes[(current + 1) % num_modes]; - Action action = { .type = ACTION_SET_QUANTIZE_MODE, .data.set_quantize_mode = { .mode = next } }; - g_dispatch(action); - break; - } - case 'T': { - AppState state; - dispatcher_get_state(&state); - jack_nframes_t new_threshold = (state.quantize_threshold == 0) ? 1000 : 0; - Action action = { .type = ACTION_SET_QUANTIZE_THRESHOLD, .data.set_quantize_threshold = { .threshold = new_threshold } }; - g_dispatch(action); - break; - } - case ' ': { - Action action = { .type = ACTION_TRANSPORT_TOGGLE_PLAY }; - g_dispatch(action); - break; - } - case 'S': { - Action action = { .type = ACTION_TRANSPORT_STOP }; - g_dispatch(action); - break; - } - case 'C': { - AppState state; - dispatcher_get_state(&state); - ClockSource next = (state.clock_source == CLOCK_SOURCE_INTERNAL) ? - CLOCK_SOURCE_MIDI : CLOCK_SOURCE_INTERNAL; - Action action = { .type = ACTION_SET_CLOCK_SOURCE, .data.set_clock_source = { .source = next } }; - g_dispatch(action); - break; - } - case 'x': { - Action action = { .type = ACTION_RESET_TRANSPORT }; - g_dispatch(action); - break; - } - case ':': { - bool should_quit = handle_command_mode(); - if (should_quit) return; - break; - } - case 'z': { - zoom_mode = !zoom_mode; - break; - } - case 'G': { - AppState s; - dispatcher_get_state(&s); - Action a = { .type = ACTION_SET_SHOW_MIDI_GRID, .data.set_show_midi_grid = { .show = !s.show_midi_grid } }; - g_dispatch(a); - break; - } - case 'L': { - // Start fuzzy search for WAV files - const char *samples_dir = getenv("JACK_LOOPER_SAMPLES_DIR"); - if (!samples_dir) samples_dir = "."; - int count; - const char **files = list_wav_files(samples_dir, &count); - if (files && count > 0) { - start_fuzzy_search_items("Load sample:", load_sample_callback, - files, count, true); - } else { - // No files found, show message - mvprintw(LINES - 1, 0, "No WAV files found in %s", samples_dir); - refresh(); - napms(1500); - } - break; - } - case '?': - show_help = !show_help; - break; - case 'v': { - current_mode = MODE_VISUAL; - visual_start_row = selected_row; - visual_start_col = selected_col; - visual_end_row = selected_row; - visual_end_col = selected_col; - break; - } - case 'V': { - current_mode = MODE_VISUAL; - visual_start_row = selected_row; - visual_start_col = 0; - visual_end_row = selected_row; - visual_end_col = GRID_COLS - 1; - break; - } - case 'm': { - current_mode = MODE_MOVE; - break; - } - case 'N': - play_next_scene(); - break; - case 'P': - play_prev_scene(); - break; - case '\'': { - nodelay(stdscr, FALSE); - int mark_ch = getch(); - nodelay(stdscr, TRUE); - if (mark_ch != ERR) go_to_mark((char)mark_ch); - break; - } - case 'u': { - Action action = { .type = ACTION_UNDO }; - g_dispatch(action); - break; - } - case 18: { // Ctrl+R - Action action = { .type = ACTION_REDO }; - g_dispatch(action); - break; - } - case '-': case '_': { - // Decrease volume of selected channel - AppState state; - dispatcher_get_state(&state); - float current_vol = carla_get_channel_volume(&state.carla_host, selected_row); - float new_vol = fmaxf(0.0f, current_vol - 0.1f); - Action action = { - .type = ACTION_RACK_SET_VOLUME, - .data.rack_set_volume = { - .channel = selected_row, - .volume = new_vol - } - }; - g_dispatch(action); - break; - } - case '=': case '+': { - // Increase volume of selected channel - AppState state; - dispatcher_get_state(&state); - float current_vol = carla_get_channel_volume(&state.carla_host, selected_row); - float new_vol = fminf(2.0f, current_vol + 0.1f); - Action action = { - .type = ACTION_RACK_SET_VOLUME, - .data.rack_set_volume = { - .channel = selected_row, - .volume = new_vol - } - }; - g_dispatch(action); - break; - } - case '\t': { - // Switch to rack view - current_view = VIEW_RACK; - rack_selected_channel = selected_row; - rack_selected_plugin = -1; - rack_selected_param = -1; - break; - } - case 27: case 'Q': - return; - default: - if (ch == 'm') { - nodelay(stdscr, FALSE); - int mark_ch = getch(); - nodelay(stdscr, TRUE); - if (mark_ch != ERR) set_mark((char)mark_ch); - } - break; - } - draw_grid(); } } void tui_cleanup(void) { - if (yank_buffer.clip_indices) { - free(yank_buffer.clip_indices); - yank_buffer.clip_indices = NULL; - yank_buffer.count = 0; - } - - mousemask(0, NULL); - curs_set(1); - endwin(); + if (yank_buffer.clip_indices) free(yank_buffer.clip_indices); + curs_set(1); endwin(); } diff --git a/client/src/tui.h b/client/src/tui.h index eef59b3..7166f9f 100644 --- a/client/src/tui.h +++ b/client/src/tui.h @@ -1,18 +1,8 @@ #ifndef TUI_H #define TUI_H -#include "engine.h" -#include "dispatcher.h" - -#define NUM_GRIDS 8 - -// Initialize TUI -void tui_init(Engine *engine); - -// Run the TUI main loop -void tui_run(Engine *engine); - -// Cleanup TUI +void tui_init(void); +void tui_run(void); void tui_cleanup(void); -#endif // TUI_H +#endif