feat: add fuzzy search dialog for loading WAV samples
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
This commit is contained in:
161
tui.c
161
tui.c
@@ -4,11 +4,17 @@
|
||||
#include "carla.h"
|
||||
#include <ncurses.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <math.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
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) {
|
||||
mvprintw(start_y + 2 + i, start_x, "%s", plugins[idx]);
|
||||
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];
|
||||
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 && fuzzy_search.callback) {
|
||||
fuzzy_search.callback(plugins[idx]);
|
||||
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,6 +463,13 @@ static bool handle_fuzzy_search(int 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++) {
|
||||
@@ -367,6 +477,7 @@ static bool handle_fuzzy_search(int ch) {
|
||||
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;
|
||||
}
|
||||
@@ -407,6 +518,13 @@ static bool handle_fuzzy_search(int 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++) {
|
||||
@@ -414,6 +532,7 @@ static bool handle_fuzzy_search(int ch) {
|
||||
fuzzy_search.result_indices[fuzzy_search.num_results++] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
fuzzy_search.selected_index = 0;
|
||||
}
|
||||
return true;
|
||||
@@ -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,14 +550,28 @@ 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;
|
||||
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
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user