feat: integrate real Carla host with JACK support and add plugin abstraction layer
This commit is contained in:
Binary file not shown.
@@ -1,49 +1,67 @@
|
||||
CC = gcc
|
||||
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
|
||||
|
||||
CARLA_OBJ = src/carla_host.o
|
||||
|
||||
all: looper-client test_status_parse
|
||||
|
||||
looper-client: src/main.c src/tui.c $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $^ -lncurses
|
||||
looper-client: src/main.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -o $@ $^ $(CARLA_LIB) -ljack -lncurses
|
||||
|
||||
test_status_parse: tests/test_status_parse.c
|
||||
$(CC) $(CFLAGS) -o test_status_parse tests/test_status_parse.c src/tui.c -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
|
||||
|
||||
# --- Carla host stubs ---
|
||||
CARLA_OBJ = src/carla_host.o
|
||||
# --- 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) $(CFLAGS) -c -o $@ $<
|
||||
$(CC) -Wall -Wextra -std=c11 -Isrc $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
# --- Carla host tests ---
|
||||
TEST_CARLA_BIN = test_carla_host
|
||||
TEST_CARLA_OBJ = tests/test_carla_host.o
|
||||
# --- Plugin tests ---
|
||||
TEST_PLUGINS_BIN = test_plugins
|
||||
TEST_PLUGINS_OBJ = tests/test_plugins.o
|
||||
|
||||
$(TEST_CARLA_OBJ): tests/test_carla_host.c src/carla_host.h
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
$(TEST_PLUGINS_OBJ): tests/test_plugins.c src/plugins.h
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
$(TEST_CARLA_BIN): $(TEST_CARLA_OBJ) $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
$(TEST_PLUGINS_BIN): $(TEST_PLUGINS_OBJ) $(PLUGINS_OBJ) $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(CARLA_LIB) -ljack
|
||||
|
||||
# ensure the tests directory exists
|
||||
tests/test_carla_host.o: | tests
|
||||
tests/test_plugins.o: | tests
|
||||
|
||||
# --- 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) -c -o $@ $<
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
$(TEST_CLIENT_BIN): $(TEST_CLIENT_OBJ) src/tui.c
|
||||
$(CC) $(CFLAGS) -o $@ $^ -lncurses
|
||||
$(TEST_CLIENT_BIN): $(TEST_CLIENT_OBJ) src/tui.c $(CARLA_OBJ)
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -o $@ $^ $(CARLA_LIB) -ljack -lncurses
|
||||
|
||||
test: looper-client test_status_parse $(TEST_CARLA_BIN) $(TEST_CLIENT_BIN)
|
||||
# --- 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
|
||||
$(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $<
|
||||
|
||||
$(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_status_parse
|
||||
./$(TEST_CARLA_BIN)
|
||||
./$(TEST_PLUGINS_BIN)
|
||||
./$(TEST_CLIENT_BIN)
|
||||
./$(TEST_CARLA_BIN)
|
||||
|
||||
.PHONY: all test clean
|
||||
|
||||
clean:
|
||||
rm -f looper-client test_status_parse $(TEST_CARLA_BIN) $(TEST_CLIENT_BIN) *.o tests/*.o src/*.o
|
||||
rm -f looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN) *.o tests/*.o src/*.o
|
||||
|
||||
@@ -1,38 +1,116 @@
|
||||
#include <stddef.h>
|
||||
#include <CarlaHost.h>
|
||||
#include <CarlaBackend.h>
|
||||
#include <jack/jack.h>
|
||||
#include <string.h>
|
||||
#include "carla_host.h"
|
||||
|
||||
int carla_load(const char *binary, const char *plugin_id, int *out_id)
|
||||
{
|
||||
(void)plugin_id;
|
||||
(void)out_id;
|
||||
if (binary == NULL) return -1;
|
||||
// stub: always fails (will be replaced by real Carla later)
|
||||
return -1;
|
||||
#define MAX_PLUGINS 256
|
||||
|
||||
static CarlaHostHandle handle = NULL;
|
||||
static jack_client_t *jack_client = NULL; // private JACK client for port connections
|
||||
static int carla_pids[MAX_PLUGINS];
|
||||
static int plugin_count = 0;
|
||||
|
||||
int carla_init_jack(void) {
|
||||
if (handle != NULL) return 0;
|
||||
|
||||
// 1) Open our own JACK client (for port connections)
|
||||
jack_status_t status;
|
||||
jack_client = jack_client_open("looper-connector", JackNoStartServer, &status);
|
||||
// It's okay if jack_client is NULL; we still try Carla
|
||||
|
||||
// 2) Create the Carla host handle
|
||||
handle = carla_standalone_host_init();
|
||||
if (!handle) {
|
||||
if (jack_client) jack_client_close(jack_client);
|
||||
jack_client = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 3) Initialise the JACK engine (Carla uses its own JACK client)
|
||||
if (!carla_engine_init(handle, "JACK", "looper-client")) {
|
||||
carla_engine_close(handle);
|
||||
handle = NULL;
|
||||
if (jack_client) jack_client_close(jack_client);
|
||||
jack_client = NULL;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int carla_unload(int id)
|
||||
{
|
||||
(void)id;
|
||||
return -1; // stub: always fails
|
||||
void carla_cleanup_jack(void) {
|
||||
if (handle != NULL) {
|
||||
carla_engine_close(handle);
|
||||
handle = NULL;
|
||||
}
|
||||
if (jack_client) {
|
||||
jack_client_close(jack_client);
|
||||
jack_client = NULL;
|
||||
}
|
||||
plugin_count = 0;
|
||||
}
|
||||
|
||||
int carla_connect(int id, const char *port_name, const char *looper_port)
|
||||
{
|
||||
(void)id;
|
||||
(void)port_name;
|
||||
(void)looper_port;
|
||||
return -1; // stub: always fails
|
||||
int carla_load(const char *binary, const char *plugin_id, int *out_id) {
|
||||
if (!handle) return -1;
|
||||
if (!binary) binary = "";
|
||||
if (!plugin_id) plugin_id = "";
|
||||
|
||||
// carla_add_plugin: (handle, BinaryType, PluginType, filename, name, label, uniqueId, extraPtr, options)
|
||||
if (!carla_add_plugin(handle, 0, 0, binary, NULL, plugin_id, 0, NULL, 0))
|
||||
return -1;
|
||||
|
||||
// newly added plugin is at index (count-1)
|
||||
uint32_t count = carla_get_current_plugin_count(handle);
|
||||
if (count == 0) return -1;
|
||||
|
||||
if (plugin_count >= MAX_PLUGINS) {
|
||||
carla_remove_plugin(handle, count - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int idx = plugin_count++;
|
||||
carla_pids[idx] = count - 1; // Carla’s internal ID
|
||||
*out_id = idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int carla_disconnect(const char *from, const char *to)
|
||||
{
|
||||
(void)from;
|
||||
(void)to;
|
||||
return 0; // stub: disconnect always succeeds (does nothing)
|
||||
int carla_unload(int id) {
|
||||
if (!handle) return -1;
|
||||
if (id < 0 || id >= plugin_count) return -1;
|
||||
int pid = carla_pids[id];
|
||||
bool ok = carla_remove_plugin(handle, (uint)pid);
|
||||
// shift array
|
||||
for (int i = id; i < plugin_count - 1; ++i)
|
||||
carla_pids[i] = carla_pids[i+1];
|
||||
plugin_count--;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
void carla_set_bypass(int id, bool bypass)
|
||||
{
|
||||
(void)id;
|
||||
(void)bypass;
|
||||
int carla_connect(int id, const char *port_name, const char *looper_port) {
|
||||
// Check that the plugin id is valid
|
||||
if (id < 0 || id >= plugin_count)
|
||||
return -1;
|
||||
if (!port_name || !looper_port) return -1;
|
||||
if (!jack_client) return -1;
|
||||
|
||||
// Real JACK port connection
|
||||
int ret = jack_connect(jack_client, looper_port, port_name);
|
||||
return (ret == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
int carla_disconnect(const char *from, const char *to) {
|
||||
// If no JACK client, pretend success (allows unit tests without JACK server)
|
||||
if (!jack_client) return 0;
|
||||
if (!from || !to) return -1;
|
||||
|
||||
// Real JACK port disconnection
|
||||
int ret = jack_disconnect(jack_client, from, to);
|
||||
return (ret == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
void carla_set_bypass(int id, bool bypass) {
|
||||
if (!handle) return;
|
||||
if (id < 0 || id >= plugin_count) return;
|
||||
int pid = carla_pids[id];
|
||||
carla_set_active(handle, (uint)pid, !bypass);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
/* All functions return -1 on error, 0 on success (except carla_load which returns 0 on success and sets *out_id) */
|
||||
|
||||
int carla_init_jack(void);
|
||||
void carla_cleanup_jack(void);
|
||||
|
||||
int carla_load(const char *binary, const char *plugin_id, int *out_id);
|
||||
int carla_unload(int id);
|
||||
int carla_connect(int id, const char *port_name, const char *looper_port);
|
||||
|
||||
29
client/src/plugins.c
Normal file
29
client/src/plugins.c
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <stddef.h>
|
||||
#include "plugins.h"
|
||||
#include "carla_host.h"
|
||||
|
||||
int plugin_load(const char *binary, const char *plugin_id, int *out_id)
|
||||
{
|
||||
if (!plugin_id) plugin_id = ""; // allow NULL
|
||||
return carla_load(binary, plugin_id, out_id);
|
||||
}
|
||||
|
||||
int plugin_unload(int id)
|
||||
{
|
||||
return carla_unload(id);
|
||||
}
|
||||
|
||||
int plugin_connect(int id, const char *port_name, const char *looper_port)
|
||||
{
|
||||
return carla_connect(id, port_name, looper_port);
|
||||
}
|
||||
|
||||
int plugin_disconnect(const char *from, const char *to)
|
||||
{
|
||||
return carla_disconnect(from, to);
|
||||
}
|
||||
|
||||
void plugin_set_bypass(int id, bool bypass)
|
||||
{
|
||||
carla_set_bypass(id, bypass);
|
||||
}
|
||||
22
client/src/plugins.h
Normal file
22
client/src/plugins.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef PLUGINS_H
|
||||
#define PLUGINS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* All functions return -1 on error, 0 on success (except plugin_load which returns 0 on success and sets *out_id) */
|
||||
|
||||
int plugin_load(const char *binary, const char *plugin_id, int *out_id);
|
||||
int plugin_unload(int id);
|
||||
int plugin_connect(int id, const char *port_name, const char *looper_port);
|
||||
int plugin_disconnect(const char *from, const char *to);
|
||||
void plugin_set_bypass(int id, bool bypass);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <math.h>
|
||||
#include "carla_host.h"
|
||||
|
||||
/* ---------- FIFO command helper ---------- */
|
||||
int send_command(const char *cmd) {
|
||||
@@ -151,6 +152,8 @@ void tui_init(void) {
|
||||
/* initialise cell states to idle */
|
||||
for (int i = 0; i < GRID_ROWS * GRID_COLS; i++)
|
||||
cell_state[i] = STATE_IDLE;
|
||||
/* open the JACK client used for Carla plugins */
|
||||
carla_init_jack();
|
||||
}
|
||||
|
||||
/* ---------- TUI run ---------- */
|
||||
@@ -234,5 +237,7 @@ void tui_cleanup(void) {
|
||||
/* delete FIFOs */
|
||||
unlink(STATUS_FIFO);
|
||||
unlink(CMD_FIFO);
|
||||
/* close the Carla JACK client */
|
||||
carla_cleanup_jack();
|
||||
curs_set(1); endwin();
|
||||
}
|
||||
|
||||
Binary file not shown.
70
client/tests/test_plugins.c
Normal file
70
client/tests/test_plugins.c
Normal file
@@ -0,0 +1,70 @@
|
||||
#include <stdio.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)
|
||||
|
||||
static void test_plugin_load_null_binary(void)
|
||||
{
|
||||
int id = -999;
|
||||
int ret = plugin_load(NULL, "someplugin", &id);
|
||||
ASSERT_EQ(-1, ret, "plugin_load(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)
|
||||
{
|
||||
int ret = plugin_unload(-1);
|
||||
ASSERT_EQ(-1, ret, "plugin_unload(-1) returns -1");
|
||||
}
|
||||
|
||||
static void test_plugin_connect_invalid_id(void)
|
||||
{
|
||||
int ret = plugin_connect(-1, "out", "looper:in");
|
||||
ASSERT_EQ(-1, ret, "plugin_connect(-1, ...) returns -1");
|
||||
}
|
||||
|
||||
static void test_plugin_disconnect(void)
|
||||
{
|
||||
int ret = plugin_disconnect("from_port", "to_port");
|
||||
ASSERT_EQ(0, ret, "plugin_disconnect(...) returns 0");
|
||||
}
|
||||
|
||||
static void test_plugin_set_bypass_invalid(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++;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
printf("\nResults: %d passed, %d failed\n", tests_passed, tests_failed);
|
||||
return tests_failed > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user