1-multichannel #1
@@ -24,12 +24,13 @@ The control key is released either by sending note‑off (note 64 or any note)
|
|||||||
| 61 | **Remove** the highest‑numbered active channel (excluding channel 0). |
|
| 61 | **Remove** the highest‑numbered active channel (excluding channel 0). |
|
||||||
| 62 | **Toggle** the current bound channel through its state machine: |
|
| 62 | **Toggle** the current bound channel through its state machine: |
|
||||||
| | IDLE → RECORD → LOOPING → PAUSED → LOOPING → … (each press advances one step). |
|
| | IDLE → RECORD → LOOPING → PAUSED → LOOPING → … (each press advances one step). |
|
||||||
|
| 63 | **Unbind** – reset the bound channel back to **0**. |
|
||||||
|
|
||||||
> **Notes:**
|
> **Notes:**
|
||||||
> - The default bound channel is **0**. If you never send a bind command, `control+62` controls channel 0.
|
> - The default bound channel is **0**. If you never send a bind command, `control+62` controls channel 0.
|
||||||
> - To bind a different channel, send `control + note <16>` (e.g., control + note 5 binds channel 5).
|
> - To bind a different channel, send `control + note <16>` (e.g., control + note 5 binds channel 5).
|
||||||
> - Bind is sticky – it stays until overwritten by another bind command.
|
> - Bind is sticky – it stays until overwritten by another bind command.
|
||||||
> - There is **no unbind** command; you can rebind to channel 0 if needed.
|
> - To **unbind** (reset to channel 0), send `control + note 63`.
|
||||||
|
|
||||||
## Direct Mapping (without control key)
|
## Direct Mapping (without control key)
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ void midi_handle_events(void *port_buffer, jack_nframes_t nframes)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 63: /* unbind – reset bind to channel 0 */
|
||||||
|
atomic_store(&bind_channel, 0);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -628,6 +628,134 @@ static int test_bind_channel(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* test unbind */
|
||||||
|
static int test_bind_unbind(void) {
|
||||||
|
printf("Test: bind to channel 5, unbind, then toggle default (channel 0)\n");
|
||||||
|
pid_t pid = start_looper();
|
||||||
|
if (pid < 0) return 1;
|
||||||
|
jack_client_t *client;
|
||||||
|
jack_status_t status;
|
||||||
|
client = jack_client_open("test_unbind", 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_unbind:out");
|
||||||
|
snprintf(my_in, sizeof(my_in), "test_unbind: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;
|
||||||
|
}
|
||||||
|
/* Bind to channel 5 */
|
||||||
|
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", 5, 127) != 0) {
|
||||||
|
jack_client_close(client);
|
||||||
|
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
|
||||||
|
fprintf(stderr, " FAIL: bind to 5 failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
usleep(200000);
|
||||||
|
/* Unbind (reset to 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: control key for unbind\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
usleep(200000);
|
||||||
|
if (send_jack_note_on("looper:control", 63, 127) != 0) {
|
||||||
|
jack_client_close(client);
|
||||||
|
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
|
||||||
|
fprintf(stderr, " FAIL: send unbind note 63 failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
usleep(200000);
|
||||||
|
/* Now toggle with control+62 – should affect 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: control key for toggle\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 note 62\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/* Wait for beep and loop */
|
||||||
|
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 */
|
||||||
|
/* second control+62 -> loop */
|
||||||
|
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 (unbind works, toggle channel 0)\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* test remove channel */
|
/* test remove channel */
|
||||||
static int test_remove_channel(void) {
|
static int test_remove_channel(void) {
|
||||||
printf("Test: dynamic channel removal via MIDI command\n");
|
printf("Test: dynamic channel removal via MIDI command\n");
|
||||||
@@ -738,7 +866,13 @@ int main(void) {
|
|||||||
failures++;
|
failures++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 8. Test channel removal */
|
/* 8. Test unbind */
|
||||||
|
if (test_bind_unbind() != 0) {
|
||||||
|
fprintf(stderr, " FAILED\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9. Test channel removal */
|
||||||
if (test_remove_channel() != 0) {
|
if (test_remove_channel() != 0) {
|
||||||
fprintf(stderr, " FAILED\n");
|
fprintf(stderr, " FAILED\n");
|
||||||
failures++;
|
failures++;
|
||||||
|
|||||||
Reference in New Issue
Block a user