14 Commits

Author SHA1 Message Date
2b4531f3f3 Merge pull request '1-multichannel' (#1) from 1-multichannel into multichannel
Reviewed-on: #1
2026-05-09 15:47:08 -04:00
Loic Coenen
b4a65a5788 chore: add git hooks 2026-05-09 14:47:04 +00:00
Loic Coenen
20c0820910 refactor: use explicit pointer casts to clarify type conversions
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 14:46:09 +00:00
Loic Coenen
934843e9b1 fix: move cppcheck suppression comment to same line as assignment
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 13:23:32 +00:00
Loic Coenen
2fa1d159e7 refactor: remove global cppcheck suppressions and move usleep suppression locally
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 13:20:37 +00:00
Loic Coenen
982dd4dc02 refactor: replace usleep with safe_usleep in integration tests 2026-05-09 13:20:36 +00:00
Loic Coenen
b02c45e7ab fix: suppress cppcheck warnings for missing includes and usleep
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 13:19:08 +00:00
Loic Coenen
aa0b191b46 makefile
```makefile
<<<<<<< SEARCH
check:
	cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem --suppress=usleepCalled --suppress=unreadVariable --suppress=normalCheckLevelMaxBranches src/*.c --library=posix .
=======
check:
	cppcheck --enable=all --error-exitcode=1 --suppress=unreadVariable src/*.c --library=posix .
>>>>>>> REPLACE
```

src/channel.c
```c
<<<<<<< SEARCH
#include "channel.h"
#include <jack/jack.h>
#include <stdatomic.h>
#include <stdio.h>
#include <string.h>
=======
// cppcheck-suppress missingIncludeSystem
#include "channel.h"
#include <jack/jack.h>
#include <stdatomic.h>
#include <stdio.h>
#include <string.h>
>>>>>>> REPLACE
```

src/channel.h
```c
<<<<<<< SEARCH
#include <jack/jack.h>
#include <stdatomic.h>
=======
// cppcheck-suppress missingIncludeSystem
#include <jack/jack.h>
#include <stdatomic.h>
>>>>>>> REPLACE
```

src/looper.c
```c
<<<<<<< SEARCH
#include "looper.h"
#include "channel.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>
=======
// cppcheck-suppress missingIncludeSystem
#include "looper.h"
#include "channel.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>
>>>>>>> REPLACE
```

src/looper.h
```c
<<<<<<< SEARCH
#include <jack/jack.h>
=======
// cppcheck-suppress missingIncludeSystem
#include <jack/jack.h>
>>>>>>> REPLACE
```

src/main.c
```c
<<<<<<< SEARCH
#include "looper.h"
#include <jack/jack.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
=======
// cppcheck-suppress missingIncludeSystem
#include "looper.h"
#include <jack/jack.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
>>>>>>> REPLACE
```

src/midi.c
```c
<<<<<<< SEARCH
#include "midi.h"
#include "channel.h"
#include <jack/jack.h>
#include <jack/midiport.h>
#include <stdatomic.h>
=======
// cppcheck-suppress missingIncludeSystem
#include "midi.h"
#include "channel.h"
#include <jack/jack.h>
#include <jack/midiport.h>
#include <stdatomic.h>
>>>>>>> REPLACE
```

src/midi.h
```c
<<<<<<< SEARCH
#include <jack/types.h>
=======
// cppcheck-suppress missingIncludeSystem
#include <jack/types.h>
>>>>>>> REPLACE
```

tests/integration.c
```c
<<<<<<< SEARCH
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#include <math.h>
#include <time.h>
=======
// cppcheck-suppress missingIncludeSystem
// cppcheck-suppress usleepCalled
// cppcheck-suppress normalCheckLevelMaxBranches
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#include <math.h>
#include <time.h>
>>>>>>> REPLACE
```

Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 12:46:50 +00:00
Loic Coenen
c2df024350 style: reformat code and update cppcheck suppressions 2026-05-09 12:46:31 +00:00
Loic Coenen
d2e39f3451 chore: add install-hooks target and update cppcheck library flag 2026-05-09 12:38:35 +00:00
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
10 changed files with 421 additions and 389 deletions

4
.githooks/pre-push Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
make test
make check
make format

View File

@@ -22,8 +22,11 @@ clean:
rm -f looper integration_test src/*.o rm -f looper integration_test src/*.o
check: check:
cppcheck --enable=all --error-exitcode=1 src/*.c cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem --suppress=normalCheckLevelMaxBranches src/*.c --library=posix .
# Optional: Format code using clang-format # Optional: Format code using clang-format
format: format:
clang-format -i src/*.c clang-format -i src/*.c
install-hooks:
git config core.hooksPath .githooks

View File

@@ -1,42 +1,40 @@
#include <stdio.h> // cppcheck-suppress missingIncludeSystem
#include <string.h> #include "channel.h"
#include <jack/jack.h> #include <jack/jack.h>
#include <stdatomic.h> #include <stdatomic.h>
#include "channel.h" #include <stdio.h>
#include <string.h>
void channel_add(jack_client_t *client, int idx) void channel_add(jack_client_t *client, int idx) {
{ char in_name[64], out_name[64];
char in_name[64], out_name[64]; snprintf(in_name, sizeof(in_name), "channel%d_input", next_channel_id);
snprintf(in_name, sizeof(in_name), "channel%d_input", next_channel_id); snprintf(out_name, sizeof(out_name), "channel%d_output", next_channel_id);
snprintf(out_name, sizeof(out_name), "channel%d_output", next_channel_id);
channels[idx].audio_in = jack_port_register(client, in_name, channels[idx].audio_in = jack_port_register(
JACK_DEFAULT_AUDIO_TYPE, client, in_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
JackPortIsInput, 0); channels[idx].audio_out = jack_port_register(
channels[idx].audio_out = jack_port_register(client, out_name, client, out_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
JACK_DEFAULT_AUDIO_TYPE, if (!channels[idx].audio_in || !channels[idx].audio_out) {
JackPortIsOutput, 0); fprintf(stderr, "Failed to register ports for channel %d\n",
if (!channels[idx].audio_in || !channels[idx].audio_out) { next_channel_id);
fprintf(stderr, "Failed to register ports for channel %d\n", next_channel_id); /* Do NOT mark channel active process loop will skip it */
/* Do NOT mark channel active process loop will skip it */
atomic_store(&channels[idx].active, 0);
return;
}
atomic_store(&channels[idx].active, 1);
atomic_store(&channels[idx].state, STATE_IDLE);
channels[idx].prev_state = -1;
channels[idx].loop_count = 0;
channels[idx].record_pos = 0;
channels[idx].playback_pos = 0;
next_channel_id++;
channel_count++;
}
void channel_remove(jack_client_t *client, int idx)
{
(void)client;
atomic_store(&channels[idx].active, 0); atomic_store(&channels[idx].active, 0);
channel_count--; return;
}
atomic_store(&channels[idx].active, 1);
atomic_store(&channels[idx].state, STATE_IDLE);
channels[idx].prev_state = -1;
channels[idx].loop_count = 0;
channels[idx].record_pos = 0;
channels[idx].playback_pos = 0;
next_channel_id++;
channel_count++;
}
void channel_remove(jack_client_t *client, int idx) {
(void)client;
atomic_store(&channels[idx].active, 0);
channel_count--;
} }

View File

@@ -1,6 +1,7 @@
#ifndef CHANNEL_H #ifndef CHANNEL_H
#define CHANNEL_H #define CHANNEL_H
// cppcheck-suppress missingIncludeSystem
#include <jack/jack.h> #include <jack/jack.h>
#include <stdatomic.h> #include <stdatomic.h>

View File

@@ -1,24 +1,25 @@
#include <stdio.h> // cppcheck-suppress missingIncludeSystem
#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];
atomic_int channel_count = 0; atomic_int channel_count = 0;
int next_channel_id = 1; int next_channel_id = 1;
atomic_int cmd_add = 0; atomic_int cmd_add = 0;
atomic_int cmd_remove = 0; atomic_int cmd_remove = 0;
jack_port_t *midi_control_port = NULL; jack_port_t *midi_control_port = NULL;
jack_port_t *midi_clock_port = NULL; jack_port_t *midi_clock_port = NULL;
atomic_int control_key_active = 0; atomic_int control_key_active = 0;
atomic_int bind_channel = 0; atomic_int bind_channel = 0;
/* Deferred removal index (1 second grace) */ /* Deferred removal index (1 second grace) */
static int pending_unregister_idx = -1; static int pending_unregister_idx = -1;
@@ -26,211 +27,216 @@ 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) {
void *midi_ctrl_buf = jack_port_get_buffer(midi_control_port, nframes); void *midi_ctrl_buf = jack_port_get_buffer(midi_control_port, nframes);
if (midi_ctrl_buf) { if (midi_ctrl_buf) {
midi_handle_events(midi_ctrl_buf, nframes); midi_handle_events(midi_ctrl_buf, nframes);
} }
}
/* process each active channel */
for (int c = 0; c < MAX_CHANNELS; c++) {
if (!atomic_load(&channels[c].active))
continue;
/* Guard against NULL ports (e.g. if port registration failed) */
if (!channels[c].audio_in || !channels[c].audio_out) {
fprintf(stderr, "WARN: channel %d has NULL audio port(s), skipping\n", c);
continue;
} }
/* process each active channel */ const jack_default_audio_sample_t *in =
for (int c = 0; c < MAX_CHANNELS; c++) { (const jack_default_audio_sample_t *)jack_port_get_buffer(
if (!atomic_load(&channels[c].active)) continue; channels[c].audio_in, nframes);
jack_default_audio_sample_t *out =
(jack_default_audio_sample_t *)jack_port_get_buffer(
channels[c].audio_out, nframes);
if (!out)
continue;
/* Guard against NULL ports (e.g. if port registration failed) */ int state = atomic_load(&channels[c].state);
if (!channels[c].audio_in || !channels[c].audio_out) {
fprintf(stderr, "WARN: channel %d has NULL audio port(s), skipping\n", c);
continue;
}
jack_default_audio_sample_t *in = (jack_default_audio_sample_t *) if (state != channels[c].prev_state) {
jack_port_get_buffer(channels[c].audio_in, nframes); switch (state) {
jack_default_audio_sample_t *out = (jack_default_audio_sample_t *) case STATE_RECORD:
jack_port_get_buffer(channels[c].audio_out, nframes); channels[c].record_pos = 0;
if (!out) continue; channels[c].loop_count = 0;
break;
int state = atomic_load(&channels[c].state); case STATE_LOOPING:
if (channels[c].record_pos > 0)
if (state != channels[c].prev_state) { channels[c].loop_count = channels[c].record_pos;
switch (state) { channels[c].playback_pos = 0;
case STATE_RECORD: break;
channels[c].record_pos = 0; default:
channels[c].loop_count = 0; break;
break; }
case STATE_LOOPING:
if (channels[c].record_pos > 0)
channels[c].loop_count = channels[c].record_pos;
channels[c].playback_pos = 0;
break;
default:
break;
}
}
jack_nframes_t i;
switch (state) {
case STATE_RECORD:
if (in) {
for (i = 0; i < nframes; i++) {
if (channels[c].record_pos < LOOP_BUF_SIZE)
channels[c].loop_buffer[channels[c].record_pos++] = ((const float *)in)[i];
((float *)out)[i] = ((const float *)in)[i];
}
} else {
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
}
break;
case STATE_LOOPING:
if (channels[c].loop_count > 0) {
float *outf = (float *)out;
for (i = 0; i < nframes; i++) {
outf[i] = channels[c].loop_buffer[channels[c].playback_pos];
channels[c].playback_pos = (channels[c].playback_pos + 1) % channels[c].loop_count;
}
} else {
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
}
break;
case STATE_PAUSED:
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
break;
default: /* IDLE */
if (in) {
memcpy(out, in, sizeof(jack_default_audio_sample_t) * nframes);
} else {
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
}
break;
}
channels[c].prev_state = state;
} }
/* MIDI clock events affect channel 0 only */ jack_nframes_t i;
if (midi_clock_port) { switch (state) {
void *midi_clock_buf = jack_port_get_buffer(midi_clock_port, nframes); case STATE_RECORD:
if (midi_clock_buf) { if (in) {
jack_nframes_t n_clock_events = jack_midi_get_event_count(midi_clock_buf); float *f_out = (float *)out;
jack_midi_event_t cev; const float *f_in = (const float *)in;
for (jack_nframes_t j = 0; j < n_clock_events; j++) { for (i = 0; i < nframes; i++) {
if (jack_midi_event_get(&cev, midi_clock_buf, j) != 0) continue; if (channels[c].record_pos < LOOP_BUF_SIZE)
if (cev.size >= 1) { channels[c].loop_buffer[channels[c].record_pos++] =
unsigned char msg = cev.buffer[0]; f_in[i];
switch (msg) { f_out[i] = f_in[i];
case 0xFA: {
int s = atomic_load(&channels[0].state);
if (s == STATE_IDLE) atomic_store(&channels[0].state, STATE_RECORD);
break;
}
case 0xFC:
atomic_store(&channels[0].state, STATE_IDLE);
break;
case 0xFB: {
int s = atomic_load(&channels[0].state);
if (s == STATE_PAUSED) atomic_store(&channels[0].state, STATE_LOOPING);
break;
}
default:
break;
}
}
}
} }
} else {
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
}
break;
case STATE_LOOPING:
if (channels[c].loop_count > 0) {
float *outf = (float *)out;
for (i = 0; i < nframes; i++) {
outf[i] = channels[c].loop_buffer[channels[c].playback_pos];
channels[c].playback_pos =
(channels[c].playback_pos + 1) % channels[c].loop_count;
}
} else {
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
}
break;
case STATE_PAUSED:
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
break;
default: /* IDLE */
if (in) {
memcpy(out, in, sizeof(jack_default_audio_sample_t) * nframes);
} else {
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
}
break;
} }
return 0; channels[c].prev_state = state;
}
/* MIDI clock events affect channel 0 only */
if (midi_clock_port) {
void *midi_clock_buf = jack_port_get_buffer(midi_clock_port, nframes);
if (midi_clock_buf) {
jack_nframes_t n_clock_events = jack_midi_get_event_count(midi_clock_buf);
jack_midi_event_t cev;
for (jack_nframes_t j = 0; j < n_clock_events; j++) {
if (jack_midi_event_get(&cev, midi_clock_buf, j) != 0)
continue;
if (cev.size >= 1) {
unsigned char msg = cev.buffer[0];
switch (msg) {
case 0xFA: {
int s = atomic_load(&channels[0].state);
if (s == STATE_IDLE)
atomic_store(&channels[0].state, STATE_RECORD);
break;
}
case 0xFC:
atomic_store(&channels[0].state, STATE_IDLE);
break;
case 0xFB: {
int s = atomic_load(&channels[0].state);
if (s == STATE_PAUSED)
atomic_store(&channels[0].state, STATE_LOOPING);
break;
}
default:
break;
}
}
}
}
}
return 0;
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* 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);
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* 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); channels[0].prev_state = -1;
channels[0].prev_state = -1; channels[0].loop_count = 0;
channels[0].loop_count = 0; 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, if (!channels[0].audio_in || !channels[0].audio_out) {
JackPortIsOutput, 0); fprintf(stderr, "Could not create audio ports for channel 0\n");
if (!channels[0].audio_in || !channels[0].audio_out) { return -1;
fprintf(stderr, "Could not create audio ports for channel 0\n"); }
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", JackPortIsInput, 0);
JACK_DEFAULT_MIDI_TYPE, if (!midi_control_port || !midi_clock_port) {
JackPortIsInput, 0); fprintf(stderr, "Could not create MIDI ports\n");
if (!midi_control_port || !midi_clock_port) { return -1;
fprintf(stderr, "Could not create MIDI ports\n"); }
return -1;
}
return 0; return 0;
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* 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. */ if (pending_unregister_idx != -1) {
if (pending_unregister_idx != -1) { int idx = pending_unregister_idx;
int idx = pending_unregister_idx; if (channels[idx].audio_in)
if (channels[idx].audio_in) jack_port_unregister(client, channels[idx].audio_in);
jack_port_unregister(client, channels[idx].audio_in); if (channels[idx].audio_out)
if (channels[idx].audio_out) jack_port_unregister(client, channels[idx].audio_out);
jack_port_unregister(client, channels[idx].audio_out); pending_unregister_idx = -1;
pending_unregister_idx = -1; }
}
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)
if (idx < MAX_CHANNELS) { break;
channel_add(client, idx); if (idx < MAX_CHANNELS) {
} channel_add(client, idx);
} }
}
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)
if (remove_idx != -1) { remove_idx = idx;
/* Mark inactive now; ports will be unregistered next round */ if (remove_idx != -1) {
channel_remove(client, remove_idx); /* Mark inactive now; ports will be unregistered next round */
pending_unregister_idx = remove_idx; channel_remove(client, remove_idx);
} pending_unregister_idx = remove_idx;
} }
}
} }

View File

@@ -1,6 +1,7 @@
#ifndef LOOPER_H #ifndef LOOPER_H
#define LOOPER_H #define LOOPER_H
// cppcheck-suppress missingIncludeSystem
#include <jack/jack.h> #include <jack/jack.h>
/* Initialisation must be called after setting process callback */ /* Initialisation must be called after setting process callback */

View File

@@ -1,50 +1,51 @@
// cppcheck-suppress missingIncludeSystem
#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"; jack_options_t options = JackNullOption;
jack_options_t options = JackNullOption; jack_status_t status;
jack_status_t status;
jack_client_t *client = jack_client_open(client_name, options, &status); jack_client_t *client = jack_client_open(client_name, options, &status);
if (client == NULL) { if (client == NULL) {
fprintf(stderr, "jack_client_open() failed, status = 0x%2.0x\n", status); fprintf(stderr, "jack_client_open() failed, status = 0x%2.0x\n", status);
if (status & JackServerFailed) if (status & JackServerFailed)
fprintf(stderr, "Unable to connect to JACK server\n"); fprintf(stderr, "Unable to connect to JACK server\n");
return 1; return 1;
} }
if (status & JackNameNotUnique) if (status & JackNameNotUnique)
client_name = jack_get_client_name(client); client_name = jack_get_client_name(client);
jack_set_process_callback(client, process_callback, NULL); jack_set_process_callback(client, process_callback, NULL);
jack_on_shutdown(client, jack_shutdown_cb, NULL); jack_on_shutdown(client, jack_shutdown_cb, NULL);
if (looper_init(client) != 0) {
fprintf(stderr, "looper initialisation failed\n");
jack_client_close(client);
return 1;
}
if (jack_activate(client)) {
fprintf(stderr, "Cannot activate client\n");
jack_client_close(client);
return 1;
}
fprintf(stderr, "looper running (client name '%s')\n", client_name);
while (1) {
looper_process_commands(client);
usleep(50000); /* check commands every 50 ms */
}
if (looper_init(client) != 0) {
fprintf(stderr, "looper initialisation failed\n");
jack_client_close(client); jack_client_close(client);
return 0; return 1;
}
if (jack_activate(client)) {
fprintf(stderr, "Cannot activate client\n");
jack_client_close(client);
return 1;
}
fprintf(stderr, "looper running (client name '%s')\n", client_name);
while (1) {
looper_process_commands(client);
{ struct timespec ts = { .tv_sec = 0, .tv_nsec = 50000000 }; nanosleep(&ts, NULL); } /* check commands every 50 ms */
}
jack_client_close(client);
return 0;
} }

View File

@@ -1,102 +1,111 @@
// cppcheck-suppress missingIncludeSystem
#include "midi.h"
#include "channel.h"
#include <jack/jack.h> #include <jack/jack.h>
#include <jack/midiport.h> #include <jack/midiport.h>
#include <stdatomic.h> #include <stdatomic.h>
#include "midi.h"
#include "channel.h"
extern atomic_int control_key_active; extern atomic_int control_key_active;
extern atomic_int cmd_add; extern atomic_int cmd_add;
extern atomic_int cmd_remove; extern atomic_int cmd_remove;
extern atomic_int bind_channel; extern atomic_int bind_channel;
void midi_handle_events(void *port_buffer, jack_nframes_t nframes) void midi_handle_events(void *port_buffer, jack_nframes_t nframes) {
{ (void)nframes;
(void)nframes; jack_nframes_t nevents = jack_midi_get_event_count(port_buffer);
jack_nframes_t nevents = jack_midi_get_event_count(port_buffer); jack_midi_event_t ev;
jack_midi_event_t ev;
for (jack_nframes_t i = 0; i < nevents; i++) { for (jack_nframes_t i = 0; i < nevents; i++) {
if (jack_midi_event_get(&ev, port_buffer, i) != 0) continue; if (jack_midi_event_get(&ev, port_buffer, i) != 0)
if (ev.size < 3) continue; continue;
if (ev.size < 3)
continue;
unsigned char status = ev.buffer[0]; unsigned char status = ev.buffer[0];
unsigned char note = ev.buffer[1]; unsigned char note = ev.buffer[1];
unsigned char vel = ev.buffer[2]; unsigned char vel = ev.buffer[2];
/* noteon */ /* noteon */
if ((status & 0xf0) == 0x90 && vel > 0) { if ((status & 0xf0) == 0x90 && vel > 0) {
if (note == 64) { if (note == 64) {
atomic_store(&control_key_active, 1); atomic_store(&control_key_active, 1);
} else { } else {
int ck = atomic_load(&control_key_active); int ck = atomic_load(&control_key_active);
if (ck) { if (ck) {
atomic_store(&control_key_active, 0); atomic_store(&control_key_active, 0);
if (note < 16) { if (note < 16) {
atomic_store(&bind_channel, note); atomic_store(&bind_channel, note);
} else { } else {
switch (note) { switch (note) {
case 60: atomic_store(&cmd_add, 1); break; case 60:
case 61: atomic_store(&cmd_remove, 1); break; atomic_store(&cmd_add, 1);
case 62: /* trigger looper channel via bind_channel */ break;
{ case 61:
int bch = atomic_load(&bind_channel); atomic_store(&cmd_remove, 1);
if (bch >= 0 && bch < MAX_CHANNELS) { break;
int cur = atomic_load(&channels[bch].state); case 62: /* trigger looper channel via bind_channel */
switch (cur) { {
case STATE_IDLE: int bch = atomic_load(&bind_channel);
atomic_store(&channels[bch].state, STATE_RECORD); if (bch >= 0 && bch < MAX_CHANNELS) {
break; int cur = atomic_load(&channels[bch].state);
case STATE_RECORD: switch (cur) {
atomic_store(&channels[bch].state, STATE_LOOPING); case STATE_IDLE:
break; atomic_store(&channels[bch].state, STATE_RECORD);
case STATE_LOOPING: break;
atomic_store(&channels[bch].state, STATE_PAUSED); case STATE_RECORD:
break; atomic_store(&channels[bch].state, STATE_LOOPING);
case STATE_PAUSED: break;
atomic_store(&channels[bch].state, STATE_LOOPING); case STATE_LOOPING:
break; atomic_store(&channels[bch].state, STATE_PAUSED);
} break;
} case STATE_PAUSED:
} atomic_store(&channels[bch].state, STATE_LOOPING);
break; break;
case 63: /* unbind reset bind to channel 0 */
atomic_store(&bind_channel, 0);
break;
default:
break;
}
}
} else {
/* direct mapping */
switch (note) {
case 1: /* toggle channel 0 */
{
int cur0 = atomic_load(&channels[0].state);
switch (cur0) {
case STATE_IDLE:
atomic_store(&channels[0].state, STATE_RECORD);
break;
case STATE_RECORD:
atomic_store(&channels[0].state, STATE_LOOPING);
break;
case STATE_LOOPING:
atomic_store(&channels[0].state, STATE_PAUSED);
break;
case STATE_PAUSED:
atomic_store(&channels[0].state, STATE_LOOPING);
break;
}
}
break;
case 60: atomic_store(&cmd_add, 1); break;
case 61: atomic_store(&cmd_remove, 1); break;
default:
break;
}
} }
}
} break;
case 63: /* unbind reset bind to channel 0 */
atomic_store(&bind_channel, 0);
break;
default:
break;
} }
} else if ((status & 0xf0) == 0x80 || ((status & 0xf0) == 0x90 && vel == 0)) { }
atomic_store(&control_key_active, 0); } else {
/* direct mapping */
switch (note) {
case 1: /* toggle channel 0 */
{
int cur0 = atomic_load(&channels[0].state);
switch (cur0) {
case STATE_IDLE:
atomic_store(&channels[0].state, STATE_RECORD);
break;
case STATE_RECORD:
atomic_store(&channels[0].state, STATE_LOOPING);
break;
case STATE_LOOPING:
atomic_store(&channels[0].state, STATE_PAUSED);
break;
case STATE_PAUSED:
atomic_store(&channels[0].state, STATE_LOOPING);
break;
}
} break;
case 60:
atomic_store(&cmd_add, 1);
break;
case 61:
atomic_store(&cmd_remove, 1);
break;
default:
break;
}
} }
}
} else if ((status & 0xf0) == 0x80 ||
((status & 0xf0) == 0x90 && vel == 0)) {
atomic_store(&control_key_active, 0);
} }
}
} }

View File

@@ -1,6 +1,7 @@
#ifndef MIDI_H #ifndef MIDI_H
#define MIDI_H #define MIDI_H
// cppcheck-suppress missingIncludeSystem
#include <jack/types.h> #include <jack/types.h>
void midi_handle_events(void *port_buffer, jack_nframes_t nframes); void midi_handle_events(void *port_buffer, jack_nframes_t nframes);

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,11 +65,11 @@ 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 *f_out = (float *)out;
const float *inf = in; const float *f_in = (const float *)in;
for (jack_nframes_t i = 0; i < nframes; i++) { for (jack_nframes_t i = 0; i < nframes; i++) {
/* generate beep while beep_remaining > 0 or continuous sine */ /* generate beep while beep_remaining > 0 or continuous sine */
float out_val; float out_val;
@@ -74,17 +82,17 @@ static int passthrough_process(jack_nframes_t nframes, void *arg) {
} else { } else {
out_val = 0.0f; out_val = 0.0f;
} }
outf[i] = out_val; f_out[i] = out_val;
/* detect bursts on the input (looper output) */ /* detect bursts on the input (looper output) */
float sample = inf[i]; float sample = f_in[i];
int above = (fabsf(sample) > 0.05f); int above = (fabsf(sample) > 0.05f);
if (above && !prev_above) { if (above && !prev_above) {
bursts++; bursts++;
} }
prev_above = above; prev_above = above;
passthrough_sum_sq += (double)inf[i] * (double)inf[i]; passthrough_sum_sq += (double)f_in[i] * (double)f_in[i];
passthrough_total_samples++; passthrough_total_samples++;
} }
if (passthrough_total_samples >= passthrough_sample_rate * 2) { if (passthrough_total_samples >= passthrough_sample_rate * 2) {
@@ -157,7 +165,7 @@ static int test_audio_pass_through(void) {
waitpid(pid, NULL, 0); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
const char *looper_input = "looper:input"; const char *looper_input = "looper:input";
const char *looper_output = "looper:output"; const char *looper_output = "looper:output";
char my_output[64], my_input[64]; char my_output[64], my_input[64];
@@ -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;
@@ -251,7 +259,7 @@ static int send_jack_note_on(const char *target_port, unsigned char note, unsign
} }
/* wait for the process callback to clear the flag (event delivered) */ /* wait for the process callback to clear the flag (event delivered) */
for (int attempts = 0; attempts < 50; attempts++) { /* ~50 * 10ms = 500ms */ for (int attempts = 0; attempts < 50; attempts++) { /* ~50 * 10ms = 500ms */
usleep(10000); safe_usleep(10000);
if (!midi_inject_pending) break; if (!midi_inject_pending) break;
} }
jack_deactivate(midi_inject_client); jack_deactivate(midi_inject_client);
@@ -295,7 +303,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(200000); /* wait for ports to appear */ safe_usleep(200000); /* wait for ports to appear */
/* connect test:out -> looper:input, looper:output -> test:in */ /* connect test:out -> looper:input, looper:output -> test:in */
char my_out[64], my_in[64]; char my_out[64], my_in[64];
snprintf(my_out, sizeof(my_out), "test_looping:out"); snprintf(my_out, sizeof(my_out), "test_looping:out");
@@ -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);
@@ -440,7 +448,7 @@ static int test_control_key_modifier(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
char my_out[64], my_in[64]; char my_out[64], my_in[64];
snprintf(my_out, sizeof(my_out), "test_ctrl_key:out"); snprintf(my_out, sizeof(my_out), "test_ctrl_key:out");
snprintf(my_in, sizeof(my_in), "test_ctrl_key:in"); snprintf(my_in, sizeof(my_in), "test_ctrl_key:in");
@@ -457,7 +465,7 @@ static int test_control_key_modifier(void) {
fprintf(stderr, " FAIL: send note 64 failed\n"); fprintf(stderr, " FAIL: send note 64 failed\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
/* Now send note 62 (toggle channel 0) */ /* Now send note 62 (toggle channel 0) */
if (send_jack_note_on("looper:control", 62, 127) != 0) { if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_client_close(client); jack_client_close(client);
@@ -485,7 +493,7 @@ static int test_control_key_modifier(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(200000); /* allow beep */ safe_usleep(200000); /* allow beep */
/* send note 62 again under control key to move RECORD->LOOPING */ /* send note 62 again under control key to move RECORD->LOOPING */
if (send_jack_note_on("looper:control", 64, 127) != 0) { if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_client_close(client); jack_client_close(client);
@@ -493,14 +501,14 @@ static int test_control_key_modifier(void) {
fprintf(stderr, " FAIL: control key resend\n"); fprintf(stderr, " FAIL: control key resend\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
if (send_jack_note_on("looper:control", 62, 127) != 0) { if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
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);
@@ -539,7 +547,7 @@ static int test_bind_channel(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
char my_out[64], my_in[64]; char my_out[64], my_in[64];
snprintf(my_out, sizeof(my_out), "test_bind:out"); snprintf(my_out, sizeof(my_out), "test_bind:out");
snprintf(my_in, sizeof(my_in), "test_bind:in"); snprintf(my_in, sizeof(my_in), "test_bind:in");
@@ -556,14 +564,14 @@ static int test_bind_channel(void) {
fprintf(stderr, " FAIL: send control key failed\n"); fprintf(stderr, " FAIL: send control key failed\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
if (send_jack_note_on("looper:control", 0, 127) != 0) { if (send_jack_note_on("looper:control", 0, 127) != 0) {
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: send bind note 0 failed\n"); fprintf(stderr, " FAIL: send bind note 0 failed\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
/* Now toggle using control+note62 should toggle channel 0 */ /* Now toggle using control+note62 should toggle channel 0 */
if (send_jack_note_on("looper:control", 64, 127) != 0) { if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_client_close(client); jack_client_close(client);
@@ -571,7 +579,7 @@ static int test_bind_channel(void) {
fprintf(stderr, " FAIL: send control key again failed\n"); fprintf(stderr, " FAIL: send control key again failed\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
if (send_jack_note_on("looper:control", 62, 127) != 0) { if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
@@ -598,7 +606,7 @@ static int test_bind_channel(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(200000); /* allow beep */ safe_usleep(200000); /* allow beep */
/* send control+note62 again to move RECORD->LOOPING */ /* send control+note62 again to move RECORD->LOOPING */
if (send_jack_note_on("looper:control", 64, 127) != 0) { if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_client_close(client); jack_client_close(client);
@@ -606,14 +614,14 @@ static int test_bind_channel(void) {
fprintf(stderr, " FAIL: control key for loop\n"); fprintf(stderr, " FAIL: control key for loop\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
if (send_jack_note_on("looper:control", 62, 127) != 0) { if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
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);
@@ -652,7 +660,7 @@ static int test_bind_unbind(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
char my_out[64], my_in[64]; char my_out[64], my_in[64];
snprintf(my_out, sizeof(my_out), "test_unbind:out"); snprintf(my_out, sizeof(my_out), "test_unbind:out");
snprintf(my_in, sizeof(my_in), "test_unbind:in"); snprintf(my_in, sizeof(my_in), "test_unbind:in");
@@ -669,14 +677,14 @@ static int test_bind_unbind(void) {
fprintf(stderr, " FAIL: send control key failed\n"); fprintf(stderr, " FAIL: send control key failed\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
if (send_jack_note_on("looper:control", 5, 127) != 0) { if (send_jack_note_on("looper:control", 5, 127) != 0) {
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: bind to 5 failed\n"); fprintf(stderr, " FAIL: bind to 5 failed\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
/* Unbind (reset to 0) */ /* Unbind (reset to 0) */
if (send_jack_note_on("looper:control", 64, 127) != 0) { if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_client_close(client); jack_client_close(client);
@@ -684,14 +692,14 @@ static int test_bind_unbind(void) {
fprintf(stderr, " FAIL: control key for unbind\n"); fprintf(stderr, " FAIL: control key for unbind\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
if (send_jack_note_on("looper:control", 63, 127) != 0) { if (send_jack_note_on("looper:control", 63, 127) != 0) {
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: send unbind note 63 failed\n"); fprintf(stderr, " FAIL: send unbind note 63 failed\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
/* Now toggle with control+62 should affect channel 0 */ /* Now toggle with control+62 should affect channel 0 */
if (send_jack_note_on("looper:control", 64, 127) != 0) { if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_client_close(client); jack_client_close(client);
@@ -699,7 +707,7 @@ static int test_bind_unbind(void) {
fprintf(stderr, " FAIL: control key for toggle\n"); fprintf(stderr, " FAIL: control key for toggle\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
if (send_jack_note_on("looper:control", 62, 127) != 0) { if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
@@ -726,7 +734,7 @@ static int test_bind_unbind(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1; return 1;
} }
usleep(200000); /* allow beep */ safe_usleep(200000); /* allow beep */
/* second control+62 -> loop */ /* second control+62 -> loop */
if (send_jack_note_on("looper:control", 64, 127) != 0) { if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_client_close(client); jack_client_close(client);
@@ -734,14 +742,14 @@ static int test_bind_unbind(void) {
fprintf(stderr, " FAIL: control key for loop\n"); fprintf(stderr, " FAIL: control key for loop\n");
return 1; return 1;
} }
usleep(200000); safe_usleep(200000);
if (send_jack_note_on("looper:control", 62, 127) != 0) { if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_client_close(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0); kill(pid, SIGTERM); waitpid(pid, NULL, 0);
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;