feat: add initial JACK audio looper with MIDI control

Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
This commit is contained in:
Loic Coenen
2026-05-07 20:11:05 +00:00
parent 14236b9312
commit 7014aa9e34
2 changed files with 141 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <jack/jack.h>
typedef enum {
STATE_IDLE,
STATE_RECORD,
STATE_LOOPING,
STATE_PAUSED
} looper_state;
static volatile looper_state current_state = STATE_IDLE;
static jack_port_t *input_port;
static jack_port_t *output_port;
static jack_port_t *midi_control_port;
static jack_port_t *midi_clock_port;
static jack_client_t *client;
static int process(jack_nframes_t nframes, void *arg)
{
jack_default_audio_sample_t *in = (jack_default_audio_sample_t *) jack_port_get_buffer(input_port, nframes);
jack_default_audio_sample_t *out = (jack_default_audio_sample_t *) jack_port_get_buffer(output_port, nframes);
if (in && out) {
memcpy(out, in, sizeof(jack_default_audio_sample_t) * nframes);
}
void *midi_ctrl_buf = jack_port_get_buffer(midi_control_port, nframes);
if (midi_ctrl_buf) {
jack_nframes_t nevents = jack_midi_get_event_count(midi_ctrl_buf);
jack_midi_event_t ev;
for (jack_nframes_t i = 0; i < nevents; i++) {
if (jack_midi_event_get(&ev, midi_ctrl_buf, i) == 0) {
// note on with note number 1
if ((ev.size >= 3) && ((ev.buffer[0] & 0xf0) == 0x90)) {
unsigned char note = ev.buffer[1];
if (note == 1) {
switch (current_state) {
case STATE_IDLE:
current_state = STATE_RECORD;
fprintf(stderr, "[looper] -> record\n");
break;
case STATE_RECORD:
current_state = STATE_LOOPING;
fprintf(stderr, "[looper] -> looping\n");
break;
case STATE_LOOPING:
current_state = STATE_PAUSED;
fprintf(stderr, "[looper] -> paused\n");
break;
case STATE_PAUSED:
current_state = STATE_LOOPING;
fprintf(stderr, "[looper] -> looping\n");
break;
}
}
}
}
}
}
// midi clock port is registered but not processed here
return 0;
}
static void jack_shutdown(void *arg)
{
(void)arg;
fprintf(stderr, "JACK shutdown\n");
exit(0);
}
int main(int argc, char *argv[])
{
const char *client_name = "looper";
jack_options_t options = JackNullOption;
jack_status_t status;
client = jack_client_open(client_name, options, &status);
if (client == NULL) {
fprintf(stderr, "jack_client_open() failed, status = 0x%2.0x\n", status);
if (status & JackServerFailed) {
fprintf(stderr, "Unable to connect to JACK server\n");
}
return 1;
}
if (status & JackNameNotUnique) {
client_name = jack_get_client_name(client);
}
jack_set_process_callback(client, process, NULL);
jack_on_shutdown(client, jack_shutdown, NULL);
input_port = jack_port_register(client, "input",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput, 0);
output_port = jack_port_register(client, "output",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
midi_control_port = jack_port_register(client, "control",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsInput, 0);
midi_clock_port = jack_port_register(client, "clock",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsInput, 0);
if ((input_port == NULL) || (output_port == NULL) ||
(midi_control_port == NULL) || (midi_clock_port == NULL)) {
fprintf(stderr, "Could not create ports\n");
return 1;
}
if (jack_activate(client)) {
fprintf(stderr, "Cannot activate client\n");
return 1;
}
fprintf(stderr, "looper running (client name '%s')\n", client_name);
while (1) {
sleep(1);
}
jack_client_close(client);
return 0;
}