diff --git a/tui.c b/tui.c index 6ffbaf1..8151953 100644 --- a/tui.c +++ b/tui.c @@ -4,11 +4,17 @@ #include "carla.h" #include #include +#include #include #include #include #include +#include +#include +#include +#include #include +#include #define CELL_WIDTH 6 #define CELL_HEIGHT 3 @@ -54,6 +60,9 @@ typedef struct { 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 } FuzzySearch; static FuzzySearch fuzzy_search = {0}; @@ -276,6 +285,60 @@ static void midi_to_callback(const char *selection) { } } +// 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; @@ -320,10 +383,20 @@ static void draw_fuzzy_search(void) { if (i == fuzzy_search.selected_index) { attron(A_REVERSE); } - int count; - const char **plugins = carla_get_available_plugins(&g_engine->carla_host, &count); - if (plugins && idx >= 0 && idx < count) { - mvprintw(start_y + 2 + i, start_x, "%s", plugins[idx]); + 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); @@ -342,16 +415,46 @@ static bool handle_fuzzy_search(int 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]; - int count; - const char **plugins = carla_get_available_plugins(NULL, &count); - if (plugins && idx >= 0 && idx < count && fuzzy_search.callback) { - fuzzy_search.callback(plugins[idx]); + 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; } @@ -360,11 +463,19 @@ static bool handle_fuzzy_search(int ch) { fuzzy_search.query[--fuzzy_search.query_len] = '\0'; // Update results fuzzy_search.num_results = 0; - 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.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) { @@ -407,11 +518,19 @@ static bool handle_fuzzy_search(int ch) { fuzzy_search.query[fuzzy_search.query_len] = '\0'; // Update results fuzzy_search.num_results = 0; - 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.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; @@ -421,8 +540,9 @@ static bool handle_fuzzy_search(int ch) { } } -// Start fuzzy search -static void start_fuzzy_search(const char *prompt, void (*callback)(const char *)) { +// 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'; @@ -430,16 +550,30 @@ static void start_fuzzy_search(const char *prompt, void (*callback)(const char * 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 plugins + // Initialize results with all items fuzzy_search.num_results = 0; - 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; + 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(); @@ -811,6 +945,21 @@ static bool handle_command_mode(void) { } 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);