feat: add initial JACK audio looper with MIDI control
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
This commit is contained in:
10
makefile
10
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
|
||||||
|
|||||||
131
src/main.c
131
src/main.c
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user