Files
jack-looper/engine.c
Loic Coenen 8341261d7a fix: add missing carla.h header and implementation files
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
2026-05-02 21:35:46 +00:00

250 lines
8.7 KiB
C

#include "engine.h"
#include "carla.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int process_callback(jack_nframes_t nframes, void *arg) {
Engine *engine = (Engine *)arg;
if (!engine || !engine->dispatch) return 0;
jack_default_audio_sample_t *audio_in[MAX_CHANNELS];
jack_default_audio_sample_t *audio_out[MAX_CHANNELS];
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
audio_in[ch] = (jack_default_audio_sample_t *)
jack_port_get_buffer(engine->audio_in_ports[ch], nframes);
audio_out[ch] = (jack_default_audio_sample_t *)
jack_port_get_buffer(engine->audio_out_ports[ch], nframes);
}
void *midi_in_buf = jack_port_get_buffer(engine->midi_in_port, nframes);
void *midi_scene_buf = jack_port_get_buffer(engine->midi_scene_in_port, nframes);
(void)jack_port_get_buffer(engine->midi_clock_in_port, nframes);
void *midi_out_buf = jack_port_get_buffer(engine->midi_out_port, nframes);
jack_midi_clear_buffer(midi_out_buf);
// Get state snapshot for reads
AppState state = dispatcher_get_state();
// Process MIDI input
int event_index;
jack_midi_event_t midi_event;
event_index = 0;
while (jack_midi_event_get(&midi_event, midi_in_buf, event_index) == 0) {
event_index++;
uint8_t *data = midi_event.buffer;
uint8_t status = data[0] & 0xF0;
uint8_t channel = data[0] & 0x0F;
uint8_t note = data[1];
uint8_t velocity = data[2];
if (status == 0x90 && channel == 0 && velocity > 0) {
Action action = {
.type = ACTION_MIDI_NOTE_ON,
.data.midi_note_on = { .note = note, .velocity = velocity,
.channel = channel, .time = midi_event.time }
};
engine->dispatch(action);
uint8_t out_velocity = clip_state_to_velocity(state.clips[note % MAX_CLIPS].state);
uint8_t out_msg[3] = {0x90 | channel, note, out_velocity};
jack_midi_event_write(midi_out_buf, midi_event.time, out_msg, 3);
} else {
jack_midi_event_write(midi_out_buf, midi_event.time,
midi_event.buffer, midi_event.size);
}
}
event_index = 0;
while (jack_midi_event_get(&midi_event, midi_scene_buf, event_index) == 0) {
event_index++;
uint8_t *data = midi_event.buffer;
uint8_t status = data[0] & 0xF0;
uint8_t note = data[1];
uint8_t velocity = data[2];
if (status == 0x90 && velocity > 0) {
Action action = {
.type = ACTION_MIDI_SCENE_LAUNCH,
.data.midi_scene_launch = { .scene_index = note % MAX_SCENES, .time = midi_event.time }
};
engine->dispatch(action);
}
}
// Process audio per-channel
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
memset(audio_out[ch], 0, sizeof(jack_default_audio_sample_t) * nframes);
// Create temporary buffer for rack processing
float *rack_in = malloc(nframes * sizeof(float));
float *rack_out = malloc(nframes * sizeof(float));
if (!rack_in || !rack_out) {
free(rack_in);
free(rack_out);
continue;
}
for (jack_nframes_t i = 0; i < nframes; i++) {
rack_in[i] = 0.0f;
for (int s = 0; s < MAX_SCENES; s++) {
int clip_idx = CLIP_INDEX(s, ch);
Clip *clip = &state.clips[clip_idx];
if (clip->state == CLIP_RECORDING) {
if (clip->write_position < MAX_BUFFER_SIZE) {
clip->buffer[clip->write_position++] = audio_in[ch][i];
}
}
if (clip->state == CLIP_LOOPING && clip->buffer_size > 0) {
rack_in[i] += clip->buffer[clip->read_position];
clip->read_position = (clip->read_position + 1) % clip->buffer_size;
}
}
}
// Process through Carla rack
carla_process(&state.carla_host, ch, rack_in, rack_out, nframes);
// Copy to output
memcpy(audio_out[ch], rack_out, nframes * sizeof(jack_default_audio_sample_t));
free(rack_in);
free(rack_out);
}
return 0;
}
static void shutdown_callback(void *arg) {
Engine *engine = (Engine *)arg;
engine->running = false;
fprintf(stderr, "JACK shutdown\n");
}
int engine_init(Engine *engine, const char *client_name, DispatchFn dispatch) {
if (!engine || !client_name || !dispatch) return -1;
memset(engine, 0, sizeof(Engine));
engine->dispatch = dispatch;
engine->running = false;
jack_status_t status;
engine->client = jack_client_open(client_name, JackNullOption, &status, NULL);
if (!engine->client) {
fprintf(stderr, "Failed to open JACK client, status = 0x%2.0x\n", status);
return -1;
}
char port_name[32];
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
snprintf(port_name, sizeof(port_name), "audio_in_%d", ch);
engine->audio_in_ports[ch] = jack_port_register(engine->client, port_name,
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput, 0);
snprintf(port_name, sizeof(port_name), "audio_out_%d", ch);
engine->audio_out_ports[ch] = jack_port_register(engine->client, port_name,
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if (!engine->audio_in_ports[ch] || !engine->audio_out_ports[ch]) {
fprintf(stderr, "Failed to register audio port %d\n", ch);
engine_cleanup(engine);
return -1;
}
}
engine->midi_in_port = jack_port_register(engine->client, "midi_control_in",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsInput, 0);
engine->midi_scene_in_port = jack_port_register(engine->client, "midi_scene_in",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsInput, 0);
engine->midi_clock_in_port = jack_port_register(engine->client, "midi_clock_in",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsInput, 0);
engine->midi_out_port = jack_port_register(engine->client, "midi_out",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsOutput, 0);
if (!engine->midi_in_port || !engine->midi_scene_in_port ||
!engine->midi_clock_in_port || !engine->midi_out_port) {
fprintf(stderr, "Failed to register MIDI ports\n");
engine_cleanup(engine);
return -1;
}
jack_set_process_callback(engine->client, process_callback, engine);
jack_on_shutdown(engine->client, shutdown_callback, engine);
engine->sample_rate = jack_get_sample_rate(engine->client);
return 0;
}
void engine_cleanup(Engine *engine) {
if (!engine) return;
if (engine->client) {
jack_client_close(engine->client);
engine->client = NULL;
}
}
int engine_start(Engine *engine) {
if (!engine || !engine->client) return -1;
if (jack_activate(engine->client) != 0) {
fprintf(stderr, "Failed to activate JACK client\n");
return -1;
}
engine->running = true;
return 0;
}
void engine_stop(Engine *engine) {
if (!engine || !engine->client) return;
engine->running = false;
jack_deactivate(engine->client);
}
const char* clip_state_to_string(ClipState state) {
switch (state) {
case CLIP_EMPTY: return "Empty";
case CLIP_RECORDING: return "Recording";
case CLIP_LOOPING: return "Looping";
case CLIP_STOPPED: return "Stopped";
default: return "Unknown";
}
}
uint8_t clip_state_to_velocity(ClipState state) {
switch (state) {
case CLIP_EMPTY: return 0;
case CLIP_RECORDING: return 64;
case CLIP_LOOPING: return 127;
case CLIP_STOPPED: return 32;
default: return 0;
}
}
const char* quantize_mode_to_string(QuantizeMode mode) {
switch (mode) {
case QUANTIZE_OFF: return "Off";
case QUANTIZE_BEAT: return "Beat";
case QUANTIZE_BAR: return "Bar";
default: return "Unknown";
}
}