From 7014aa9e34a80fc669494fb67e91a6d068ee4a56 Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Thu, 7 May 2026 20:11:05 +0000 Subject: [PATCH] feat: add initial JACK audio looper with MIDI control Co-authored-by: aider (deepseek/deepseek-reasoner) --- makefile | 10 ++++ src/main.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/makefile b/makefile index e69de29..660dec2 100644 --- a/makefile +++ b/makefile @@ -0,0 +1,10 @@ +CC ?= gcc +CFLAGS ?= -Wall -Wextra -g +LDFLAGS ?= -ljack + +looper: src/main.c + $(CC) $(CFLAGS) -o looper src/main.c $(LDFLAGS) + +.PHONY: clean +clean: + rm -f looper diff --git a/src/main.c b/src/main.c index e69de29..48a56ab 100644 --- a/src/main.c +++ b/src/main.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include + +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; +}