// cppcheck-suppress missingIncludeSystem #include "looper.h" #include "channel.h" #include "midi.h" <<<<<<< HEAD #include "wav.h" #include "ringbuffer.h" #include "pipe.h" #include #include ======= #include "queue.h" >>>>>>> 3-integrate-carla #include #include #include #include #include #include #include #include #include <<<<<<< HEAD #include #include "queue.h" #include "command.h" /* Global command queues */ spsc_queue_t cmd_queue; spsc_queue_t cmd_queue_main_midi; spsc_queue_t cmd_queue_main_fifo; ======= #include #include >>>>>>> 3-integrate-carla #define STATUS_FIFO "/tmp/looper_status" /* writer status fd */ static int status_fd = -1; static void looper_write_status(void) { <<<<<<< HEAD if (status_fd < 0) return; char buf[256]; for (int ch = 0; ch < MAX_CHANNELS; ch++) { if (!atomic_load(&channels[ch].active)) continue; int state_val = atomic_load(&channels[ch].state); const char *state_str; switch (state_val) { case STATE_IDLE: state_str = "IDLE"; break; case STATE_RECORD: state_str = "RECORD"; break; case STATE_LOOPING: state_str = "LOOPING"; break; case STATE_PAUSED: state_str = "PAUSED"; break; default: state_str = "UNKNOWN"; } int n = snprintf(buf, sizeof(buf), "CH=%d SC=%d STATE=%s\n", ch, 0, state_str); if (n > 0) { int ret = write(status_fd, buf, n); (void)ret; } } ======= int fd = open(STATUS_FIFO, O_WRONLY | O_NONBLOCK); if (fd < 0) return; struct channel_t *cur = get_channels_array(); int cap = atomic_load(&channel_capacity); char buf[256]; for (int ch = 0; ch < cap; ch++) { if (!atomic_load(&cur[ch].active)) continue; int sc_idx = atomic_load(&cur[ch].current_scene); int state = atomic_load(&cur[ch].scenes[sc_idx].state); const char *state_str; switch (state) { case STATE_IDLE: state_str = "IDLE"; break; case STATE_RECORD: state_str = "RECORD"; break; case STATE_LOOPING: state_str = "LOOPING"; break; case STATE_PAUSED: state_str = "PAUSED"; break; default: state_str = "UNKNOWN"; } int n = snprintf(buf, sizeof(buf), "CH=%d SC=%d STATE=%s\n", ch, sc_idx, state_str); if (n > 0) { int ret = write(fd, buf, n); (void)ret; } } close(fd); >>>>>>> 3-integrate-carla } /* Global state (shared across files) */ struct channel_t channels[MAX_CHANNELS]; atomic_int channel_count = 0; atomic_int channel_capacity = MAX_CHANNELS; int next_channel_id = 1; atomic_int cmd_add = 0; atomic_int cmd_remove = 0; atomic_int cmd_load = 0; atomic_int cmd_save = 0; jack_port_t *midi_control_port = NULL; jack_port_t *midi_clock_port = NULL; atomic_int control_key_active = 0; atomic_int bind_channel = 0; /* Deferred removal index (1 second grace) */ static int pending_unregister_idx = -1; /* writer thread function and sample rate holder */ static void *writer_thread(void *arg); static int global_sample_rate = 0; /* execute a single command (called from looper_process_commands) */ static void exec_command(command_t cmd, jack_client_t *client) { int ch = cmd.channel; if (ch < 0) ch = 0; switch (cmd.type) { case CMD_CYCLE: { int state = atomic_load(&channels[ch].state); switch (state) { case STATE_IDLE: atomic_store(&channels[ch].state, STATE_RECORD); break; case STATE_RECORD: atomic_store(&channels[ch].state, STATE_LOOPING); break; case STATE_LOOPING: atomic_store(&channels[ch].state, STATE_PAUSED); break; case STATE_PAUSED: atomic_store(&channels[ch].state, STATE_LOOPING); break; } atomic_store(&channels[ch].prev_state, -1); break; } case CMD_STOP: atomic_store(&channels[ch].state, STATE_IDLE); atomic_store(&channels[ch].prev_state, -1); break; case CMD_ADD_CHANNEL: case CMD_ADD_MIDI_CHANNEL: { int idx; for (idx = 0; idx < MAX_CHANNELS; idx++) if (!channels[idx].active) break; if (idx < MAX_CHANNELS) channel_add(client, idx); break; } case CMD_REMOVE_CHANNEL: { int remove_idx = -1; for (int idx = 1; idx < MAX_CHANNELS; idx++) if (channels[idx].active) remove_idx = idx; if (remove_idx != -1) { channel_remove(client, remove_idx); pending_unregister_idx = remove_idx; } break; } case CMD_BIND_CHANNEL: atomic_store(&bind_channel, cmd.data); break; case CMD_UNBIND: atomic_store(&bind_channel, 0); break; case CMD_LOAD: atomic_store(&cmd_load, 1); break; case CMD_SAVE: atomic_store(&cmd_save, 1); break; case CMD_ADD_SCENE: case CMD_REMOVE_SCENE: case CMD_NEXT_SCENE: case CMD_PREV_SCENE: break; default: break; } } /* ---------------------------------------------------------------- * process callback * ---------------------------------------------------------------- */ int process_callback(jack_nframes_t nframes, void *arg) { (void)arg; if (midi_control_port) { void *midi_ctrl_buf = jack_port_get_buffer(midi_control_port, nframes); if (midi_ctrl_buf) { midi_handle_events(midi_ctrl_buf, nframes); } } /* process each active channel */ for (int c = 0; c < MAX_CHANNELS; c++) { if (!atomic_load(&channels[c].active)) continue; /* Guard against NULL ports (e.g. if port registration failed) */ if (!channels[c].audio_in || !channels[c].audio_out) { fprintf(stderr, "WARN: channel %d has NULL audio port(s), skipping\n", c); continue; } const jack_default_audio_sample_t *in = (const jack_default_audio_sample_t *)jack_port_get_buffer( channels[c].audio_in, nframes); jack_default_audio_sample_t *out = (jack_default_audio_sample_t *)jack_port_get_buffer( channels[c].audio_out, nframes); if (!out) continue; int state = atomic_load(&channels[c].state); if (state != atomic_load(&channels[c].prev_state)) { switch (state) { case STATE_RECORD: atomic_store(&channels[c].record_pos, 0); atomic_store(&channels[c].loop_count, 0); break; case STATE_LOOPING: if (atomic_load(&channels[c].prev_state) == STATE_RECORD && atomic_load(&channels[c].record_pos) > 0) atomic_store(&channels[c].loop_count, atomic_load(&channels[c].record_pos)); atomic_store(&channels[c].playback_pos, 0); break; default: break; } } <<<<<<< HEAD jack_nframes_t i; switch (state) { case STATE_RECORD: if (in) { float *f_out = (float *)out; const float *f_in = (const float *)in; for (i = 0; i < nframes; i++) { int rp = atomic_fetch_add(&channels[c].record_pos, 1); if (rp < LOOP_BUF_SIZE) channels[c].loop_buffer[rp] = f_in[i]; f_out[i] = f_in[i]; } } else { ======= if (active_channels[c].type == CHANNEL_MIDI) { /* MIDI channel handling */ switch (state) { case STATE_RECORD: { void *midi_in_buf = jack_port_get_buffer(active_channels[c].midi_in, nframes); if (midi_in_buf) { jack_nframes_t nevents = jack_midi_get_event_count(midi_in_buf); jack_midi_event_t ev; for (jack_nframes_t j = 0; j < nevents; j++) { if (jack_midi_event_get(&ev, midi_in_buf, j) != 0) continue; int rp = atomic_load(&sc->record_pos); if (rp < MAX_MIDI_EVENTS) { sc->loop.midi_events[rp].timestamp = ev.time; sc->loop.midi_events[rp].status = ev.buffer[0]; sc->loop.midi_events[rp].note = (ev.size > 1) ? ev.buffer[1] : 0; sc->loop.midi_events[rp].velocity = (ev.size > 2) ? ev.buffer[2] : 0; atomic_store(&sc->record_pos, rp + 1); } } /* forward incoming MIDI to output during record */ void *midi_out_buf = jack_port_get_buffer(active_channels[c].midi_out, nframes); if (midi_out_buf) { jack_midi_clear_buffer(midi_out_buf); for (jack_nframes_t j = 0; j < nevents; j++) { if (jack_midi_event_get(&ev, midi_in_buf, j) != 0) continue; jack_midi_event_write(midi_out_buf, ev.time, ev.buffer, ev.size); } } } break; } case STATE_LOOPING: { void *midi_out_buf = jack_port_get_buffer(active_channels[c].midi_out, nframes); if (midi_out_buf) { jack_midi_clear_buffer(midi_out_buf); int cnt = atomic_load(&sc->loop_count); if (cnt > 0) { for (int e = 0; e < cnt; e++) { unsigned char msg[3]; msg[0] = sc->loop.midi_events[e].status; msg[1] = sc->loop.midi_events[e].note; msg[2] = sc->loop.midi_events[e].velocity; jack_midi_event_write(midi_out_buf, 0, msg, 3); } } } break; } case STATE_PAUSED: /* no output */ break; default: /* IDLE */ { void *midi_in_buf = jack_port_get_buffer(active_channels[c].midi_in, nframes); void *midi_out_buf = jack_port_get_buffer(active_channels[c].midi_out, nframes); if (midi_in_buf && midi_out_buf) { jack_midi_clear_buffer(midi_out_buf); jack_nframes_t nevents = jack_midi_get_event_count(midi_in_buf); jack_midi_event_t ev; for (jack_nframes_t j = 0; j < nevents; j++) { if (jack_midi_event_get(&ev, midi_in_buf, j) != 0) continue; jack_midi_event_write(midi_out_buf, ev.time, ev.buffer, ev.size); } } } break; } if (state == STATE_LOOPING) { atomic_store(&sc->loop_count, atomic_load(&sc->record_pos)); } } else { /* audio channel handling */ jack_nframes_t i; switch (state) { case STATE_RECORD: if (in) { float *f_out = (float *)out; const float *f_in = (const float *)in; for (i = 0; i < nframes; i++) { int rp = atomic_load(&sc->record_pos); if (rp < LOOP_BUF_SIZE) { sc->loop.audio_buffer[rp] = f_in[i]; atomic_store(&sc->record_pos, rp + 1); } f_out[i] = f_in[i]; } } else { memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); } break; case STATE_LOOPING: { int loop_cnt = atomic_load(&sc->loop_count); if (loop_cnt > 0) { float *outf = (float *)out; int pp = atomic_load(&sc->playback_pos); for (i = 0; i < nframes; i++) { outf[i] = sc->loop.audio_buffer[pp]; pp = (pp + 1) % loop_cnt; } atomic_store(&sc->playback_pos, pp); } else { memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); } break; } case STATE_PAUSED: >>>>>>> 3-integrate-carla memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); } break; case STATE_LOOPING: int lc = atomic_load(&channels[c].loop_count); if (lc > 0) { float *outf = (float *)out; for (i = 0; i < nframes; i++) { int pp = atomic_load(&channels[c].playback_pos); outf[i] = channels[c].loop_buffer[pp]; atomic_store(&channels[c].playback_pos, (pp + 1) % lc); } } else { memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); } break; case STATE_PAUSED: memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); break; default: /* IDLE */ if (in) { memcpy(out, in, sizeof(jack_default_audio_sample_t) * nframes); } else { memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); } break; } // push loop output into save ring if saving (atomic load) RingBuf *r = (RingBuf *)atomic_load_explicit(&channels[c].save_ring, memory_order_acquire); if (r != NULL) { if (state == STATE_LOOPING && atomic_load(&channels[c].loop_count) > 0) { const float *outf = (const float *)out; ring_write(r, outf, nframes); } } atomic_store(&channels[c].prev_state, state); } /* MIDI clock events – affect channel 0 only */ if (midi_clock_port) { void *midi_clock_buf = jack_port_get_buffer(midi_clock_port, nframes); if (midi_clock_buf) { jack_nframes_t n_clock_events = jack_midi_get_event_count(midi_clock_buf); jack_midi_event_t cev; for (jack_nframes_t j = 0; j < n_clock_events; j++) { if (jack_midi_event_get(&cev, midi_clock_buf, j) != 0) continue; if (cev.size >= 1) { unsigned char msg = cev.buffer[0]; switch (msg) { case 0xFA: { int s = atomic_load(&channels[0].state); if (s == STATE_IDLE) atomic_store(&channels[0].state, STATE_RECORD); break; } case 0xFC: atomic_store(&channels[0].state, STATE_IDLE); break; case 0xFB: { int s = atomic_load(&channels[0].state); if (s == STATE_PAUSED) atomic_store(&channels[0].state, STATE_LOOPING); break; } default: break; } } } } } return 0; } /* ---------------------------------------------------------------- * shutdown callback * ---------------------------------------------------------------- */ void jack_shutdown_cb(void *arg) { (void)arg; fprintf(stderr, "JACK shutdown\n"); exit(0); } /* ---------------------------------------------------------------- * looper initialisation * ---------------------------------------------------------------- */ int looper_init(jack_client_t *client) { /* store sample rate for writer thread */ global_sample_rate = jack_get_sample_rate(client); /* create status FIFO (ignore if already exists) */ mkfifo(STATUS_FIFO, 0666); /* open the status FIFO for reading+writing so writes work even without reader */ status_fd = open(STATUS_FIFO, O_RDWR); if (status_fd < 0) { perror("open status FIFO"); } queue_init(&cmd_queue); queue_init(&cmd_queue_main_midi); queue_init(&cmd_queue_main_fifo); /* start the FIFO reader thread */ pipe_start_reader(); /* channel 0 */ channels[0].active = 1; atomic_store(&channels[0].state, STATE_IDLE); atomic_store(&channels[0].prev_state, -1); channels[0].loop_count = 0; atomic_store(&channels[0].record_pos, 0); atomic_store(&channels[0].playback_pos, 0); atomic_store_explicit(&channels[0].save_ring, NULL, memory_order_release); channels[0].audio_in = jack_port_register( client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); channels[0].audio_out = jack_port_register( client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (!channels[0].audio_in || !channels[0].audio_out) { fprintf(stderr, "Could not create audio ports for channel 0\n"); return -1; } channel_count = 1; 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 (!midi_control_port || !midi_clock_port) { fprintf(stderr, "Could not create MIDI ports\n"); return -1; } return 0; } /* ---------------------------------------------------------------- * writer thread – consumes the save ring and writes WAV file * ---------------------------------------------------------------- */ static void *writer_thread(void *arg) { struct channel_t *ch = (struct channel_t *)arg; RingBuf *ring = (RingBuf *)ch->save_ring; if (!ring) return NULL; static const char *path = "save.wav"; unsigned sr = (unsigned)global_sample_rate; if (sr == 0) sr = 48000; int lc = atomic_load(&ch->loop_count); float *outbuf = malloc((size_t)lc * sizeof(float)); if (!outbuf) { ring_destroy(ring); free(ring); ch->save_ring = NULL; return NULL; } size_t collected = 0; size_t want = (size_t)lc; while (collected < want) { size_t got = ring_read(ring, outbuf + collected, want - collected); collected += got; if (got == 0) { struct timespec req = {.tv_sec = 0, .tv_nsec = 10000000}; nanosleep(&req, NULL); } } wav_write(path, outbuf, (unsigned)lc, sr); free(outbuf); ring_destroy(ring); free(ring); atomic_store_explicit(&ch->save_ring, NULL, memory_order_release); return NULL; } /* ---------------------------------------------------------------- * main‑loop command processing * ---------------------------------------------------------------- */ void looper_process_commands(jack_client_t *client) { /* process commands from the three queues FIRST */ command_t cmd; while (queue_pop(&cmd_queue, &cmd)) exec_command(cmd, client); while (queue_pop(&cmd_queue_main_midi, &cmd)) exec_command(cmd, client); while (queue_pop(&cmd_queue_main_fifo, &cmd)) exec_command(cmd, client); /* Unregister any ports that were marked for deferred removal. By now the real‑time thread has had at least one full cycle to see the `active = 0` store. */ if (pending_unregister_idx != -1) { int idx = pending_unregister_idx; if (channels[idx].audio_in) jack_port_unregister(client, channels[idx].audio_in); if (channels[idx].audio_out) jack_port_unregister(client, channels[idx].audio_out); pending_unregister_idx = -1; } /* ---------- add channel ---------- */ if (atomic_exchange(&cmd_add, 0)) { int idx; for (idx = 0; idx < MAX_CHANNELS; idx++) if (!channels[idx].active) break; if (idx < MAX_CHANNELS) { channel_add(client, idx); } } /* ---------- remove channel ---------- */ if (atomic_exchange(&cmd_remove, 0)) { int remove_idx = -1; for (int idx = 1; idx < MAX_CHANNELS; idx++) if (channels[idx].active) remove_idx = idx; if (remove_idx != -1) { /* Mark inactive now; ports will be unregistered next round */ channel_remove(client, remove_idx); pending_unregister_idx = remove_idx; } } /* ---------- load command ---------- */ if (atomic_exchange(&cmd_load, 0)) { float *buf = NULL; unsigned frames = 0; printf("LOAD: wav_read called\n"); if (wav_read("loop.wav", &buf, &frames) == 0 && frames > 0) { printf("LOAD: success, frames=%u\n", frames); if (frames > LOOP_BUF_SIZE) frames = LOOP_BUF_SIZE; memcpy(channels[0].loop_buffer, buf, frames * sizeof(float)); atomic_store(&channels[0].loop_count, (int)frames); atomic_store(&channels[0].record_pos, 0); atomic_store(&channels[0].playback_pos, 0); atomic_store(&channels[0].state, STATE_LOOPING); atomic_store(&channels[0].prev_state, -1); free(buf); } else { fprintf(stderr, "Failed to load loop.wav\n"); printf("LOAD: FAILED\n"); } } /* ---------- save command (writer thread) ---------- */ if (atomic_exchange(&cmd_save, 0)) { int lc = atomic_load(&channels[0].loop_count); if (atomic_load(&channels[0].state) == STATE_LOOPING && lc > 0 && channels[0].save_ring == NULL) { RingBuf *ring = (RingBuf *)malloc(sizeof(RingBuf)); if (ring) { size_t sz = (size_t)lc * 2; if (ring_init(ring, sz) == 0) { atomic_store_explicit(&channels[0].save_ring, (_Atomic RingBuf *)ring, memory_order_release); pthread_t th; pthread_create(&th, NULL, writer_thread, &channels[0]); pthread_detach(th); } else { free(ring); } } } } /* write current state to status FIFO */ looper_write_status(); }