Files
jack-looper/transport.c
Loic Coenen f3da43f4db fix: add missing UndoAction fields and null checks to prevent memory corruption
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
2026-05-01 23:13:04 +00:00

243 lines
9.1 KiB
C

#include "transport.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
void transport_init(Transport *transport, jack_nframes_t sample_rate) {
if (!transport) return;
memset(transport, 0, sizeof(Transport));
transport->state = TRANSPORT_STOPPED;
transport->clock_source = CLOCK_SOURCE_INTERNAL;
transport->bpm = DEFAULT_BPM;
transport->sample_rate = sample_rate;
transport->samples_per_beat = (sample_rate * 60.0) / DEFAULT_BPM;
transport->sample_accumulator = 0.0;
// Initialize atomic mirrors
atomic_store(&transport->state_atomic, TRANSPORT_STOPPED);
atomic_store(&transport->clock_count_atomic, 0);
atomic_store(&transport->beat_position_atomic, 0);
atomic_store(&transport->bar_position_atomic, 0);
atomic_store(&transport->sample_position_atomic, 0);
atomic_store(&transport->clock_source_atomic, CLOCK_SOURCE_INTERNAL);
transport->bpm_atomic = DEFAULT_BPM;
atomic_store(&transport->bpm_atomic_raw, (unsigned int)(DEFAULT_BPM * 100.0));
}
void transport_cleanup(Transport *transport) {
// Nothing to free currently
(void)transport;
}
void transport_play(Transport *transport) {
if (!transport) return;
transport->state = TRANSPORT_PLAYING;
atomic_store(&transport->state_atomic, TRANSPORT_PLAYING);
// If starting from stopped, reset position
if (transport->clock_count == 0 && transport->sample_position == 0) {
transport->sample_accumulator = 0.0;
}
}
void transport_pause(Transport *transport) {
if (!transport) return;
transport->state = TRANSPORT_PAUSED;
atomic_store(&transport->state_atomic, TRANSPORT_PAUSED);
}
void transport_stop(Transport *transport) {
if (!transport) return;
transport->state = TRANSPORT_STOPPED;
transport->clock_count = 0;
transport->beat_position = 0;
transport->bar_position = 0;
transport->sample_position = 0;
transport->sample_accumulator = 0.0;
atomic_store(&transport->state_atomic, TRANSPORT_STOPPED);
atomic_store(&transport->clock_count_atomic, 0);
atomic_store(&transport->beat_position_atomic, 0);
atomic_store(&transport->bar_position_atomic, 0);
atomic_store(&transport->sample_position_atomic, 0);
}
void transport_toggle_play(Transport *transport) {
if (!transport) return;
if (transport->state == TRANSPORT_PLAYING) {
transport_pause(transport);
} else {
transport_play(transport);
}
}
void transport_set_clock_source(Transport *transport, ClockSource source) {
if (!transport) return;
transport->clock_source = source;
atomic_store(&transport->clock_source_atomic, source);
// Reset position when switching sources
transport->clock_count = 0;
transport->beat_position = 0;
transport->bar_position = 0;
transport->sample_position = 0;
transport->sample_accumulator = 0.0;
}
ClockSource transport_get_clock_source(Transport *transport) {
if (!transport) return CLOCK_SOURCE_INTERNAL;
return transport->clock_source;
}
void transport_set_bpm(Transport *transport, double bpm) {
if (!transport || bpm < 1.0 || bpm > 999.0) return;
transport->bpm = bpm;
transport->samples_per_beat = (transport->sample_rate * 60.0) / bpm;
transport->bpm_atomic = bpm;
atomic_store(&transport->bpm_atomic_raw, (unsigned int)(bpm * 100.0));
}
double transport_get_bpm(Transport *transport) {
if (!transport) return DEFAULT_BPM;
return transport->bpm;
}
int transport_process(Transport *transport, jack_nframes_t nframes,
void *midi_clock_in_buf, void *midi_clock_out_buf) {
if (!transport) return -1; // ADD THIS
int clock_ticks_generated = 0;
if (transport->clock_source == CLOCK_SOURCE_MIDI) {
// Slave mode: process incoming MIDI clock
jack_midi_event_t midi_event;
jack_nframes_t event_index = 0;
while (jack_midi_event_get(&midi_event, midi_clock_in_buf, event_index) == 0) {
event_index++;
uint8_t *data = midi_event.buffer;
uint8_t status = data[0];
if (status == 0xF8) { // MIDI Clock
transport->clock_count++;
transport->sample_position =
(transport->clock_count * transport->sample_rate * 4) /
(MIDI_CLOCKS_PER_BEAT * BEATS_PER_BAR);
atomic_store(&transport->clock_count_atomic, transport->clock_count);
atomic_store(&transport->sample_position_atomic, transport->sample_position);
if (transport->clock_count % MIDI_CLOCKS_PER_BEAT == 0) {
transport->beat_position =
(transport->beat_position + 1) % BEATS_PER_BAR;
atomic_store(&transport->beat_position_atomic, transport->beat_position);
if (transport->beat_position == 0) {
transport->bar_position++;
atomic_store(&transport->bar_position_atomic, transport->bar_position);
}
}
} else if (status == 0xFA) { // MIDI Start
transport->state = TRANSPORT_PLAYING;
transport->clock_count = 0;
transport->beat_position = 0;
transport->bar_position = 0;
transport->sample_position = 0;
atomic_store(&transport->state_atomic, TRANSPORT_PLAYING);
atomic_store(&transport->clock_count_atomic, 0);
atomic_store(&transport->beat_position_atomic, 0);
atomic_store(&transport->bar_position_atomic, 0);
atomic_store(&transport->sample_position_atomic, 0);
} else if (status == 0xFC) { // MIDI Stop
transport->state = TRANSPORT_STOPPED;
atomic_store(&transport->state_atomic, TRANSPORT_STOPPED);
} else if (status == 0xFB) { // MIDI Continue
transport->state = TRANSPORT_PLAYING;
atomic_store(&transport->state_atomic, TRANSPORT_PLAYING);
}
}
} else {
// Master mode: generate internal clock
if (transport->state == TRANSPORT_PLAYING) {
for (jack_nframes_t i = 0; i < nframes; i++) {
transport->sample_accumulator += 1.0;
if (transport->sample_accumulator >= transport->samples_per_beat / MIDI_CLOCKS_PER_BEAT) {
transport->sample_accumulator -= transport->samples_per_beat / MIDI_CLOCKS_PER_BEAT;
// Generate MIDI clock tick
if (midi_clock_out_buf) {
uint8_t clock_msg[1] = {0xF8};
if (jack_midi_event_write(midi_clock_out_buf, i, clock_msg, 1) != 0) {
fprintf(stderr, "Failed to write MIDI clock\n");
}
}
transport->clock_count++;
transport->sample_position =
(transport->clock_count * transport->sample_rate * 4) /
(MIDI_CLOCKS_PER_BEAT * BEATS_PER_BAR);
atomic_store(&transport->clock_count_atomic, transport->clock_count);
atomic_store(&transport->sample_position_atomic, transport->sample_position);
if (transport->clock_count % MIDI_CLOCKS_PER_BEAT == 0) {
transport->beat_position =
(transport->beat_position + 1) % BEATS_PER_BAR;
atomic_store(&transport->beat_position_atomic, transport->beat_position);
if (transport->beat_position == 0) {
transport->bar_position++;
atomic_store(&transport->bar_position_atomic, transport->bar_position);
}
}
clock_ticks_generated++;
}
}
}
}
return clock_ticks_generated;
}
void transport_reset(Transport *transport) {
if (!transport) return;
transport->clock_count = 0;
transport->beat_position = 0;
transport->bar_position = 0;
transport->sample_position = 0;
transport->sample_accumulator = 0.0;
atomic_store(&transport->clock_count_atomic, 0);
atomic_store(&transport->beat_position_atomic, 0);
atomic_store(&transport->bar_position_atomic, 0);
atomic_store(&transport->sample_position_atomic, 0);
}
const char* transport_state_to_string(TransportState state) {
switch (state) {
case TRANSPORT_STOPPED: return "Stopped";
case TRANSPORT_PLAYING: return "Playing";
case TRANSPORT_PAUSED: return "Paused";
default: return "Unknown";
}
}
const char* clock_source_to_string(ClockSource source) {
switch (source) {
case CLOCK_SOURCE_INTERNAL: return "Internal";
case CLOCK_SOURCE_MIDI: return "MIDI";
default: return "Unknown";
}
}