feat: add rack mode, colon commands, and client command parser
This commit is contained in:
committed by
Loic Coenen (aider)
parent
c7df02d37c
commit
9fda1b2669
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user