feat: add rack mode, colon commands, and client command parser
This commit is contained in:
committed by
Loic Coenen (aider)
parent
c7df02d37c
commit
9fda1b2669
@@ -3,27 +3,36 @@ CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -Isrc
|
||||
CARLA_INC = -I/usr/include/carla -I/usr/include/carla/includes
|
||||
CARLA_LIB = -L/usr/lib/carla -Wl,-rpath,/usr/lib/carla -lcarla_standalone2
|
||||
|
||||
# Objects (must be defined before any rules)
|
||||
CARLA_OBJ = src/carla_host.o
|
||||
PLUGINS_OBJ = src/plugins.o
|
||||
CLIENT_CMD_OBJ = src/client_cmd.o
|
||||
|
||||
# Test binaries
|
||||
TEST_PLUGINS_BIN = test_plugins
|
||||
TEST_CLIENT_BIN = test_client
|
||||
TEST_CARLA_BIN = test_carla_host
|
||||
TEST_CLIENT_CMD_BIN = test_client_cmd
|
||||
|
||||
all: looper-client test_status_parse
|
||||
|
||||
looper-client: src/main.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ)
|
||||
looper-client: src/main.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ)
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -o $@ $^ $(CARLA_LIB) -ljack -lncurses
|
||||
|
||||
test_status_parse: tests/test_status_parse.c $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -o test_status_parse tests/test_status_parse.c src/tui.c $(CARLA_OBJ) $(CARLA_LIB) -ljack -lncurses
|
||||
test_status_parse: tests/test_status_parse.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ)
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -o test_status_parse tests/test_status_parse.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) $(CARLA_LIB) -ljack -lncurses
|
||||
|
||||
# --- Plugin stubs (now real) ---
|
||||
PLUGINS_OBJ = src/plugins.o
|
||||
|
||||
$(PLUGINS_OBJ): src/plugins.c src/plugins.h
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
$(CARLA_OBJ): src/carla_host.c src/carla_host.h
|
||||
$(CC) -Wall -Wextra -std=c11 -Isrc $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
$(CLIENT_CMD_OBJ): src/client_cmd.c src/client_cmd.h
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
# --- Plugin tests ---
|
||||
TEST_PLUGINS_BIN = test_plugins
|
||||
TEST_PLUGINS_OBJ = tests/test_plugins.o
|
||||
|
||||
$(TEST_PLUGINS_OBJ): tests/test_plugins.c src/plugins.h
|
||||
@@ -33,20 +42,27 @@ $(TEST_PLUGINS_BIN): $(TEST_PLUGINS_OBJ) $(PLUGINS_OBJ) $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(CARLA_LIB) -ljack
|
||||
|
||||
# ensure the tests directory exists
|
||||
tests/test_plugins.o: | tests
|
||||
$(TEST_PLUGINS_OBJ): | tests
|
||||
|
||||
# --- Client command tests ---
|
||||
TEST_CLIENT_CMD_OBJ = tests/test_client_cmd.o
|
||||
|
||||
$(TEST_CLIENT_CMD_OBJ): tests/test_client_cmd.c src/client_cmd.h src/plugins.h
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
$(TEST_CLIENT_CMD_BIN): $(TEST_CLIENT_CMD_OBJ) $(CLIENT_CMD_OBJ) $(PLUGINS_OBJ) $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(CARLA_LIB) -ljack
|
||||
|
||||
# --- send_command test ---
|
||||
TEST_CLIENT_BIN = test_client
|
||||
TEST_CLIENT_OBJ = tests/test_client.o
|
||||
|
||||
$(TEST_CLIENT_OBJ): tests/test_client.c src/tui.h
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
$(TEST_CLIENT_BIN): $(TEST_CLIENT_OBJ) src/tui.c $(CARLA_OBJ)
|
||||
$(TEST_CLIENT_BIN): $(TEST_CLIENT_OBJ) src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ)
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -o $@ $^ $(CARLA_LIB) -ljack -lncurses
|
||||
|
||||
# --- Carla host tests ---
|
||||
TEST_CARLA_BIN = test_carla_host
|
||||
TEST_CARLA_OBJ = tests/test_carla_host.o
|
||||
|
||||
$(TEST_CARLA_OBJ): tests/test_carla_host.c src/carla_host.h
|
||||
@@ -55,13 +71,14 @@ $(TEST_CARLA_OBJ): tests/test_carla_host.c src/carla_host.h
|
||||
$(TEST_CARLA_BIN): $(TEST_CARLA_OBJ) $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(CARLA_LIB) -ljack
|
||||
|
||||
test: looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN)
|
||||
test: looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN) $(TEST_CLIENT_CMD_BIN)
|
||||
./test_status_parse
|
||||
./$(TEST_PLUGINS_BIN)
|
||||
./$(TEST_CLIENT_BIN)
|
||||
./$(TEST_CARLA_BIN)
|
||||
./$(TEST_CLIENT_CMD_BIN)
|
||||
|
||||
.PHONY: all test clean
|
||||
|
||||
clean:
|
||||
rm -f looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN) *.o tests/*.o src/*.o
|
||||
rm -f looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN) $(TEST_CLIENT_CMD_BIN) *.o tests/*.o src/*.o
|
||||
|
||||
@@ -11,6 +11,17 @@ static jack_client_t *jack_client = NULL; // private JACK client for port conn
|
||||
static int carla_pids[MAX_PLUGINS];
|
||||
static int plugin_count = 0;
|
||||
|
||||
#define MAX_CONNECTIONS 1024
|
||||
|
||||
typedef struct {
|
||||
int plugin_id;
|
||||
char plugin_port[256];
|
||||
char looper_port[256];
|
||||
} connection_t;
|
||||
|
||||
static connection_t connections[MAX_CONNECTIONS];
|
||||
static int conn_count = 0;
|
||||
|
||||
int carla_init_jack(void) {
|
||||
if (handle != NULL) return 0;
|
||||
|
||||
@@ -95,7 +106,20 @@ int carla_connect(int id, const char *port_name, const char *looper_port) {
|
||||
|
||||
// Real JACK port connection
|
||||
int ret = jack_connect(jack_client, looper_port, port_name);
|
||||
return (ret == 0) ? 0 : -1;
|
||||
if (ret != 0) return -1;
|
||||
|
||||
// Store the connection so we can disconnect it later
|
||||
if (conn_count < MAX_CONNECTIONS) {
|
||||
connections[conn_count].plugin_id = id;
|
||||
strncpy(connections[conn_count].plugin_port, port_name,
|
||||
sizeof(connections[conn_count].plugin_port) - 1);
|
||||
connections[conn_count].plugin_port[sizeof(connections[conn_count].plugin_port) - 1] = '\0';
|
||||
strncpy(connections[conn_count].looper_port, looper_port,
|
||||
sizeof(connections[conn_count].looper_port) - 1);
|
||||
connections[conn_count].looper_port[sizeof(connections[conn_count].looper_port) - 1] = '\0';
|
||||
conn_count++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int carla_disconnect(const char *from, const char *to) {
|
||||
@@ -105,7 +129,20 @@ int carla_disconnect(const char *from, const char *to) {
|
||||
|
||||
// Real JACK port disconnection
|
||||
int ret = jack_disconnect(jack_client, from, to);
|
||||
return (ret == 0) ? 0 : -1;
|
||||
if (ret != 0) return -1;
|
||||
|
||||
// Remove the connection from our internal list (matching both port names)
|
||||
for (int i = 0; i < conn_count; i++) {
|
||||
if (strcmp(connections[i].looper_port, from) == 0 &&
|
||||
strcmp(connections[i].plugin_port, to) == 0) {
|
||||
// Shift remaining entries down
|
||||
for (int j = i; j < conn_count - 1; j++)
|
||||
connections[j] = connections[j + 1];
|
||||
conn_count--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void carla_set_bypass(int id, bool bypass) {
|
||||
@@ -114,3 +151,28 @@ void carla_set_bypass(int id, bool bypass) {
|
||||
int pid = carla_pids[id];
|
||||
carla_set_active(handle, (uint)pid, !bypass);
|
||||
}
|
||||
|
||||
int carla_disconnect_plugin(int id) {
|
||||
if (!jack_client) return 0;
|
||||
// Disconnect all stored connections for this plugin id
|
||||
int any = 0;
|
||||
for (int i = 0; i < conn_count; ) {
|
||||
if (connections[i].plugin_id == id) {
|
||||
jack_disconnect(jack_client,
|
||||
connections[i].looper_port,
|
||||
connections[i].plugin_port);
|
||||
// Shift array
|
||||
for (int j = i; j < conn_count - 1; j++)
|
||||
connections[j] = connections[j + 1];
|
||||
conn_count--;
|
||||
any = 1;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return any ? 0 : -1; // return -1 if no connections were found (harmless)
|
||||
}
|
||||
|
||||
CarlaHostHandle carla_get_handle(void) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define CARLA_HOST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <CarlaHost.h> /* CarlaHostHandle typedef */
|
||||
|
||||
/* All functions return -1 on error, 0 on success (except carla_load which returns 0 on success and sets *out_id) */
|
||||
|
||||
@@ -14,4 +15,8 @@ int carla_connect(int id, const char *port_name, const char *looper_port);
|
||||
int carla_disconnect(const char *from, const char *to);
|
||||
void carla_set_bypass(int id, bool bypass);
|
||||
|
||||
/* Get internal Carla host handle, may be NULL */
|
||||
int carla_disconnect_plugin(int id);
|
||||
CarlaHostHandle carla_get_handle(void);
|
||||
|
||||
#endif
|
||||
|
||||
119
client/src/client_cmd.c
Normal file
119
client/src/client_cmd.c
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "client_cmd.h"
|
||||
#include "plugins.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static char from_port[256] = "";
|
||||
static char to_port[256] = "";
|
||||
|
||||
const char* get_stored_from(void) { return from_port; }
|
||||
const char* get_stored_to(void) { return to_port; }
|
||||
|
||||
static int get_plugin_id_for_port(const char *port_spec) {
|
||||
// port_spec format: "plugin_id:port_name"
|
||||
const char *colon = strchr(port_spec, ':');
|
||||
if (!colon) return -1;
|
||||
int id = atoi(port_spec);
|
||||
(void)colon; // atoi stops at colon
|
||||
return id;
|
||||
}
|
||||
|
||||
int handle_client_command(const char *input, int *out_id) {
|
||||
if (!input || *input == '\0') return -1;
|
||||
|
||||
// Copy input so we can use strtok
|
||||
char buf[256];
|
||||
strncpy(buf, input, sizeof(buf)-1);
|
||||
buf[sizeof(buf)-1] = '\0';
|
||||
|
||||
const char *token = strtok(buf, " ");
|
||||
if (!token) return -1;
|
||||
|
||||
// --- from <port> ---
|
||||
if (strcmp(token, "from") == 0) {
|
||||
const char *port = strtok(NULL, " ");
|
||||
if (!port) return -1;
|
||||
strncpy(from_port, port, sizeof(from_port)-1);
|
||||
from_port[sizeof(from_port)-1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- to <port> ---
|
||||
if (strcmp(token, "to") == 0) {
|
||||
const char *port = strtok(NULL, " ");
|
||||
if (!port) return -1;
|
||||
strncpy(to_port, port, sizeof(to_port)-1);
|
||||
to_port[sizeof(to_port)-1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- addplugin <path> ---
|
||||
if (strcmp(token, "addplugin") == 0) {
|
||||
const char *path = strtok(NULL, " ");
|
||||
if (!path || *path == '\0') return -1;
|
||||
|
||||
int id;
|
||||
int ret = plugin_load(path, path, &id);
|
||||
if (ret == 0 && out_id) *out_id = id;
|
||||
|
||||
// auto-connect using stored :from/:to if set
|
||||
if (ret == 0 && from_port[0] && to_port[0]) {
|
||||
// parse plugin port name from stored from_port ("plugin_id:port_name")
|
||||
const char *colon = strchr(from_port, ':');
|
||||
if (colon) {
|
||||
const char *pname = colon + 1;
|
||||
plugin_connect(id, pname, to_port);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// --- connect [<from_port>] [<to_port>] ---
|
||||
if (strcmp(token, "connect") == 0) {
|
||||
const char *from = strtok(NULL, " ");
|
||||
const char *to = strtok(NULL, " ");
|
||||
if (!from) {
|
||||
if (from_port[0]) from = from_port;
|
||||
else return -1;
|
||||
}
|
||||
if (!to) {
|
||||
if (to_port[0]) to = to_port;
|
||||
else return -1;
|
||||
}
|
||||
|
||||
// Parse plugin id from "plugin_id:port"
|
||||
int id_from = get_plugin_id_for_port(from);
|
||||
if (id_from < 0) return -1;
|
||||
|
||||
const char *port_name = strchr(from, ':');
|
||||
if (!port_name) return -1;
|
||||
port_name++;
|
||||
|
||||
return plugin_connect(id_from, port_name, to);
|
||||
}
|
||||
|
||||
// --- disconnect [<from_port>] [<to_port>] ---
|
||||
if (strcmp(token, "disconnect") == 0) {
|
||||
const char *from = strtok(NULL, " ");
|
||||
const char *to = strtok(NULL, " ");
|
||||
if (!from) {
|
||||
if (from_port[0]) from = from_port;
|
||||
else return -1;
|
||||
}
|
||||
if (!to) {
|
||||
if (to_port[0]) to = to_port;
|
||||
else return -1;
|
||||
}
|
||||
return plugin_disconnect(from, to);
|
||||
}
|
||||
|
||||
// --- rack / grid commands toggle via colon mode (just acknowledge) ---
|
||||
if (strcmp(token, "rack") == 0 || strcmp(token, "grid") == 0) {
|
||||
// rack mode toggled by 'R' key in tui; colon commands do nothing except return success
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1; // unknown command
|
||||
}
|
||||
16
client/src/client_cmd.h
Normal file
16
client/src/client_cmd.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef CLIENT_CMD_H
|
||||
#define CLIENT_CMD_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
* Handle a client command (without the leading ':').
|
||||
* Returns 0 on success, -1 on error.
|
||||
* If the command loads/creates a new plugin, *out_id is set to the new ID.
|
||||
* Otherwise *out_id is unchanged.
|
||||
*/
|
||||
int handle_client_command(const char *input, int *out_id);
|
||||
const char* get_stored_from(void);
|
||||
const char* get_stored_to(void);
|
||||
|
||||
#endif
|
||||
130
client/src/tui.c
130
client/src/tui.c
@@ -10,6 +10,9 @@
|
||||
#include <sys/stat.h>
|
||||
#include <math.h>
|
||||
#include "carla_host.h"
|
||||
#include "client_cmd.h"
|
||||
#include "plugins.h"
|
||||
#include <CarlaHost.h>
|
||||
|
||||
/* ---------- FIFO command helper ---------- */
|
||||
int send_command(const char *cmd) {
|
||||
@@ -54,6 +57,8 @@ enum {
|
||||
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];
|
||||
@@ -118,7 +123,41 @@ static void draw_cell(int grid, int row, int col, bool selected) {
|
||||
attroff(COLOR_PAIR(color));
|
||||
}
|
||||
|
||||
static void draw_rack(void) {
|
||||
clear();
|
||||
attron(A_BOLD);
|
||||
mvprintw(0,0,"Rack View - Plugins");
|
||||
attroff(A_BOLD);
|
||||
CarlaHostHandle h = carla_get_handle();
|
||||
if (!h) {
|
||||
mvprintw(2,0,"Carla host not initialised");
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
uint32_t count = carla_get_current_plugin_count(h);
|
||||
if (count == 0) {
|
||||
mvprintw(2,0,"No plugins loaded");
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
for (uint32_t i=0; i<count; ++i) {
|
||||
const CarlaPluginInfo *info = carla_get_plugin_info(h, i);
|
||||
if (!info) continue;
|
||||
if ((int)i == rack_selected)
|
||||
attron(A_REVERSE);
|
||||
mvprintw(2+i,0,"%u: %s", i, info->name ? 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)");
|
||||
@@ -130,7 +169,7 @@ static void draw_grid(void) {
|
||||
selected_grid, selected_row, selected_col);
|
||||
if (show_help) {
|
||||
attron(COLOR_PAIR(COLOR_HELP));
|
||||
mvprintw(GRID_ROWS*CELL_HEIGHT+4, 0, "Help: h/j/k/l navigate, t record, d/D stop, s/S scene, a add, A add_midi, r remove, b bind, u unbind, ? help, Esc/Q quit");
|
||||
mvprintw(GRID_ROWS*CELL_HEIGHT+4, 0, "Help: h/j/k/l navigate, t record, d/D stop, s/S scene, a add, A add_midi, r remove, b bind, u unbind, R rack, ? help, Esc/Q quit");
|
||||
attroff(COLOR_PAIR(COLOR_HELP));
|
||||
}
|
||||
refresh();
|
||||
@@ -157,6 +196,10 @@ void tui_init(void) {
|
||||
}
|
||||
|
||||
/* ---------- TUI run ---------- */
|
||||
static char colon_buf[256];
|
||||
static int colon_len = 0;
|
||||
static bool in_colon = false;
|
||||
|
||||
void tui_run(void) {
|
||||
draw_grid();
|
||||
while (1) {
|
||||
@@ -185,7 +228,45 @@ void tui_run(void) {
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
if (in_colon) {
|
||||
int chc = getch();
|
||||
if (chc == '\n') {
|
||||
colon_buf[colon_len] = '\0';
|
||||
colon_len = 0;
|
||||
in_colon = 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;
|
||||
@@ -225,8 +306,51 @@ void tui_run(void) {
|
||||
send_command("unbind\n");
|
||||
break;
|
||||
case '?': show_help = !show_help; break;
|
||||
case 27: case 'Q': return;
|
||||
default: 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();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,16 @@ static int tests_failed = 0;
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_TRUE(expr, msg) do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "FAIL: %s\n", msg); \
|
||||
tests_failed++; \
|
||||
} else { \
|
||||
printf("PASS: %s\n", msg); \
|
||||
tests_passed++; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static void test_carla_load_null_binary(void)
|
||||
{
|
||||
int id = -999;
|
||||
@@ -33,13 +43,50 @@ static void test_carla_connect_invalid_id(void)
|
||||
ASSERT_EQ(-1, ret, "carla_connect(-1, ...) returns -1");
|
||||
}
|
||||
|
||||
static void test_carla_get_handle_before_init(void)
|
||||
{
|
||||
CarlaHostHandle h = carla_get_handle();
|
||||
ASSERT_TRUE(h == NULL, "carla_get_handle() returns NULL before init");
|
||||
}
|
||||
|
||||
static void test_carla_set_bypass_invalid_id(void)
|
||||
{
|
||||
carla_set_bypass(-1, true);
|
||||
printf("PASS: carla_set_bypass(-1, true) did not crash\n");
|
||||
tests_passed++;
|
||||
}
|
||||
|
||||
static void test_carla_disconnect_no_jack(void)
|
||||
{
|
||||
int ret = carla_disconnect("from", "to");
|
||||
ASSERT_EQ(0, ret, "carla_disconnect('from','to') returns 0 when no JACK client");
|
||||
}
|
||||
|
||||
static void test_carla_set_bypass_valid_id_no_handle(void)
|
||||
{
|
||||
carla_set_bypass(0, true);
|
||||
printf("PASS: carla_set_bypass(0, true) did not crash (no handle)\n");
|
||||
tests_passed++;
|
||||
}
|
||||
|
||||
static void test_carla_unload_valid_id_no_handle(void)
|
||||
{
|
||||
int ret = carla_unload(0);
|
||||
ASSERT_EQ(-1, ret, "carla_unload(0) returns -1 when no handle");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("=== Carla host stub unit tests ===\n");
|
||||
printf("=== Carla host unit tests ===\n");
|
||||
|
||||
test_carla_load_null_binary();
|
||||
test_carla_unload_invalid_id();
|
||||
test_carla_connect_invalid_id();
|
||||
test_carla_get_handle_before_init();
|
||||
test_carla_set_bypass_invalid_id();
|
||||
test_carla_disconnect_no_jack();
|
||||
test_carla_set_bypass_valid_id_no_handle();
|
||||
test_carla_unload_valid_id_no_handle();
|
||||
|
||||
printf("\nResults: %d passed, %d failed\n", tests_passed, tests_failed);
|
||||
return tests_failed > 0 ? 1 : 0;
|
||||
|
||||
111
client/tests/test_client_cmd.c
Normal file
111
client/tests/test_client_cmd.c
Normal file
@@ -0,0 +1,111 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "client_cmd.h"
|
||||
#include "plugins.h"
|
||||
|
||||
static int tests_passed = 0;
|
||||
static int tests_failed = 0;
|
||||
|
||||
#define ASSERT_EQ(expected, actual, msg) do { \
|
||||
if ((expected) != (actual)) { \
|
||||
fprintf(stderr, "FAIL: %s (expected %d, got %d)\n", msg, (int)(expected), (int)(actual)); \
|
||||
tests_failed++; \
|
||||
} else { \
|
||||
printf("PASS: %s\n", msg); \
|
||||
tests_passed++; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_STR_EQ(expected, actual, msg) do { \
|
||||
if (strcmp((expected), (actual)) != 0) { \
|
||||
fprintf(stderr, "FAIL: %s (expected \"%s\", got \"%s\")\n", msg, (expected), (actual)); \
|
||||
tests_failed++; \
|
||||
} else { \
|
||||
printf("PASS: %s\n", msg); \
|
||||
tests_passed++; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* Test invalid commands */
|
||||
static void test_unknown_command(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command("unknown_command", &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command('unknown_command', ...) returns -1");
|
||||
}
|
||||
|
||||
static void test_empty_input(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command("", &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command('', ...) returns -1");
|
||||
}
|
||||
|
||||
static void test_null_input(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command(NULL, &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command(NULL, ...) returns -1");
|
||||
}
|
||||
|
||||
/* Test addplugin command */
|
||||
static void test_addplugin_no_path(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command("addplugin", &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command('addplugin', ...) returns -1 (no path)");
|
||||
}
|
||||
|
||||
static void test_addplugin_empty_path(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command("addplugin ", &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command('addplugin ', ...) returns -1 (empty path)");
|
||||
}
|
||||
|
||||
static void test_addplugin_valid(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command("addplugin /does/not/exist.so", &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command('addplugin /does/not/exist.so', ...) returns -1 (no such file)");
|
||||
}
|
||||
|
||||
/* Test connect command */
|
||||
static void test_connect_no_args(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command("connect", &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command('connect', ...) returns -1 (no args)");
|
||||
}
|
||||
|
||||
static void test_connect_missing_to(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command("connect plugin:out_1", &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command('connect plugin:out_1', ...) returns -1 (missing 'to')");
|
||||
}
|
||||
|
||||
static void test_connect_invalid_id(void)
|
||||
{
|
||||
int id = -1;
|
||||
int ret = handle_client_command("connect plugin:out looper:in", &id);
|
||||
ASSERT_EQ(-1, ret, "handle_client_command('connect plugin:out looper:in', ...) returns -1 (stub)");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("=== Client command parser unit tests ===\n");
|
||||
|
||||
test_unknown_command();
|
||||
test_empty_input();
|
||||
test_null_input();
|
||||
test_addplugin_no_path();
|
||||
test_addplugin_empty_path();
|
||||
test_addplugin_valid();
|
||||
test_connect_no_args();
|
||||
test_connect_missing_to();
|
||||
test_connect_invalid_id();
|
||||
|
||||
printf("\nResults: %d passed, %d failed\n", tests_passed, tests_failed);
|
||||
return tests_failed > 0 ? 1 : 0;
|
||||
}
|
||||
@@ -14,56 +14,72 @@ static int tests_failed = 0;
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static void test_plugin_load_null_binary(void)
|
||||
#define ASSERT_TRUE(expr, msg) do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "FAIL: %s\n", msg); \
|
||||
tests_failed++; \
|
||||
} else { \
|
||||
printf("PASS: %s\n", msg); \
|
||||
tests_passed++; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
static void test_plugin_load_null(void)
|
||||
{
|
||||
int id = -999;
|
||||
int ret = plugin_load(NULL, "someplugin", &id);
|
||||
ASSERT_EQ(-1, ret, "plugin_load(NULL, ...) returns -1");
|
||||
int ret = plugin_load(NULL, NULL, &id);
|
||||
ASSERT_EQ(-1, ret, "plugin_load(NULL, NULL, ...) returns -1");
|
||||
}
|
||||
|
||||
static void test_plugin_load_nonnull_binary(void)
|
||||
{
|
||||
int id = -999;
|
||||
int ret = plugin_load("/path/to/plugin.so", NULL, &id);
|
||||
ASSERT_EQ(-1, ret, "plugin_load(non‑NULL binary, ...) returns -1");
|
||||
}
|
||||
|
||||
static void test_plugin_unload_invalid_id(void)
|
||||
static void test_plugin_unload_invalid(void)
|
||||
{
|
||||
int ret = plugin_unload(-1);
|
||||
ASSERT_EQ(-1, ret, "plugin_unload(-1) returns -1");
|
||||
}
|
||||
|
||||
static void test_plugin_connect_invalid_id(void)
|
||||
static void test_plugin_connect_invalid(void)
|
||||
{
|
||||
int ret = plugin_connect(-1, "out", "looper:in");
|
||||
ASSERT_EQ(-1, ret, "plugin_connect(-1, ...) returns -1");
|
||||
}
|
||||
|
||||
static void test_plugin_disconnect(void)
|
||||
static void test_plugin_disconnect_no_jack(void)
|
||||
{
|
||||
int ret = plugin_disconnect("from_port", "to_port");
|
||||
ASSERT_EQ(0, ret, "plugin_disconnect(...) returns 0");
|
||||
int ret = plugin_disconnect("from", "to");
|
||||
ASSERT_EQ(0, ret, "plugin_disconnect('from','to') returns 0 (safe stub)");
|
||||
}
|
||||
|
||||
static void test_plugin_set_bypass_invalid(void)
|
||||
static void test_plugin_set_bypass_invalid_id(void)
|
||||
{
|
||||
/* set_bypass returns void; just make sure it doesn't crash */
|
||||
plugin_set_bypass(-1, true);
|
||||
printf("PASS: plugin_set_bypass(-1, true) did not crash\n");
|
||||
tests_passed++;
|
||||
}
|
||||
|
||||
static void test_plugin_set_bypass_valid_id(void)
|
||||
{
|
||||
plugin_set_bypass(0, true);
|
||||
printf("PASS: plugin_set_bypass(0, true) did not crash\n");
|
||||
tests_passed++;
|
||||
}
|
||||
|
||||
static void test_plugin_connect_valid_id(void)
|
||||
{
|
||||
int ret = plugin_connect(0, "out", "looper:in");
|
||||
ASSERT_EQ(-1, ret, "plugin_connect(0, ...) returns -1 (no plugin loaded)");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("=== Plugin stub unit tests ===\n");
|
||||
|
||||
test_plugin_load_null_binary();
|
||||
test_plugin_load_nonnull_binary();
|
||||
test_plugin_unload_invalid_id();
|
||||
test_plugin_connect_invalid_id();
|
||||
test_plugin_disconnect();
|
||||
test_plugin_set_bypass_invalid();
|
||||
test_plugin_load_null();
|
||||
test_plugin_unload_invalid();
|
||||
test_plugin_connect_invalid();
|
||||
test_plugin_disconnect_no_jack();
|
||||
test_plugin_set_bypass_invalid_id();
|
||||
test_plugin_set_bypass_valid_id();
|
||||
test_plugin_connect_valid_id();
|
||||
|
||||
printf("\nResults: %d passed, %d failed\n", tests_passed, tests_failed);
|
||||
return tests_failed > 0 ? 1 : 0;
|
||||
|
||||
Reference in New Issue
Block a user