diff --git a/tests/integration.c b/tests/integration.c index 1d4eea2..948abb7 100644 --- a/tests/integration.c +++ b/tests/integration.c @@ -835,6 +835,244 @@ static int test_remove_channel(void) { return 0; } +/* ------------------------------------------------------------ + * Helper: generate a simple 440 Hz WAV file for load tests + * ------------------------------------------------------------ */ +static int generate_test_wav(const char *path, unsigned sample_rate, unsigned duration_frames) { + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return -1; + unsigned data_bytes = duration_frames * 2; + unsigned file_size = 44 + data_bytes; + unsigned char header[44]; + memset(header, 0, 44); + memcpy(header, "RIFF", 4); + header[4] = file_size & 0xff; header[5] = (file_size>>8)&0xff; + header[6] = (file_size>>16)&0xff; header[7] = (file_size>>24)&0xff; + memcpy(header+8, "WAVE", 4); + memcpy(header+12, "fmt ", 4); + header[16]=16; header[17]=0; header[18]=0; header[19]=0; + header[20]=1; header[21]=0; /* PCM */ + header[22]=1; header[23]=0; /* mono */ + header[24]= sample_rate & 0xff; header[25]=(sample_rate>>8)&0xff; + header[26]=(sample_rate>>16)&0xff; header[27]=(sample_rate>>24)&0xff; + unsigned br = sample_rate * 2; + header[28]= br & 0xff; header[29]=(br>>8)&0xff; + header[30]=(br>>16)&0xff; header[31]=(br>>24)&0xff; + header[32]=2; header[33]=0; + header[34]=16; header[35]=0; + memcpy(header+36, "data", 4); + header[40]= data_bytes & 0xff; header[41]=(data_bytes>>8)&0xff; + header[42]=(data_bytes>>16)&0xff; header[43]=(data_bytes>>24)&0xff; + if (write(fd, header, 44) != 44) { close(fd); return -1; } + for (unsigned i = 0; i < duration_frames; i++) { + float sample = sinf(2.0f * (float)M_PI * 440.0f * i / sample_rate); + int16_t s = (int16_t)(sample * 32767); + if (write(fd, &s, 2) != 2) { close(fd); return -1; } + } + close(fd); + return 0; +} + +/* ------------------------------------------------------------ + * Test: load WAV file (note 70 under control key) + * ------------------------------------------------------------ */ +static int test_wav_load(void) { + printf("Test: load WAV file into channel 0 and detect playback\n"); + if (generate_test_wav("loop.wav", 48000, 48000) != 0) { + fprintf(stderr, " FAIL: could not create test WAV\n"); + return 1; + } + pid_t pid = start_looper(); + if (pid < 0) { unlink("loop.wav"); return 1; } + jack_client_t *client; + jack_status_t status; + client = jack_client_open("test_wav_load", JackNoStartServer, &status); + if (!client) { + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + unlink("loop.wav"); + fprintf(stderr, " SKIP: no JACK\n"); + return 1; + } + jack_port_t *audio_out = jack_port_register(client, "out", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + jack_port_t *audio_in = jack_port_register(client, "in", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + if (!audio_out || !audio_in) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + unlink("loop.wav"); + return 1; + } + safe_usleep(200000); + if (jack_connect(client, "test_wav_load:out", "looper:input") || + jack_connect(client, "looper:output", "test_wav_load:in")) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + unlink("loop.wav"); + return 1; + } + /* send control key + note 70 to trigger load */ + if (send_jack_note_on("looper:control", 64, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + unlink("loop.wav"); return 1; + } + safe_usleep(200000); + if (send_jack_note_on("looper:control", 70, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + unlink("loop.wav"); return 1; + } + safe_usleep(500000); /* give time for load to complete */ + /* listen for the loop */ + int sr = jack_get_sample_rate(client); + continuous_sine = 0; + beep_remaining = 0; + bursts = 0; + prev_above = 0; + passthrough_output_port = audio_out; + passthrough_input_port = audio_in; + passthrough_phase = 0.0f; + passthrough_freq = 440.0f; + passthrough_sample_rate = sr; + passthrough_total_samples = 0; + passthrough_sum_sq = 0.0; + passthrough_done = 0; + jack_set_process_callback(client, passthrough_process, NULL); + if (jack_activate(client)) { + jack_deactivate(client); + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + unlink("loop.wav"); + return 1; + } + safe_usleep(3000000); + jack_deactivate(client); + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + unlink("loop.wav"); + int got_bursts = bursts; + printf(" detected bursts: %d\n", got_bursts); + if (got_bursts < 3) { + fprintf(stderr, " FAIL: expected ≥3 bursts from loaded loop, got %d\n", got_bursts); + return 1; + } + printf(" PASS (loaded loop plays)\n"); + return 0; +} + +/* ------------------------------------------------------------ + * Test: save WAV file (note 71 under control key) + * ------------------------------------------------------------ */ +static int test_wav_save(void) { + printf("Test: save WAV file from loop\n"); + pid_t pid = start_looper(); + if (pid < 0) return 1; + jack_client_t *client; + jack_status_t status; + client = jack_client_open("test_wav_save", JackNoStartServer, &status); + if (!client) { + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " SKIP: no JACK\n"); + return 1; + } + jack_port_t *audio_out = jack_port_register(client, "out", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + jack_port_t *audio_in = jack_port_register(client, "in", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + if (!audio_out || !audio_in) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + safe_usleep(200000); + if (jack_connect(client, "test_wav_save:out", "looper:input") || + jack_connect(client, "looper:output", "test_wav_save:in")) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + /* record a beep: send note 1 (toggle channel 0) */ + if (send_jack_note_on("looper:control", 1, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + safe_usleep(200000); + /* start generating a beep */ + int sr = jack_get_sample_rate(client); + continuous_sine = 0; + beep_remaining = (int)(0.5f * sr); + bursts = 0; prev_above = 0; + passthrough_output_port = audio_out; + passthrough_input_port = audio_in; + passthrough_phase = 0.0f; + passthrough_freq = 440.0f; + passthrough_sample_rate = sr; + passthrough_total_samples = 0; + passthrough_sum_sq = 0.0; + passthrough_done = 0; + jack_set_process_callback(client, passthrough_process, NULL); + if (jack_activate(client)) { + jack_deactivate(client); + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + safe_usleep(800000); + /* toggle again to stop recording and start looping */ + if (send_jack_note_on("looper:control", 1, 127) != 0) { + jack_deactivate(client); + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + safe_usleep(500000); + /* send control key + note 71 to save */ + if (send_jack_note_on("looper:control", 64, 127) != 0) { + jack_deactivate(client); + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + safe_usleep(200000); + if (send_jack_note_on("looper:control", 71, 127) != 0) { + jack_deactivate(client); + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + safe_usleep(2000000); + /* check save.wav exists and has data */ + int fd = open("save.wav", O_RDONLY); + if (fd < 0) { + jack_deactivate(client); + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: save.wav not created\n"); + return 1; + } + unsigned char hdr[44]; + if (read(fd, hdr, 44) != 44) { + close(fd); unlink("save.wav"); + jack_deactivate(client); jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: short header\n"); + return 1; + } + unsigned data_bytes = hdr[40] | (hdr[41]<<8) | (hdr[42]<<16) | (hdr[43]<<24); + close(fd); + if (data_bytes == 0) { + unlink("save.wav"); + jack_deactivate(client); jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: empty save.wav\n"); + return 1; + } + printf(" save.wav data size: %u bytes\n", data_bytes); + unlink("save.wav"); + jack_deactivate(client); + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + printf(" PASS (save.wav created)\n"); + return 0; +} int main(void) { /* 1. binary must exist */ @@ -886,6 +1124,18 @@ int main(void) { failures++; } + /* 10. Test WAV load */ + if (test_wav_load() != 0) { + fprintf(stderr, " FAILED\n"); + failures++; + } + + /* 11. Test WAV save */ + if (test_wav_save() != 0) { + fprintf(stderr, " FAILED\n"); + failures++; + } + if (failures > 0) { fprintf(stderr, "%d test(s) FAILED\n", failures); return 1;