diff --git a/src/looper.c b/src/looper.c index 564886e..5eb0ec8 100644 --- a/src/looper.c +++ b/src/looper.c @@ -18,6 +18,7 @@ atomic_int cmd_remove = 0; jack_port_t *midi_control_port = NULL; jack_port_t *midi_clock_port = NULL; atomic_int control_key_active = 0; +atomic_int bind_channel = 0; /* Deferred removal index (1 second grace) */ static int pending_unregister_idx = -1; diff --git a/src/midi.c b/src/midi.c index 095fc3d..901455d 100644 --- a/src/midi.c +++ b/src/midi.c @@ -7,6 +7,7 @@ extern atomic_int control_key_active; extern atomic_int cmd_add; extern atomic_int cmd_remove; +extern atomic_int bind_channel; void midi_handle_events(void *port_buffer, jack_nframes_t nframes) { @@ -30,30 +31,37 @@ void midi_handle_events(void *port_buffer, jack_nframes_t nframes) int ck = atomic_load(&control_key_active); if (ck) { atomic_store(&control_key_active, 0); - switch (note) { - case 60: atomic_store(&cmd_add, 1); break; - case 61: atomic_store(&cmd_remove, 1); break; - case 62: /* trigger looper – 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; + if (note < 16) { + atomic_store(&bind_channel, note); + } else { + switch (note) { + case 60: atomic_store(&cmd_add, 1); break; + case 61: atomic_store(&cmd_remove, 1); break; + case 62: /* trigger looper – channel via bind_channel */ + { + int bch = atomic_load(&bind_channel); + if (bch >= 0 && bch < MAX_CHANNELS) { + int cur = atomic_load(&channels[bch].state); + switch (cur) { + case STATE_IDLE: + atomic_store(&channels[bch].state, STATE_RECORD); + break; + case STATE_RECORD: + atomic_store(&channels[bch].state, STATE_LOOPING); + break; + case STATE_LOOPING: + atomic_store(&channels[bch].state, STATE_PAUSED); + break; + case STATE_PAUSED: + atomic_store(&channels[bch].state, STATE_LOOPING); + break; + } + } } + break; + default: + break; } - break; - default: - break; } } else { /* direct mapping */ diff --git a/tests/integration.c b/tests/integration.c index 11d0d47..c0c79d5 100644 --- a/tests/integration.c +++ b/tests/integration.c @@ -515,6 +515,119 @@ static int test_control_key_modifier(void) { return 0; } +/* test bind channel */ +static int test_bind_channel(void) { + printf("Test: control‑key bind channel (note 0) and toggle\n"); + pid_t pid = start_looper(); + if (pid < 0) return 1; + jack_client_t *client; + jack_status_t status; + client = jack_client_open("test_bind", 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; + } + usleep(200000); + char my_out[64], my_in[64]; + snprintf(my_out, sizeof(my_out), "test_bind:out"); + snprintf(my_in, sizeof(my_in), "test_bind: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; + } + /* Send control key + note 0 to bind to channel 0 */ + if (send_jack_note_on("looper:control", 64, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: send control key failed\n"); + return 1; + } + usleep(200000); + if (send_jack_note_on("looper:control", 0, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: send bind note 0 failed\n"); + return 1; + } + usleep(200000); + /* Now toggle using control+note62 – should toggle channel 0 */ + if (send_jack_note_on("looper:control", 64, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: send control key again failed\n"); + return 1; + } + usleep(200000); + if (send_jack_note_on("looper:control", 62, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: send toggle note 62 failed\n"); + return 1; + } + /* Wait and detect bursts as before */ + int sr = jack_get_sample_rate(client); + continuous_sine = 0; + beep_remaining = (int)(0.1f * 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_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + usleep(200000); /* allow beep */ + /* send control+note62 again to move RECORD->LOOPING */ + if (send_jack_note_on("looper:control", 64, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: control key for loop\n"); + return 1; + } + usleep(200000); + if (send_jack_note_on("looper:control", 62, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " FAIL: toggle for loop\n"); + return 1; + } + usleep(2000000); + 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 (bind and toggle)\n"); + return 0; +} + /* test remove channel */ static int test_remove_channel(void) { printf("Test: dynamic channel removal via MIDI command\n"); @@ -619,7 +732,13 @@ int main(void) { failures++; } - /* 7. Test channel removal */ + /* 7. Test bind channel */ + if (test_bind_channel() != 0) { + fprintf(stderr, " FAILED\n"); + failures++; + } + + /* 8. Test channel removal */ if (test_remove_channel() != 0) { fprintf(stderr, " FAILED\n"); failures++;