#include #include #include #include #include #include #include #include #include "dispatcher.h" #include "transport.h" #include 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; }