#include "transport.h" #include #include #include 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 0; 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"; } }