#include #include #include #include #include #include #include #include "engine.h" static volatile int keep_running = 1; static void handle_sigint(int sig) { (void)sig; keep_running = 0; } static Engine *create_stress_engine(void) { Engine *engine = (Engine *)calloc(1, sizeof(Engine)); assert(engine != NULL); engine->control_channel = 0; engine->sample_rate = 48000; engine->quantize_mode = QUANTIZE_OFF; engine->quantize_threshold = 0; engine->queued_triggers = NULL; command_queue_init(&engine->command_queue); atomic_store(&engine->quantize_mode_atomic, (int)QUANTIZE_OFF); atomic_store(&engine->quantize_threshold_atomic, 0); engine->transport = (Transport *)calloc(1, sizeof(Transport)); assert(engine->transport != NULL); transport_init(engine->transport, 48000); for (int i = 0; i < MAX_CLIPS; i++) { engine->clips[i].state = CLIP_EMPTY; engine->clips[i].buffer = (float *)calloc(MAX_BUFFER_SIZE, sizeof(float)); assert(engine->clips[i].buffer != NULL); engine->clips[i].buffer_size = 0; engine->clips[i].write_position = 0; engine->clips[i].read_position = 0; } return engine; } static void destroy_stress_engine(Engine *engine) { if (engine) { engine->queued_triggers = NULL; // pool, no free if (engine->transport) { transport_cleanup(engine->transport); free(engine->transport); } for (int i = 0; i < MAX_CLIPS; i++) { free(engine->clips[i].buffer); } free(engine); } } // Stress test 1: Rapid clip triggers via command queue static void stress_command_queue(void) { printf("Stress test 1: Rapid command queue submission...\n"); Engine *engine = create_stress_engine(); int num_ops = 10000; int success = 0; int dropped = 0; for (int i = 0; i < num_ops; i++) { int clip_idx = i % MAX_CLIPS; int ret = engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); if (ret == 0) { success++; } else { dropped++; } } // At least some commands should have been accepted assert(success > 0); printf(" Submitted %d commands, %d succeeded, %d dropped\n", num_ops, success, dropped); // Process all commands engine_process_commands(engine); // Verify no commands left CommandQueue *q = &engine->command_queue; unsigned int write = atomic_load(&q->write_index); unsigned int read = atomic_load(&q->read_index); assert(write == read); destroy_stress_engine(engine); 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"); Engine *engine = create_stress_engine(); int num_ops = 5000; int success = 0; int dropped = 0; for (int i = 0; i < num_ops; i++) { int ret; if (i % 3 == 0) { int scene_idx = (i / 3) % MAX_SCENES; ret = engine_submit_command(engine, CMD_TRIGGER_SCENE, scene_idx, 0); } else { int clip_idx = i % MAX_CLIPS; ret = engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); } if (ret == 0) { success++; } else { dropped++; } } assert(success > 0); printf(" Submitted %d commands, %d succeeded, %d dropped\n", num_ops, success, dropped); engine_process_commands(engine); // Verify queue drained CommandQueue *q = &engine->command_queue; assert(atomic_load(&q->write_index) == atomic_load(&q->read_index)); destroy_stress_engine(engine); 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"); Engine *engine = create_stress_engine(); // Submit more than MAX_QUEUED_COMMANDS int num_ops = MAX_QUEUED_COMMANDS * 2; int dropped = 0; for (int i = 0; i < num_ops; i++) { int ret = engine_submit_command(engine, CMD_TRIGGER_CLIP, i % MAX_CLIPS, 0); if (ret != 0) dropped++; } assert(dropped > 0); // some should have been dropped printf(" Dropped %d commands (expected)\n", dropped); // Process what we can engine_process_commands(engine); destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 4: Trigger pool exhaustion (simulate many MIDI notes) static void stress_trigger_pool(void) { printf("Stress test 4: Trigger pool exhaustion...\n"); Engine *engine = create_stress_engine(); // Set transport playing so queue_trigger is used engine->transport->state = TRANSPORT_PLAYING; atomic_store(&engine->transport->state_atomic, TRANSPORT_PLAYING); engine->quantize_mode = QUANTIZE_BEAT; atomic_store(&engine->quantize_mode_atomic, (int)QUANTIZE_BEAT); // Queue many triggers (current implementation uses malloc, no pool limit) int num_triggers = 200; for (int i = 0; i < num_triggers; i++) { queue_trigger(engine, i % MAX_CLIPS, false, 100); } // Verify that some triggers were queued (no crash) int count = 0; QueuedTrigger *qt = engine->queued_triggers; while (qt) { count++; qt = qt->next; } assert(count > 0); printf(" Queued %d triggers\n", count); // Clean up (free the malloc'd nodes) qt = engine->queued_triggers; while (qt) { QueuedTrigger *next = qt->next; free(qt); qt = next; } engine->queued_triggers = NULL; destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 5: Undo/Redo stress static void stress_undo_redo(void) { printf("Stress test 5: Undo/Redo stress...\n"); Engine *engine = create_stress_engine(); int num_ops = 1000; int success = 0; int dropped = 0; for (int i = 0; i < num_ops; i++) { int clip_idx = i % MAX_CLIPS; int ret = engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); if (ret == 0) success++; else dropped++; engine_process_commands(engine); // Undo every other operation if (i % 2 == 0) { ret = engine_submit_command(engine, CMD_UNDO, 0, 0); if (ret == 0) success++; else dropped++; engine_process_commands(engine); } } assert(success > 0); printf(" Submitted %d commands, %d succeeded, %d dropped\n", num_ops * 2, success, dropped); // Redo some for (int i = 0; i < 100; i++) { engine_submit_command(engine, CMD_REDO, 0, 0); engine_process_commands(engine); } destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 6: Transport state changes static void stress_transport(void) { printf("Stress test 6: Transport state changes...\n"); Engine *engine = create_stress_engine(); int num_ops = 5000; for (int i = 0; i < num_ops; i++) { switch (i % 5) { case 0: engine_submit_command(engine, CMD_TRANSPORT_PLAY, 0, 0); break; case 1: engine_submit_command(engine, CMD_TRANSPORT_PAUSE, 0, 0); break; case 2: engine_submit_command(engine, CMD_TRANSPORT_STOP, 0, 0); break; case 3: engine_submit_command(engine, CMD_TRANSPORT_TOGGLE_PLAY, 0, 0); break; case 4: engine_submit_command(engine, CMD_SET_CLOCK_SOURCE, (int)CLOCK_SOURCE_MIDI, 0); break; } engine_process_commands(engine); } destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 7: Quantize mode changes static void stress_quantize(void) { printf("Stress test 7: Quantize mode changes...\n"); Engine *engine = create_stress_engine(); int num_ops = 5000; for (int i = 0; i < num_ops; i++) { QuantizeMode mode = (QuantizeMode)(i % 3); engine_submit_command(engine, CMD_SET_QUANTIZE_MODE, (int)mode, 0); engine_submit_command(engine, CMD_SET_QUANTIZE_THRESHOLD, 0, (jack_nframes_t)(i * 100)); engine_process_commands(engine); } destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 8: Reset clips while triggering static void stress_reset_while_triggering(void) { printf("Stress test 8: Reset clips while triggering...\n"); Engine *engine = create_stress_engine(); int num_ops = 2000; for (int i = 0; i < num_ops; i++) { int clip_idx = i % MAX_CLIPS; engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); engine_process_commands(engine); if (i % 10 == 0) { engine_submit_command(engine, CMD_RESET_CLIP, clip_idx, 0); engine_process_commands(engine); } } destroy_stress_engine(engine); printf(" PASSED\n"); } int main(void) { signal(SIGINT, handle_sigint); printf("Running JACK Looper stress tests...\n\n"); stress_command_queue(); stress_mixed_triggers(); stress_queue_overflow(); stress_trigger_pool(); stress_undo_redo(); stress_transport(); stress_quantize(); stress_reset_while_triggering(); printf("\nAll stress tests passed!\n"); return 0; }