fuzzy sample
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
#ifndef CARLA_H
|
||||
#define CARLA_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <jack/jack.h>
|
||||
|
||||
// Carla plugin types we support
|
||||
typedef enum {
|
||||
PLUGIN_TYPE_NONE,
|
||||
PLUGIN_TYPE_LV2,
|
||||
PLUGIN_TYPE_VST2,
|
||||
PLUGIN_TYPE_VST3,
|
||||
PLUGIN_TYPE_CLAP,
|
||||
PLUGIN_TYPE_INTERNAL // Our built-in plugins (gain, etc.)
|
||||
} PluginType;
|
||||
|
||||
// Plugin descriptor
|
||||
typedef struct {
|
||||
char name[256];
|
||||
char uri[512];
|
||||
PluginType type;
|
||||
int carla_plugin_id; // Carla's internal plugin ID
|
||||
int num_parameters;
|
||||
float *parameters; // Current parameter values
|
||||
char **parameter_names;
|
||||
} PluginInfo;
|
||||
|
||||
// Rack for a single channel
|
||||
typedef struct {
|
||||
int num_plugins;
|
||||
PluginInfo plugins[16]; // Max 16 plugins per channel
|
||||
float volume; // Channel volume (0.0 - 2.0)
|
||||
bool bypassed;
|
||||
} ChannelRack;
|
||||
|
||||
// Carla host state
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
ChannelRack channel_racks[8]; // One rack per channel (MAX_CHANNELS)
|
||||
jack_client_t *client;
|
||||
// For plugin scanning
|
||||
char **available_plugins;
|
||||
int num_available_plugins;
|
||||
} CarlaHost;
|
||||
|
||||
// Initialize Carla host
|
||||
int carla_init(CarlaHost *host, jack_client_t *client);
|
||||
|
||||
// Cleanup Carla host
|
||||
void carla_cleanup(CarlaHost *host);
|
||||
|
||||
// Add a plugin to a channel's rack
|
||||
int carla_add_plugin(CarlaHost *host, int channel, const char *uri, PluginType type);
|
||||
|
||||
// Remove a plugin from a channel's rack
|
||||
int carla_remove_plugin(CarlaHost *host, int channel, int plugin_index);
|
||||
|
||||
// Set plugin parameter
|
||||
int carla_set_parameter(CarlaHost *host, int channel, int plugin_index, int param_index, float value);
|
||||
|
||||
// Get plugin parameter
|
||||
float carla_get_parameter(CarlaHost *host, int channel, int plugin_index, int param_index);
|
||||
|
||||
// Set channel volume
|
||||
void carla_set_channel_volume(CarlaHost *host, int channel, float volume);
|
||||
|
||||
// Get channel volume
|
||||
float carla_get_channel_volume(CarlaHost *host, int channel);
|
||||
|
||||
// Process audio through the rack (called from audio thread)
|
||||
void carla_process(CarlaHost *host, int channel, float *in_buffer, float *out_buffer, jack_nframes_t nframes);
|
||||
|
||||
// Scan for available plugins
|
||||
int carla_scan_plugins(CarlaHost *host);
|
||||
|
||||
// Get available plugin names for fuzzy search
|
||||
const char** carla_get_available_plugins(CarlaHost *host, int *count);
|
||||
|
||||
// Get plugin display name
|
||||
const char* carla_get_plugin_name(CarlaHost *host, int channel, int plugin_index);
|
||||
|
||||
#endif // CARLA_H
|
||||
BIN
dispatcher.o
Normal file
BIN
dispatcher.o
Normal file
Binary file not shown.
BIN
jack-looper
BIN
jack-looper
Binary file not shown.
BIN
lib/microui.o
BIN
lib/microui.o
Binary file not shown.
Binary file not shown.
381
stress_test.c
381
stress_test.c
@@ -1,381 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include "dispatcher.h"
|
||||
#include <jack/jack.h>
|
||||
|
||||
static volatile int keep_running = 1;
|
||||
|
||||
static void handle_sigint(int sig) {
|
||||
(void)sig;
|
||||
keep_running = 0;
|
||||
}
|
||||
|
||||
// Helper: create a dispatcher with initial state
|
||||
static DispatchFn setup_dispatcher(AppState *state) {
|
||||
// Initialize default state
|
||||
memset(state, 0, sizeof(AppState));
|
||||
state->transport_state = TRANSPORT_STOPPED;
|
||||
state->clock_source = CLOCK_SOURCE_INTERNAL;
|
||||
state->bpm = 120.0;
|
||||
state->samples_per_beat = (48000 * 60.0) / 120.0;
|
||||
state->quantize_mode = QUANTIZE_OFF;
|
||||
state->quantize_threshold = 0;
|
||||
for (int i = 0; i < MAX_CLIPS; i++) {
|
||||
state->clips[i].state = CLIP_EMPTY;
|
||||
state->clips[i].buffer_size = 0;
|
||||
state->clips[i].write_position = 0;
|
||||
state->clips[i].read_position = 0;
|
||||
}
|
||||
state->undo.undo_index = 0;
|
||||
state->undo.redo_index = 0;
|
||||
|
||||
DispatchFn dispatch = dispatcher_init(state);
|
||||
assert(dispatch != NULL);
|
||||
dispatcher_start();
|
||||
return dispatch;
|
||||
}
|
||||
|
||||
static void teardown_dispatcher(DispatchFn dispatch) {
|
||||
(void)dispatch;
|
||||
dispatcher_stop();
|
||||
}
|
||||
|
||||
// Helper to build an Action
|
||||
static Action make_action(ActionType type, int int_arg, jack_nframes_t nframes_arg) {
|
||||
Action action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.type = type;
|
||||
// Set the appropriate union member based on type
|
||||
switch (type) {
|
||||
case ACTION_TRIGGER_CLIP:
|
||||
action.data.trigger_clip.clip_index = int_arg;
|
||||
break;
|
||||
case ACTION_TRIGGER_SCENE:
|
||||
action.data.trigger_scene.scene_index = int_arg;
|
||||
break;
|
||||
case ACTION_RESET_CLIP:
|
||||
action.data.reset_clip.clip_index = int_arg;
|
||||
break;
|
||||
case ACTION_SET_QUANTIZE_MODE:
|
||||
action.data.set_quantize_mode.mode = (QuantizeMode)int_arg;
|
||||
break;
|
||||
case ACTION_SET_QUANTIZE_THRESHOLD:
|
||||
action.data.set_quantize_threshold.threshold = nframes_arg;
|
||||
break;
|
||||
case ACTION_SET_CLOCK_SOURCE:
|
||||
action.data.set_clock_source.source = (ClockSource)int_arg;
|
||||
break;
|
||||
case ACTION_SET_BPM:
|
||||
action.data.set_bpm.bpm = (double)int_arg;
|
||||
break;
|
||||
case ACTION_SAVE_CLIP:
|
||||
action.data.save_clip.clip_index = int_arg;
|
||||
break;
|
||||
case ACTION_LOAD_CLIP:
|
||||
action.data.load_clip.clip_index = int_arg;
|
||||
break;
|
||||
case ACTION_MIDI_NOTE_ON:
|
||||
action.data.midi_note_on.note = int_arg;
|
||||
action.data.midi_note_on.time = nframes_arg;
|
||||
break;
|
||||
case ACTION_MIDI_SCENE_LAUNCH:
|
||||
action.data.midi_scene_launch.scene_index = int_arg;
|
||||
action.data.midi_scene_launch.time = nframes_arg;
|
||||
break;
|
||||
case ACTION_PROCESS_AUDIO:
|
||||
action.data.process_audio.nframes = nframes_arg;
|
||||
break;
|
||||
default:
|
||||
// For actions with no data (TRANSPORT_PLAY, etc.), nothing to set
|
||||
break;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
// Stress test 1: Rapid clip triggers via dispatch
|
||||
static void stress_rapid_triggers(void) {
|
||||
printf("Stress test 1: Rapid clip triggers...\n");
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
int num_ops = 10000;
|
||||
for (int i = 0; i < num_ops; i++) {
|
||||
int clip_idx = i % MAX_CLIPS;
|
||||
Action action = make_action(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
||||
dispatch(action);
|
||||
}
|
||||
|
||||
// Give dispatcher time to process
|
||||
usleep(100000);
|
||||
|
||||
// Verify some clips were triggered (state changed)
|
||||
AppState current = dispatcher_get_state();
|
||||
int triggered = 0;
|
||||
for (int i = 0; i < MAX_CLIPS; i++) {
|
||||
if (current.clips[i].state != CLIP_EMPTY) triggered++;
|
||||
}
|
||||
printf(" Triggered %d clips\n", triggered);
|
||||
|
||||
teardown_dispatcher(dispatch);
|
||||
printf(" PASSED\n");
|
||||
}
|
||||
|
||||
// Stress test 2: Mix of clip and scene triggers
|
||||
static void stress_mixed_triggers(void) {
|
||||
printf("Stress test 2: Mixed clip/scene triggers...\n");
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
int num_ops = 5000;
|
||||
for (int i = 0; i < num_ops; i++) {
|
||||
if (i % 3 == 0) {
|
||||
int scene_idx = (i / 3) % MAX_SCENES;
|
||||
Action action = make_action(ACTION_TRIGGER_SCENE, scene_idx, 0);
|
||||
dispatch(action);
|
||||
} else {
|
||||
int clip_idx = i % MAX_CLIPS;
|
||||
Action action = make_action(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
||||
dispatch(action);
|
||||
}
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
|
||||
teardown_dispatcher(dispatch);
|
||||
printf(" PASSED\n");
|
||||
}
|
||||
|
||||
// Stress test 3: Queue overflow (should not crash)
|
||||
static void stress_queue_overflow(void) {
|
||||
printf("Stress test 3: Queue overflow (should drop gracefully)...\n");
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
// Submit more than queue capacity (256)
|
||||
int num_ops = 500;
|
||||
for (int i = 0; i < num_ops; i++) {
|
||||
Action action = make_action(ACTION_TRIGGER_CLIP, i % MAX_CLIPS, 0);
|
||||
dispatch(action);
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
|
||||
teardown_dispatcher(dispatch);
|
||||
printf(" PASSED (no crash)\n");
|
||||
}
|
||||
|
||||
// Stress test 4: Undo/Redo stress
|
||||
static void stress_undo_redo(void) {
|
||||
printf("Stress test 4: Undo/Redo stress...\n");
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
int num_ops = 1000;
|
||||
for (int i = 0; i < num_ops; i++) {
|
||||
int clip_idx = i % MAX_CLIPS;
|
||||
Action action = make_action(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
||||
dispatch(action);
|
||||
usleep(1000);
|
||||
|
||||
// Undo every other operation
|
||||
if (i % 2 == 0) {
|
||||
Action undo_action = make_action(ACTION_UNDO, 0, 0);
|
||||
dispatch(undo_action);
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Redo some
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Action redo_action = make_action(ACTION_REDO, 0, 0);
|
||||
dispatch(redo_action);
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
usleep(50000);
|
||||
|
||||
teardown_dispatcher(dispatch);
|
||||
printf(" PASSED\n");
|
||||
}
|
||||
|
||||
// Stress test 5: Transport state changes
|
||||
static void stress_transport(void) {
|
||||
printf("Stress test 5: Transport state changes...\n");
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
int num_ops = 5000;
|
||||
for (int i = 0; i < num_ops; i++) {
|
||||
Action action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
switch (i % 5) {
|
||||
case 0:
|
||||
action.type = ACTION_TRANSPORT_PLAY;
|
||||
break;
|
||||
case 1:
|
||||
action.type = ACTION_TRANSPORT_PAUSE;
|
||||
break;
|
||||
case 2:
|
||||
action.type = ACTION_TRANSPORT_STOP;
|
||||
break;
|
||||
case 3:
|
||||
action.type = ACTION_TRANSPORT_TOGGLE_PLAY;
|
||||
break;
|
||||
case 4:
|
||||
action.type = ACTION_SET_CLOCK_SOURCE;
|
||||
action.data.set_clock_source.source = CLOCK_SOURCE_MIDI;
|
||||
break;
|
||||
}
|
||||
dispatch(action);
|
||||
usleep(100);
|
||||
}
|
||||
|
||||
usleep(50000);
|
||||
|
||||
teardown_dispatcher(dispatch);
|
||||
printf(" PASSED\n");
|
||||
}
|
||||
|
||||
// Stress test 6: Quantize mode changes
|
||||
static void stress_quantize(void) {
|
||||
printf("Stress test 6: Quantize mode changes...\n");
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
int num_ops = 5000;
|
||||
for (int i = 0; i < num_ops; i++) {
|
||||
QuantizeMode mode = (QuantizeMode)(i % 3);
|
||||
Action mode_action = make_action(ACTION_SET_QUANTIZE_MODE, (int)mode, 0);
|
||||
dispatch(mode_action);
|
||||
Action thresh_action = make_action(ACTION_SET_QUANTIZE_THRESHOLD, 0, (jack_nframes_t)(i * 100));
|
||||
dispatch(thresh_action);
|
||||
usleep(100);
|
||||
}
|
||||
|
||||
usleep(50000);
|
||||
|
||||
teardown_dispatcher(dispatch);
|
||||
printf(" PASSED\n");
|
||||
}
|
||||
|
||||
// Stress test 7: Reset clips while triggering
|
||||
static void stress_reset_while_triggering(void) {
|
||||
printf("Stress test 7: Reset clips while triggering...\n");
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
int num_ops = 2000;
|
||||
for (int i = 0; i < num_ops; i++) {
|
||||
int clip_idx = i % MAX_CLIPS;
|
||||
Action trigger_action = make_action(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
||||
dispatch(trigger_action);
|
||||
usleep(500);
|
||||
|
||||
if (i % 10 == 0) {
|
||||
Action reset_action = make_action(ACTION_RESET_CLIP, clip_idx, 0);
|
||||
dispatch(reset_action);
|
||||
usleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
usleep(50000);
|
||||
|
||||
teardown_dispatcher(dispatch);
|
||||
printf(" PASSED\n");
|
||||
}
|
||||
|
||||
// Stress test 8: Concurrent dispatch from multiple threads
|
||||
typedef struct {
|
||||
DispatchFn dispatch;
|
||||
int thread_id;
|
||||
int num_ops;
|
||||
} ThreadArg;
|
||||
|
||||
static void* thread_worker(void *arg) {
|
||||
ThreadArg *targ = (ThreadArg *)arg;
|
||||
for (int i = 0; i < targ->num_ops; i++) {
|
||||
int clip_idx = (targ->thread_id * 1000 + i) % MAX_CLIPS;
|
||||
Action action = make_action(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
||||
targ->dispatch(action);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void stress_concurrent(void) {
|
||||
printf("Stress test 8: Concurrent dispatch from multiple threads...\n");
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
int num_threads = 4;
|
||||
int ops_per_thread = 2500;
|
||||
pthread_t threads[num_threads];
|
||||
ThreadArg args[num_threads];
|
||||
|
||||
for (int t = 0; t < num_threads; t++) {
|
||||
args[t].dispatch = dispatch;
|
||||
args[t].thread_id = t;
|
||||
args[t].num_ops = ops_per_thread;
|
||||
pthread_create(&threads[t], NULL, thread_worker, &args[t]);
|
||||
}
|
||||
|
||||
for (int t = 0; t < num_threads; t++) {
|
||||
pthread_join(threads[t], NULL);
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
|
||||
teardown_dispatcher(dispatch);
|
||||
printf(" PASSED\n");
|
||||
}
|
||||
|
||||
// Stress test 9: Repeated init/start/stop cycles
|
||||
static void stress_create_destroy(void) {
|
||||
printf("Stress test 9: Repeated init/start/stop cycles...\n");
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
AppState state;
|
||||
DispatchFn dispatch = setup_dispatcher(&state);
|
||||
|
||||
// Do some operations
|
||||
for (int j = 0; j < 10; j++) {
|
||||
Action trigger_action = make_action(ACTION_TRIGGER_CLIP, j % MAX_CLIPS, 0);
|
||||
dispatch(trigger_action);
|
||||
Action toggle_action;
|
||||
memset(&toggle_action, 0, sizeof(toggle_action));
|
||||
toggle_action.type = ACTION_TRANSPORT_TOGGLE_PLAY;
|
||||
dispatch(toggle_action);
|
||||
Action mode_action = make_action(ACTION_SET_QUANTIZE_MODE, j % 3, 0);
|
||||
dispatch(mode_action);
|
||||
}
|
||||
|
||||
usleep(1000);
|
||||
teardown_dispatcher(dispatch);
|
||||
}
|
||||
|
||||
printf(" PASSED\n");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
signal(SIGINT, handle_sigint);
|
||||
|
||||
printf("Running JACK Looper stress tests...\n\n");
|
||||
|
||||
stress_rapid_triggers();
|
||||
stress_mixed_triggers();
|
||||
stress_queue_overflow();
|
||||
stress_undo_redo();
|
||||
stress_transport();
|
||||
stress_quantize();
|
||||
stress_reset_while_triggering();
|
||||
stress_concurrent();
|
||||
stress_create_destroy();
|
||||
|
||||
printf("\nAll stress tests passed!\n");
|
||||
return 0;
|
||||
}
|
||||
BIN
test_cli.o
BIN
test_cli.o
Binary file not shown.
@@ -1,78 +0,0 @@
|
||||
// TODO: Remove this test after debugging the double status line issue
|
||||
// test_double_process.c
|
||||
#include "engine.h"
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
void test_command_processed_only_once(void) {
|
||||
printf("Test: Command processed only once...\n");
|
||||
|
||||
Engine engine;
|
||||
memset(&engine, 0, sizeof(engine));
|
||||
engine.sample_rate = 48000;
|
||||
engine.control_channel = 0;
|
||||
engine.quantize_mode = QUANTIZE_OFF;
|
||||
engine.quantize_threshold = 0;
|
||||
engine.queued_triggers = NULL;
|
||||
|
||||
// Initialize command queue
|
||||
command_queue_init(&engine.command_queue);
|
||||
|
||||
// Initialize clips
|
||||
for (int i = 0; i < MAX_CLIPS; i++) {
|
||||
engine.clips[i].state = CLIP_EMPTY;
|
||||
engine.clips[i].buffer = NULL;
|
||||
engine.clips[i].buffer_size = 0;
|
||||
engine.clips[i].write_position = 0;
|
||||
engine.clips[i].read_position = 0;
|
||||
}
|
||||
|
||||
// Submit a trigger command
|
||||
engine_trigger_clip(&engine, 0);
|
||||
|
||||
// Check queue state
|
||||
CommandQueue *q = &engine.command_queue;
|
||||
unsigned int write = atomic_load(&q->write_index);
|
||||
unsigned int read = atomic_load(&q->read_index);
|
||||
printf(" Queue before process: write=%u, read=%u, pending=%u\n",
|
||||
write, read, write - read);
|
||||
assert(write - read == 1); // One command pending
|
||||
|
||||
// Process once
|
||||
engine_process_commands(&engine);
|
||||
printf(" After first process: state=%d (expected %d=CLIP_RECORDING)\n",
|
||||
engine.clips[0].state, CLIP_RECORDING);
|
||||
assert(engine.clips[0].state == CLIP_RECORDING);
|
||||
|
||||
// Check queue state after first process
|
||||
write = atomic_load(&q->write_index);
|
||||
read = atomic_load(&q->read_index);
|
||||
printf(" Queue after first process: write=%u, read=%u, pending=%u\n",
|
||||
write, read, write - read);
|
||||
assert(write - read == 0); // No commands pending
|
||||
|
||||
// Process again (simulating audio thread calling it)
|
||||
engine_process_commands(&engine);
|
||||
printf(" After second process: state=%d (expected %d=CLIP_RECORDING)\n",
|
||||
engine.clips[0].state, CLIP_RECORDING);
|
||||
|
||||
// If state changed, the command was processed twice
|
||||
if (engine.clips[0].state != CLIP_RECORDING) {
|
||||
printf(" BUG: State changed to %d on second process!\n", engine.clips[0].state);
|
||||
} else {
|
||||
printf(" OK: State unchanged after second process\n");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
for (int i = 0; i < MAX_CLIPS; i++) {
|
||||
free(engine.clips[i].buffer);
|
||||
}
|
||||
|
||||
printf("PASSED\n");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_command_processed_only_once();
|
||||
return 0;
|
||||
}
|
||||
BIN
test_engine
BIN
test_engine
Binary file not shown.
BIN
test_engine.o
BIN
test_engine.o
Binary file not shown.
BIN
test_gui.o
BIN
test_gui.o
Binary file not shown.
BIN
test_stress
BIN
test_stress
Binary file not shown.
BIN
test_stress.o
BIN
test_stress.o
Binary file not shown.
BIN
test_tui.o
BIN
test_tui.o
Binary file not shown.
BIN
test_wav_io
BIN
test_wav_io
Binary file not shown.
BIN
test_wav_io.o
BIN
test_wav_io.o
Binary file not shown.
BIN
transport.o
BIN
transport.o
Binary file not shown.
Reference in New Issue
Block a user