4 Commits

Author SHA1 Message Date
Loic Coenen
7e9224cdc7 fix: replace usleep with nanosleep and fix const correctness
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 12:11:57 +00:00
Loic Coenen
1db9735e1b style: reformat code and reorder includes in looper.c and main.c 2026-05-09 12:11:55 +00:00
Loic Coenen
e8d679c1af fix: replace usleep with safe_usleep in integration tests
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 11:45:32 +00:00
Loic Coenen
e7761c4b53 fix: replace usleep with nanosleep and fix const correctness in tests
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 11:44:06 +00:00
3 changed files with 253 additions and 241 deletions

View File

@@ -1,13 +1,13 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#include <stdatomic.h>
#include <math.h>
#include "looper.h" #include "looper.h"
#include "channel.h" #include "channel.h"
#include "midi.h" #include "midi.h"
#include <jack/jack.h>
#include <jack/midiport.h>
#include <math.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Global state (shared across files) */ /* Global state (shared across files) */
struct channel_t channels[MAX_CHANNELS]; struct channel_t channels[MAX_CHANNELS];
@@ -26,8 +26,7 @@ static int pending_unregister_idx = -1;
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* process callback * process callback
* ---------------------------------------------------------------- */ * ---------------------------------------------------------------- */
int process_callback(jack_nframes_t nframes, void *arg) int process_callback(jack_nframes_t nframes, void *arg) {
{
(void)arg; (void)arg;
if (midi_control_port) { if (midi_control_port) {
@@ -39,7 +38,8 @@ int process_callback(jack_nframes_t nframes, void *arg)
/* process each active channel */ /* process each active channel */
for (int c = 0; c < MAX_CHANNELS; c++) { for (int c = 0; c < MAX_CHANNELS; c++) {
if (!atomic_load(&channels[c].active)) continue; if (!atomic_load(&channels[c].active))
continue;
/* Guard against NULL ports (e.g. if port registration failed) */ /* Guard against NULL ports (e.g. if port registration failed) */
if (!channels[c].audio_in || !channels[c].audio_out) { if (!channels[c].audio_in || !channels[c].audio_out) {
@@ -47,11 +47,14 @@ int process_callback(jack_nframes_t nframes, void *arg)
continue; continue;
} }
jack_default_audio_sample_t *in = (jack_default_audio_sample_t *) const jack_default_audio_sample_t *in =
jack_port_get_buffer(channels[c].audio_in, nframes); (const jack_default_audio_sample_t *)jack_port_get_buffer(
jack_default_audio_sample_t *out = (jack_default_audio_sample_t *) channels[c].audio_in, nframes);
jack_port_get_buffer(channels[c].audio_out, nframes); jack_default_audio_sample_t *out =
if (!out) continue; (jack_default_audio_sample_t *)jack_port_get_buffer(
channels[c].audio_out, nframes);
if (!out)
continue;
int state = atomic_load(&channels[c].state); int state = atomic_load(&channels[c].state);
@@ -77,7 +80,9 @@ int process_callback(jack_nframes_t nframes, void *arg)
if (in) { if (in) {
for (i = 0; i < nframes; i++) { for (i = 0; i < nframes; i++) {
if (channels[c].record_pos < LOOP_BUF_SIZE) if (channels[c].record_pos < LOOP_BUF_SIZE)
channels[c].loop_buffer[channels[c].record_pos++] = ((const float *)in)[i]; channels[c].loop_buffer[channels[c].record_pos++] =
((const float *)in)[i];
// cppcheck-suppress unreadVariable
((float *)out)[i] = ((const float *)in)[i]; ((float *)out)[i] = ((const float *)in)[i];
} }
} else { } else {
@@ -90,7 +95,8 @@ int process_callback(jack_nframes_t nframes, void *arg)
float *outf = (float *)out; float *outf = (float *)out;
for (i = 0; i < nframes; i++) { for (i = 0; i < nframes; i++) {
outf[i] = channels[c].loop_buffer[channels[c].playback_pos]; outf[i] = channels[c].loop_buffer[channels[c].playback_pos];
channels[c].playback_pos = (channels[c].playback_pos + 1) % channels[c].loop_count; channels[c].playback_pos =
(channels[c].playback_pos + 1) % channels[c].loop_count;
} }
} else { } else {
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
@@ -120,13 +126,15 @@ int process_callback(jack_nframes_t nframes, void *arg)
jack_nframes_t n_clock_events = jack_midi_get_event_count(midi_clock_buf); jack_nframes_t n_clock_events = jack_midi_get_event_count(midi_clock_buf);
jack_midi_event_t cev; jack_midi_event_t cev;
for (jack_nframes_t j = 0; j < n_clock_events; j++) { for (jack_nframes_t j = 0; j < n_clock_events; j++) {
if (jack_midi_event_get(&cev, midi_clock_buf, j) != 0) continue; if (jack_midi_event_get(&cev, midi_clock_buf, j) != 0)
continue;
if (cev.size >= 1) { if (cev.size >= 1) {
unsigned char msg = cev.buffer[0]; unsigned char msg = cev.buffer[0];
switch (msg) { switch (msg) {
case 0xFA: { case 0xFA: {
int s = atomic_load(&channels[0].state); int s = atomic_load(&channels[0].state);
if (s == STATE_IDLE) atomic_store(&channels[0].state, STATE_RECORD); if (s == STATE_IDLE)
atomic_store(&channels[0].state, STATE_RECORD);
break; break;
} }
case 0xFC: case 0xFC:
@@ -134,7 +142,8 @@ int process_callback(jack_nframes_t nframes, void *arg)
break; break;
case 0xFB: { case 0xFB: {
int s = atomic_load(&channels[0].state); int s = atomic_load(&channels[0].state);
if (s == STATE_PAUSED) atomic_store(&channels[0].state, STATE_LOOPING); if (s == STATE_PAUSED)
atomic_store(&channels[0].state, STATE_LOOPING);
break; break;
} }
default: default:
@@ -151,8 +160,7 @@ int process_callback(jack_nframes_t nframes, void *arg)
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* shutdown callback * shutdown callback
* ---------------------------------------------------------------- */ * ---------------------------------------------------------------- */
void jack_shutdown_cb(void *arg) void jack_shutdown_cb(void *arg) {
{
(void)arg; (void)arg;
fprintf(stderr, "JACK shutdown\n"); fprintf(stderr, "JACK shutdown\n");
exit(0); exit(0);
@@ -161,8 +169,7 @@ void jack_shutdown_cb(void *arg)
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* looper initialisation * looper initialisation
* ---------------------------------------------------------------- */ * ---------------------------------------------------------------- */
int looper_init(jack_client_t *client) int looper_init(jack_client_t *client) {
{
/* channel 0 */ /* channel 0 */
channels[0].active = 1; channels[0].active = 1;
atomic_store(&channels[0].state, STATE_IDLE); atomic_store(&channels[0].state, STATE_IDLE);
@@ -171,23 +178,19 @@ int looper_init(jack_client_t *client)
channels[0].record_pos = 0; channels[0].record_pos = 0;
channels[0].playback_pos = 0; channels[0].playback_pos = 0;
channels[0].audio_in = jack_port_register(client, "input", channels[0].audio_in = jack_port_register(
JACK_DEFAULT_AUDIO_TYPE, client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
JackPortIsInput, 0); channels[0].audio_out = jack_port_register(
channels[0].audio_out = jack_port_register(client, "output", client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if (!channels[0].audio_in || !channels[0].audio_out) { if (!channels[0].audio_in || !channels[0].audio_out) {
fprintf(stderr, "Could not create audio ports for channel 0\n"); fprintf(stderr, "Could not create audio ports for channel 0\n");
return -1; return -1;
} }
channel_count = 1; channel_count = 1;
midi_control_port = jack_port_register(client, "control", midi_control_port = jack_port_register(
JACK_DEFAULT_MIDI_TYPE, client, "control", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
JackPortIsInput, 0); midi_clock_port = jack_port_register(client, "clock", JACK_DEFAULT_MIDI_TYPE,
midi_clock_port = jack_port_register(client, "clock",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsInput, 0); JackPortIsInput, 0);
if (!midi_control_port || !midi_clock_port) { if (!midi_control_port || !midi_clock_port) {
fprintf(stderr, "Could not create MIDI ports\n"); fprintf(stderr, "Could not create MIDI ports\n");
@@ -200,8 +203,7 @@ int looper_init(jack_client_t *client)
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* mainloop command processing * mainloop command processing
* ---------------------------------------------------------------- */ * ---------------------------------------------------------------- */
void looper_process_commands(jack_client_t *client) void looper_process_commands(jack_client_t *client) {
{
/* Unregister any ports that were marked for deferred removal. /* Unregister any ports that were marked for deferred removal.
By now the realtime thread has had at least one full cycle By now the realtime thread has had at least one full cycle
to see the `active = 0` store. */ to see the `active = 0` store. */
@@ -217,7 +219,8 @@ void looper_process_commands(jack_client_t *client)
if (atomic_exchange(&cmd_add, 0)) { if (atomic_exchange(&cmd_add, 0)) {
int idx; int idx;
for (idx = 0; idx < MAX_CHANNELS; idx++) for (idx = 0; idx < MAX_CHANNELS; idx++)
if (!channels[idx].active) break; if (!channels[idx].active)
break;
if (idx < MAX_CHANNELS) { if (idx < MAX_CHANNELS) {
channel_add(client, idx); channel_add(client, idx);
} }
@@ -226,7 +229,8 @@ void looper_process_commands(jack_client_t *client)
if (atomic_exchange(&cmd_remove, 0)) { if (atomic_exchange(&cmd_remove, 0)) {
int remove_idx = -1; int remove_idx = -1;
for (int idx = 1; idx < MAX_CHANNELS; idx++) for (int idx = 1; idx < MAX_CHANNELS; idx++)
if (channels[idx].active) remove_idx = idx; if (channels[idx].active)
remove_idx = idx;
if (remove_idx != -1) { if (remove_idx != -1) {
/* Mark inactive now; ports will be unregistered next round */ /* Mark inactive now; ports will be unregistered next round */
channel_remove(client, remove_idx); channel_remove(client, remove_idx);

View File

@@ -1,11 +1,11 @@
#include "looper.h"
#include <jack/jack.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <jack/jack.h> #include <time.h>
#include "looper.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{
(void)argc; (void)argc;
(void)argv; (void)argv;
const char *client_name = "looper"; const char *client_name = "looper";
@@ -42,7 +42,7 @@ int main(int argc, char *argv[])
while (1) { while (1) {
looper_process_commands(client); looper_process_commands(client);
usleep(50000); /* check commands every 50 ms */ { struct timespec ts = { .tv_sec = 0, .tv_nsec = 50000000 }; nanosleep(&ts, NULL); } /* check commands every 50 ms */
} }
jack_client_close(client); jack_client_close(client);

View File

@@ -10,6 +10,7 @@
#include <jack/jack.h> #include <jack/jack.h>
#include <jack/midiport.h> #include <jack/midiport.h>
#include <math.h> #include <math.h>
#include <time.h>
/* static variables for passthrough test */ /* static variables for passthrough test */
static jack_port_t *passthrough_output_port = NULL; static jack_port_t *passthrough_output_port = NULL;
@@ -32,6 +33,13 @@ static jack_client_t *midi_inject_client = NULL;
static unsigned char midi_inject_note = 0; static unsigned char midi_inject_note = 0;
static unsigned char midi_inject_velocity = 0; static unsigned char midi_inject_velocity = 0;
static void safe_usleep(unsigned int usec) {
struct timespec ts;
ts.tv_sec = usec / 1000000;
ts.tv_nsec = (usec % 1000000) * 1000L;
nanosleep(&ts, NULL);
}
static int midi_inject_process(jack_nframes_t nframes, void *arg) { static int midi_inject_process(jack_nframes_t nframes, void *arg) {
(void)arg; (void)arg;
if (!midi_inject_port) return 0; if (!midi_inject_port) return 0;
@@ -57,8 +65,8 @@ static int passthrough_process(jack_nframes_t nframes, void *arg) {
(void)arg; (void)arg;
jack_default_audio_sample_t *out = jack_default_audio_sample_t *out =
(jack_default_audio_sample_t *)jack_port_get_buffer(passthrough_output_port, nframes); (jack_default_audio_sample_t *)jack_port_get_buffer(passthrough_output_port, nframes);
jack_default_audio_sample_t *in = const jack_default_audio_sample_t *in =
(jack_default_audio_sample_t *)jack_port_get_buffer(passthrough_input_port, nframes); (const jack_default_audio_sample_t *)jack_port_get_buffer(passthrough_input_port, nframes);
if (!out || !in) return 0; if (!out || !in) return 0;
float *outf = out; float *outf = out;
const float *inf = in; const float *inf = in;
@@ -195,7 +203,7 @@ static int test_audio_pass_through(void) {
waitpid(pid, NULL, 0); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(2200000); /* 2.2 seconds */ safe_usleep(2200000);
int saw_input = passthrough_done; int saw_input = passthrough_done;
double rms = passthrough_total_samples > 0 ? double rms = passthrough_total_samples > 0 ?
sqrt(passthrough_sum_sq / passthrough_total_samples) : 0.0; sqrt(passthrough_sum_sq / passthrough_total_samples) : 0.0;
@@ -313,7 +321,7 @@ static int test_looper_looping(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(500000); /* allow state to change (500ms) */ safe_usleep(500000); /* allow state to change (500ms) */
int sr = jack_get_sample_rate(client); int sr = jack_get_sample_rate(client);
continuous_sine = 0; /* disable continuous tone */ continuous_sine = 0; /* disable continuous tone */
@@ -337,10 +345,10 @@ static int test_looper_looping(void) {
return 1; return 1;
} }
usleep(150000); /* let beep start */ safe_usleep(150000); /* let beep start */
/* ensure beep is fully captured */ /* ensure beep is fully captured */
usleep(800000); /* 0.8s after start of beep */ safe_usleep(800000); /* 0.8s after start of beep */
if (send_jack_note_on("looper:control", 1, 127) != 0) { if (send_jack_note_on("looper:control", 1, 127) != 0) {
jack_client_close(client); jack_client_close(client);
@@ -349,7 +357,7 @@ static int test_looper_looping(void) {
} }
/* wait enough time for several loops (4 seconds to be safe) */ /* wait enough time for several loops (4 seconds to be safe) */
usleep(4000000); safe_usleep(4000000);
jack_deactivate(client); jack_deactivate(client);
jack_client_close(client); jack_client_close(client);
@@ -390,7 +398,7 @@ static int test_multiple_channels(void) {
} }
/* wait long enough for the looper's main loop to process the add command /* wait long enough for the looper's main loop to process the add command
(it sleeps for 1 second between checks, so 1.5 s is safe) */ (it sleeps for 1 second between checks, so 1.5 s is safe) */
usleep(1500000); safe_usleep(1500000);
int found = 0; int found = 0;
const char **ports = jack_get_ports(client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0); const char **ports = jack_get_ports(client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0);
@@ -500,7 +508,7 @@ static int test_control_key_modifier(void) {
fprintf(stderr, " FAIL: send note 62 for loop\n"); fprintf(stderr, " FAIL: send note 62 for loop\n");
return 1; return 1;
} }
usleep(2000000); /* wait for a few loops */ safe_usleep(2000000);
jack_deactivate(client); jack_deactivate(client);
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); kill(pid, SIGTERM);
@@ -613,7 +621,7 @@ static int test_bind_channel(void) {
fprintf(stderr, " FAIL: toggle for loop\n"); fprintf(stderr, " FAIL: toggle for loop\n");
return 1; return 1;
} }
usleep(2000000); safe_usleep(2000000);
jack_deactivate(client); jack_deactivate(client);
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); kill(pid, SIGTERM);
@@ -741,7 +749,7 @@ static int test_bind_unbind(void) {
fprintf(stderr, " FAIL: toggle for loop\n"); fprintf(stderr, " FAIL: toggle for loop\n");
return 1; return 1;
} }
usleep(2000000); safe_usleep(2000000);
jack_deactivate(client); jack_deactivate(client);
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); kill(pid, SIGTERM);
@@ -776,7 +784,7 @@ static int test_remove_channel(void) {
fprintf(stderr, " FAIL: send note 60 failed\n"); fprintf(stderr, " FAIL: send note 60 failed\n");
return 1; return 1;
} }
usleep(1500000); safe_usleep(1500000);
/* verify channel1_input exists */ /* verify channel1_input exists */
const char **ports = jack_get_ports(client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0); const char **ports = jack_get_ports(client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0);
int found = 0; int found = 0;
@@ -803,7 +811,7 @@ static int test_remove_channel(void) {
fprintf(stderr, " FAIL: send note 61 failed\n"); fprintf(stderr, " FAIL: send note 61 failed\n");
return 1; return 1;
} }
usleep(1500000); safe_usleep(1500000);
/* verify channel1_input has disappeared */ /* verify channel1_input has disappeared */
ports = jack_get_ports(client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0); ports = jack_get_ports(client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0);
int still_found = 0; int still_found = 0;