|
|
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 |
|
Loic Coenen
|
5532b8cd50
|
chore: Add pre-push hooks
|
2026-05-09 11:36:38 +00:00 |
|
Loic Coenen
|
60a8bdcfe8
|
feat: add unbind command (note 63) to reset bind_channel to 0
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-09 11:14:52 +00:00 |
|
Loic Coenen
|
ac9ce26e9a
|
docs: add multi-channel and bind feature documentation
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-09 10:28:26 +00:00 |
|
Loic Coenen
|
4ad16824f1
|
docs: add multichannel documentation
|
2026-05-09 10:28:25 +00:00 |
|
Loic Coenen
|
4bacab68c6
|
feat: implement bind feature for associating channels with MIDI notes
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-09 10:22:33 +00:00 |
|
Loic Coenen
|
740ebaa969
|
test: add integration tests for control-key modifier and channel removal
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-09 10:09:51 +00:00 |
|
Loic Coenen
|
b0dda3d8ed
|
fix: defer port unregistration to avoid race condition in channel removal
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-09 10:00:33 +00:00 |
|
Loic Coenen
|
c0a0a6e968
|
fix: add null-checks for MIDI ports and use atomic access for channel active flag
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-09 09:57:36 +00:00 |
|
Loic Coenen
|
74a190c70c
|
#1: multichannel
|
2026-05-08 21:11:22 +00:00 |
|
Loic Coenen
|
d10aeebd13
|
fix: update segfault evaluation to reflect null pointer fixes
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-08 21:09:13 +00:00 |
|
Loic Coenen
|
b73719e8bc
|
fix: guard against NULL audio ports and defer channel activation until port registration succeeds
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-08 21:08:37 +00:00 |
|
Loic Coenen
|
96295fdb4c
|
refactor: split monolithic main.c into modular source files
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-08 21:03:10 +00:00 |
|
Loic Coenen
|
f1a92f1e95
|
fix: increase sleep duration in dynamic channel test to allow processing
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-08 20:56:56 +00:00 |
|
Loic Coenen
|
9eb264aab8
|
feat: implement control key (note 64) and trigger looper command (note 62)
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-08 20:51:34 +00:00 |
|
Loic Coenen
|
6b6f2dee3c
|
refactor: restructure looper into multi-channel architecture
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-08 20:38:37 +00:00 |
|
Loic Coenen
|
07962c2a09
|
feat: add test evaluation section to evaluation.md
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-08 20:31:04 +00:00 |
|
Loic Coenen
|
a7106f55d5
|
docs: add evaluation documentation
|
2026-05-08 20:31:02 +00:00 |
|
Loic Coenen
|
e824f6df73
|
feat: add test for dynamic channel creation via MIDI command
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-08 20:03:49 +00:00 |
|
Loic Coenen
|
0e567e1829
|
remove signal handling
|
2026-05-08 09:14:40 +00:00 |
|
Loic Coenen
|
3bb5936d02
|
fix: move prev_state update after MIDI clock handler to fix race condition
Co-authored-by: aider (deepseek/deepseek-chat) <aider@aider.chat>
|
2026-05-07 22:51:28 +00:00 |
|
Loic Coenen
|
829010a5d0
|
tests pass
|
2026-05-07 22:33:19 +00:00 |
|
Loic Coenen
|
2c868cabe2
|
fix: remove leading whitespace from test target in makefile
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 22:32:40 +00:00 |
|
Loic Coenen
|
3e291a9b58
|
fix: initialize passthrough globals in test_looper_looping
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 22:28:53 +00:00 |
|
Loic Coenen
|
65c346d14d
|
fix: increase timing margins in integration test to ensure MIDI events are delivered
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 22:27:00 +00:00 |
|
Loic Coenen
|
74b68eb378
|
fix: enable continuous sine tone in audio pass-through test
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 22:20:31 +00:00 |
|
Loic Coenen
|
7e7b12c89a
|
makefile
```makefile
<<<<<<< SEARCH
.PHONY: clean integration
=======
.PHONY: clean integration test
>>>>>>> REPLACE
```
makefile
```makefile
<<<<<<< SEARCH
./integration_test
=======
./integration_test
test: integration
>>>>>>> REPLACE
```
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 22:09:59 +00:00 |
|
Loic Coenen
|
150b01d3be
|
tests/integration.c
```c
<<<<<<< SEARCH
static volatile int passthrough_done = 0;
static volatile int beep_remaining = 0;
static volatile int bursts = 0;
static volatile int prev_above = 0;
=======
static volatile int passthrough_done = 0;
static volatile int beep_remaining = 0;
static volatile int bursts = 0;
static volatile int prev_above = 0;
/* variables for MIDI injection (used by send_jack_note_on) */
static volatile int midi_inject_pending = 0;
static jack_port_t *midi_inject_port = NULL;
static jack_client_t *midi_inject_client = NULL;
static unsigned char midi_inject_note = 0;
static unsigned char midi_inject_velocity = 0;
static int midi_inject_process(jack_nframes_t nframes, void *arg) {
(void)arg;
void *port_buf = jack_port_get_buffer(midi_inject_port, nframes);
if (!port_buf) return 0;
jack_midi_clear_buffer(port_buf);
if (!midi_inject_pending) return 0;
jack_midi_data_t *buf = jack_midi_event_reserve(port_buf, 0, 3);
if (!buf) return 0;
buf[0] = 0x90;
buf[1] = midi_inject_note;
buf[2] = midi_inject_velocity;
midi_inject_pending = 0;
return 0;
}
>>>>>>> REPLACE
```
tests/integration.c
```c
<<<<<<< SEARCH
/* Helper: open a transient JACK client, send a MIDI note‑on, close */
static int send_jack_note_on(const char *target_port, unsigned char note, unsigned char velocity) {
/* The correct JACK API requires writing events inside a process callback.
For now we stub this function; the test will skip the MIDI transition
tests and the pass‑through test suffices. */
(void)target_port;
(void)note;
(void)velocity;
return 0;
}
=======
/* Helper: open a transient JACK client, send a MIDI note‑on, close */
static int send_jack_note_on(const char *target_port, unsigned char note, unsigned char velocity) {
midi_inject_note = note;
midi_inject_velocity = velocity;
jack_status_t st;
midi_inject_client = jack_client_open("test_midi_inject", JackNoStartServer, &st);
if (!midi_inject_client) return -1;
midi_inject_port = jack_port_register(midi_inject_client, "out",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsOutput, 0);
if (!midi_inject_port) {
jack_client_close(midi_inject_client);
midi_inject_client = NULL;
return -1;
}
char src[64];
snprintf(src, sizeof(src), "test_midi_inject:out");
if (jack_connect(midi_inject_client, src, target_port) != 0) {
jack_client_close(midi_inject_client);
midi_inject_client = NULL;
midi_inject_port = NULL;
return -1;
}
jack_set_process_callback(midi_inject_client, midi_inject_process, NULL);
if (jack_activate(midi_inject_client) != 0) {
jack_client_close(midi_inject_client);
midi_inject_client = NULL;
midi_inject_port = NULL;
return -1;
}
midi_inject_pending = 1;
/* wait for one process cycle to deliver the event */
usleep(200000);
jack_deactivate(midi_inject_client);
jack_client_close(midi_inject_client);
midi_inject_client = NULL;
midi_inject_port = NULL;
return 0;
}
>>>>>>> REPLACE
```
tests/integration.c
```c
<<<<<<< SEARCH
/*
* Full loop recording test (stub – the MIDI API is non‑trivial without external tools,
* so we skip the actual instrumentation and just verify the looper doesn't crash).
*/
static int test_looper_looping(void) {
printf("Test: loop recording and playback (skip – no external MIDI tool)\n");
printf(" SUCCESS: nothing was measured (stub)\n");
return 0;
}
=======
/*
* Full loop recording test:
* 1. start looper
* 2. open JACK test client (audio)
* 3. send note‑on to move IDLE->RECORD
* 4. generate a short 440 Hz beep (~0.1 s) while recording
* 5. send note‑on to move RECORD->LOOPING
* 6. monitor looper output for the beep being repeated (≥3 times)
*/
static int test_looper_looping(void) {
printf("Test: loop recording and playback (expect ≥3 repetitions)\n");
pid_t pid = start_looper();
if (pid < 0) return 1;
jack_client_t *client;
jack_status_t status;
client = jack_client_open("test_looping", JackNoStartServer, &status);
if (!client) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " SKIP: JACK not running?\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;
}
usleep(200000); /* wait for ports to appear */
/* connect test:out -> looper:input, looper:output -> test:in */
char my_out[64], my_in[64];
snprintf(my_out, sizeof(my_out), "test_looping:out");
snprintf(my_in, sizeof(my_in), "test_looping:in");
if (jack_connect(client, my_out, "looper:input") ||
jack_connect(client, "looper:output", my_in)) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
/* first note‑on: IDLE -> RECORD */
if (send_jack_note_on("looper:control", 1, 127) != 0) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
usleep(200000); /* allow state to change */
int sr = jack_get_sample_rate(client);
beep_remaining = (int)(0.1f * sr); /* 0.1 second beep */
bursts = 0;
prev_above = 0;
jack_set_process_callback(client, passthrough_process, NULL);
if (jack_activate(client)) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
usleep(150000); /* let beep start */
/* after beep finishes, give it a moment then send note‑on to stop recording */
usleep(500000);
beep_remaining = 0;
if (send_jack_note_on("looper:control", 1, 127) != 0) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
/* wait enough time for several loops (3 seconds) */
usleep(3000000);
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM);
waitpid(pid, NULL, 0);
int got_bursts = bursts;
printf(" detected bursts: %d\n", got_bursts);
if (got_bursts < 3) {
fprintf(stderr, " FAIL: expected ≥3 bursts, got %d\n", got_bursts);
return 1;
}
printf(" PASS (at least 3 repetitions)\n");
return 0;
}
>>>>>>> REPLACE
```
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:56:12 +00:00 |
|
Loic Coenen
|
5a90446456
|
fix: stub MIDI-dependent tests to fix build errors
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:45:34 +00:00 |
|
Loic Coenen
|
3761851871
|
refactor: remove unused MIDI state transition test functions
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:44:02 +00:00 |
|
Loic Coenen
|
99c4e033f0
|
refactor: remove extern declarations and unused test function
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:42:48 +00:00 |
|
Loic Coenen
|
d4b3c2334b
|
feat: implement loop recording and playback with integration test
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:41:36 +00:00 |
|
Loic Coenen
|
944608ad7f
|
fix: start and clean up looper process in audio pass-through test
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:31:51 +00:00 |
|
Loic Coenen
|
cb58207904
|
refactor: remove external MIDI tool dependency from integration tests
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:26:07 +00:00 |
|
Loic Coenen
|
2b35be36fb
|
fix: link JACK and math libraries in integration test build
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:17:51 +00:00 |
|
Loic Coenen
|
4b9ee29d8e
|
feat: add JACK passthrough test with sine generation and RMS validation
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:17:11 +00:00 |
|
Loic Coenen
|
a8616e4ca3
|
refactor: remove external tool dependencies from integration tests
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
|
2026-05-07 21:14:34 +00:00 |
|