243 lines
9.1 KiB
C
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";
|
|
}
|
|
}
|