feat: refactor transport into separate module with master/slave clock support
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
This commit is contained in:
239
engine.c
239
engine.c
@@ -36,60 +36,8 @@ static int process_callback(jack_nframes_t nframes, void *arg) {
|
||||
// Clear output MIDI buffer
|
||||
jack_midi_clear_buffer(midi_out_buf);
|
||||
|
||||
// Process MIDI clock input
|
||||
jack_midi_event_t midi_event;
|
||||
jack_nframes_t event_index = 0;
|
||||
|
||||
while (jack_midi_event_get(&midi_event, midi_clock_buf, event_index) == 0) {
|
||||
event_index++;
|
||||
|
||||
uint8_t *data = midi_event.buffer;
|
||||
uint8_t status = data[0];
|
||||
|
||||
if (status == 0xF8) { // MIDI Clock
|
||||
engine->transport.clock_count++;
|
||||
engine->transport.sample_position =
|
||||
(engine->transport.clock_count * engine->sample_rate * 4) /
|
||||
(MIDI_CLOCKS_PER_BEAT * BEATS_PER_BAR);
|
||||
|
||||
// Update atomic mirrors for frontend reads
|
||||
atomic_store(&engine->transport_clock_count, engine->transport.clock_count);
|
||||
atomic_store(&engine->transport_sample_position, engine->transport.sample_position);
|
||||
|
||||
if (engine->transport.clock_count % MIDI_CLOCKS_PER_BEAT == 0) {
|
||||
engine->transport.beat_position =
|
||||
(engine->transport.beat_position + 1) % BEATS_PER_BAR;
|
||||
atomic_store(&engine->transport_beat_position, engine->transport.beat_position);
|
||||
if (engine->transport.beat_position == 0) {
|
||||
engine->transport.bar_position++;
|
||||
atomic_store(&engine->transport_bar_position, engine->transport.bar_position);
|
||||
}
|
||||
}
|
||||
} else if (status == 0xFA) { // MIDI Start
|
||||
engine->transport.rolling = true;
|
||||
engine->transport.clock_count = 0;
|
||||
engine->transport.beat_position = 0;
|
||||
engine->transport.bar_position = 0;
|
||||
engine->transport.sample_position = 0;
|
||||
atomic_store(&engine->transport_rolling, 1);
|
||||
atomic_store(&engine->transport_clock_count, 0);
|
||||
atomic_store(&engine->transport_beat_position, 0);
|
||||
atomic_store(&engine->transport_bar_position, 0);
|
||||
atomic_store(&engine->transport_sample_position, 0);
|
||||
} else if (status == 0xFC) { // MIDI Stop
|
||||
engine->transport.rolling = false;
|
||||
atomic_store(&engine->transport_rolling, 0);
|
||||
} else if (status == 0xFB) { // MIDI Continue
|
||||
engine->transport.rolling = true;
|
||||
atomic_store(&engine->transport_rolling, 1);
|
||||
}
|
||||
|
||||
// Pass through clock messages
|
||||
if (jack_midi_event_write(midi_out_buf, midi_event.time,
|
||||
midi_event.buffer, midi_event.size) != 0) {
|
||||
fprintf(stderr, "Failed to write MIDI event\n");
|
||||
}
|
||||
}
|
||||
// Process transport (handles both master and slave clock)
|
||||
transport_process(engine->transport, nframes, midi_clock_buf, midi_out_buf);
|
||||
|
||||
// Process control channel MIDI input (clip triggers)
|
||||
event_index = 0;
|
||||
@@ -108,8 +56,9 @@ static int process_callback(jack_nframes_t nframes, void *arg) {
|
||||
|
||||
// Read quantize mode atomically (frontend may update it)
|
||||
QuantizeMode current_quantize = (QuantizeMode)atomic_load(&engine->quantize_mode_atomic);
|
||||
TransportState transport_state = (TransportState)atomic_load(&engine->transport->state_atomic);
|
||||
|
||||
if (current_quantize != QUANTIZE_OFF && engine->transport.rolling) {
|
||||
if (current_quantize != QUANTIZE_OFF && transport_state == TRANSPORT_PLAYING) {
|
||||
// Queue for quantization
|
||||
jack_nframes_t trigger_time = midi_event.time;
|
||||
queue_trigger(engine, clip_index, false, trigger_time);
|
||||
@@ -150,8 +99,9 @@ static int process_callback(jack_nframes_t nframes, void *arg) {
|
||||
|
||||
// Read quantize mode atomically (frontend may update it)
|
||||
QuantizeMode current_quantize = (QuantizeMode)atomic_load(&engine->quantize_mode_atomic);
|
||||
TransportState transport_state = (TransportState)atomic_load(&engine->transport->state_atomic);
|
||||
|
||||
if (current_quantize != QUANTIZE_OFF && engine->transport.rolling) {
|
||||
if (current_quantize != QUANTIZE_OFF && transport_state == TRANSPORT_PLAYING) {
|
||||
// Queue for quantization
|
||||
jack_nframes_t trigger_time = midi_event.time;
|
||||
queue_trigger(engine, scene_index, true, trigger_time);
|
||||
@@ -207,34 +157,8 @@ static void shutdown_callback(void *arg) {
|
||||
|
||||
// Get the next quantization boundary frame
|
||||
static jack_nframes_t get_next_quantize_frame(Engine *engine, jack_nframes_t current_frame) {
|
||||
if (!engine->transport.rolling || engine->quantize_mode == QUANTIZE_OFF) {
|
||||
return current_frame;
|
||||
}
|
||||
|
||||
// Calculate frames per beat
|
||||
jack_nframes_t frames_per_beat = engine->sample_rate * 60 / 120; // Assume 120 BPM from clock
|
||||
if (engine->transport.clock_count > 0) {
|
||||
// Derive from actual clock
|
||||
frames_per_beat = (engine->transport.sample_position * MIDI_CLOCKS_PER_BEAT) /
|
||||
(engine->transport.clock_count / MIDI_CLOCKS_PER_BEAT + 1);
|
||||
}
|
||||
|
||||
jack_nframes_t frames_per_bar = frames_per_beat * BEATS_PER_BAR;
|
||||
|
||||
// Current position in frames
|
||||
jack_nframes_t current_pos = engine->transport.sample_position + current_frame;
|
||||
|
||||
if (engine->quantize_mode == QUANTIZE_BEAT) {
|
||||
// Next beat boundary
|
||||
jack_nframes_t beat_frames = frames_per_beat;
|
||||
jack_nframes_t next_beat = ((current_pos / beat_frames) + 1) * beat_frames;
|
||||
return next_beat - engine->transport.sample_position;
|
||||
} else { // QUANTIZE_BAR
|
||||
// Next bar boundary
|
||||
jack_nframes_t bar_frames = frames_per_bar;
|
||||
jack_nframes_t next_bar = ((current_pos / bar_frames) + 1) * bar_frames;
|
||||
return next_bar - engine->transport.sample_position;
|
||||
}
|
||||
if (!engine->transport) return current_frame;
|
||||
return transport_get_next_quantize_frame(engine->transport, current_frame, engine->quantize_mode);
|
||||
}
|
||||
|
||||
// Queue a trigger for quantization
|
||||
@@ -261,7 +185,9 @@ void queue_trigger(Engine *engine, int clip_index, bool is_scene, jack_nframes_t
|
||||
|
||||
// Process queued triggers at quantization boundaries
|
||||
static void process_queued_triggers(Engine *engine, jack_nframes_t nframes) {
|
||||
if (!engine->queued_triggers || !engine->transport.rolling) return;
|
||||
if (!engine->queued_triggers) return;
|
||||
TransportState transport_state = (TransportState)atomic_load(&engine->transport->state_atomic);
|
||||
if (transport_state != TRANSPORT_PLAYING) return;
|
||||
|
||||
jack_nframes_t quantize_frame = get_next_quantize_frame(engine, 0);
|
||||
|
||||
@@ -468,23 +394,14 @@ void engine_process_commands(Engine *engine) {
|
||||
action.type = ACTION_RESET_TRANSPORT;
|
||||
action.index = 0;
|
||||
action.value = 0;
|
||||
action.previous_rolling = engine->transport.rolling;
|
||||
action.previous_clock_count = engine->transport.clock_count;
|
||||
action.previous_beat_position = engine->transport.beat_position;
|
||||
action.previous_bar_position = engine->transport.bar_position;
|
||||
action.previous_sample_position = engine->transport.sample_position;
|
||||
action.previous_rolling = (engine->transport->state == TRANSPORT_PLAYING);
|
||||
action.previous_clock_count = engine->transport->clock_count;
|
||||
action.previous_beat_position = engine->transport->beat_position;
|
||||
action.previous_bar_position = engine->transport->bar_position;
|
||||
action.previous_sample_position = engine->transport->sample_position;
|
||||
engine_push_undo_action(engine, &action);
|
||||
|
||||
engine->transport.rolling = false;
|
||||
engine->transport.clock_count = 0;
|
||||
engine->transport.beat_position = 0;
|
||||
engine->transport.bar_position = 0;
|
||||
engine->transport.sample_position = 0;
|
||||
atomic_store(&engine->transport_rolling, 0);
|
||||
atomic_store(&engine->transport_clock_count, 0);
|
||||
atomic_store(&engine->transport_beat_position, 0);
|
||||
atomic_store(&engine->transport_bar_position, 0);
|
||||
atomic_store(&engine->transport_sample_position, 0);
|
||||
transport_reset(engine->transport);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -497,6 +414,31 @@ void engine_process_commands(Engine *engine) {
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TRANSPORT_PLAY:
|
||||
transport_play(engine->transport);
|
||||
break;
|
||||
|
||||
case CMD_TRANSPORT_PAUSE:
|
||||
transport_pause(engine->transport);
|
||||
break;
|
||||
|
||||
case CMD_TRANSPORT_STOP:
|
||||
transport_stop(engine->transport);
|
||||
break;
|
||||
|
||||
case CMD_TRANSPORT_TOGGLE_PLAY:
|
||||
transport_toggle_play(engine->transport);
|
||||
break;
|
||||
|
||||
case CMD_SET_CLOCK_SOURCE:
|
||||
transport_set_clock_source(engine->transport, (ClockSource)cmd.index);
|
||||
break;
|
||||
|
||||
case CMD_SET_BPM:
|
||||
transport_set_bpm(engine->transport, (double)cmd.value / 100.0);
|
||||
break;
|
||||
}
|
||||
|
||||
read++;
|
||||
}
|
||||
|
||||
@@ -580,16 +522,16 @@ void engine_undo(Engine *engine) {
|
||||
}
|
||||
|
||||
case ACTION_RESET_TRANSPORT: {
|
||||
engine->transport.rolling = action->previous_rolling;
|
||||
engine->transport.clock_count = action->previous_clock_count;
|
||||
engine->transport.beat_position = action->previous_beat_position;
|
||||
engine->transport.bar_position = action->previous_bar_position;
|
||||
engine->transport.sample_position = action->previous_sample_position;
|
||||
atomic_store(&engine->transport_rolling, action->previous_rolling ? 1 : 0);
|
||||
atomic_store(&engine->transport_clock_count, action->previous_clock_count);
|
||||
atomic_store(&engine->transport_beat_position, action->previous_beat_position);
|
||||
atomic_store(&engine->transport_bar_position, action->previous_bar_position);
|
||||
atomic_store(&engine->transport_sample_position, action->previous_sample_position);
|
||||
engine->transport->state = action->previous_rolling ? TRANSPORT_PLAYING : TRANSPORT_STOPPED;
|
||||
engine->transport->clock_count = action->previous_clock_count;
|
||||
engine->transport->beat_position = action->previous_beat_position;
|
||||
engine->transport->bar_position = action->previous_bar_position;
|
||||
engine->transport->sample_position = action->previous_sample_position;
|
||||
atomic_store(&engine->transport->state_atomic, engine->transport->state);
|
||||
atomic_store(&engine->transport->clock_count_atomic, action->previous_clock_count);
|
||||
atomic_store(&engine->transport->beat_position_atomic, action->previous_beat_position);
|
||||
atomic_store(&engine->transport->bar_position_atomic, action->previous_bar_position);
|
||||
atomic_store(&engine->transport->sample_position_atomic, action->previous_sample_position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -690,16 +632,16 @@ void engine_redo(Engine *engine) {
|
||||
}
|
||||
|
||||
case ACTION_RESET_TRANSPORT: {
|
||||
engine->transport.rolling = true;
|
||||
engine->transport.clock_count = 0;
|
||||
engine->transport.beat_position = 0;
|
||||
engine->transport.bar_position = 0;
|
||||
engine->transport.sample_position = 0;
|
||||
atomic_store(&engine->transport_rolling, 1);
|
||||
atomic_store(&engine->transport_clock_count, 0);
|
||||
atomic_store(&engine->transport_beat_position, 0);
|
||||
atomic_store(&engine->transport_bar_position, 0);
|
||||
atomic_store(&engine->transport_sample_position, 0);
|
||||
engine->transport->state = TRANSPORT_PLAYING;
|
||||
engine->transport->clock_count = 0;
|
||||
engine->transport->beat_position = 0;
|
||||
engine->transport->bar_position = 0;
|
||||
engine->transport->sample_position = 0;
|
||||
atomic_store(&engine->transport->state_atomic, TRANSPORT_PLAYING);
|
||||
atomic_store(&engine->transport->clock_count_atomic, 0);
|
||||
atomic_store(&engine->transport->beat_position_atomic, 0);
|
||||
atomic_store(&engine->transport->bar_position_atomic, 0);
|
||||
atomic_store(&engine->transport->sample_position_atomic, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -726,20 +668,19 @@ int engine_init(Engine *engine, const char *client_name) {
|
||||
command_queue_init(&engine->command_queue);
|
||||
|
||||
// Initialize atomic state mirrors
|
||||
atomic_store(&engine->transport_rolling, 0);
|
||||
atomic_store(&engine->transport_clock_count, 0);
|
||||
atomic_store(&engine->transport_beat_position, 0);
|
||||
atomic_store(&engine->transport_bar_position, 0);
|
||||
atomic_store(&engine->transport_sample_position, 0);
|
||||
atomic_store(&engine->quantize_mode_atomic, (int)QUANTIZE_OFF);
|
||||
atomic_store(&engine->quantize_threshold_atomic, 0);
|
||||
|
||||
// Initialize transport
|
||||
engine->transport.rolling = false;
|
||||
engine->transport.clock_count = 0;
|
||||
engine->transport.beat_position = 0;
|
||||
engine->transport.bar_position = 0;
|
||||
engine->transport.sample_position = 0;
|
||||
engine->transport = (Transport *)calloc(1, sizeof(Transport));
|
||||
if (!engine->transport) {
|
||||
// Cleanup on allocation failure
|
||||
for (int j = 0; j < MAX_CLIPS; j++) {
|
||||
free(engine->clips[j].buffer);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
transport_init(engine->transport, engine->sample_rate);
|
||||
|
||||
// Initialize clips
|
||||
for (int i = 0; i < MAX_CLIPS; i++) {
|
||||
@@ -828,6 +769,13 @@ void engine_cleanup(Engine *engine) {
|
||||
}
|
||||
engine->queued_triggers = NULL;
|
||||
|
||||
// Free transport
|
||||
if (engine->transport) {
|
||||
transport_cleanup(engine->transport);
|
||||
free(engine->transport);
|
||||
engine->transport = NULL;
|
||||
}
|
||||
|
||||
if (engine->client) {
|
||||
jack_client_close(engine->client);
|
||||
engine->client = NULL;
|
||||
@@ -896,11 +844,34 @@ void engine_set_quantize_threshold(Engine *engine, jack_nframes_t samples) {
|
||||
engine_submit_command(engine, CMD_SET_QUANTIZE_THRESHOLD, 0, samples);
|
||||
}
|
||||
|
||||
void engine_reset_transport(Engine *engine) {
|
||||
void engine_transport_play(Engine *engine) {
|
||||
if (!engine) return;
|
||||
|
||||
engine_submit_command(engine, CMD_RESET_TRANSPORT, 0, 0);
|
||||
printf("Transport reset\n");
|
||||
engine_submit_command(engine, CMD_TRANSPORT_PLAY, 0, 0);
|
||||
}
|
||||
|
||||
void engine_transport_pause(Engine *engine) {
|
||||
if (!engine) return;
|
||||
engine_submit_command(engine, CMD_TRANSPORT_PAUSE, 0, 0);
|
||||
}
|
||||
|
||||
void engine_transport_stop(Engine *engine) {
|
||||
if (!engine) return;
|
||||
engine_submit_command(engine, CMD_TRANSPORT_STOP, 0, 0);
|
||||
}
|
||||
|
||||
void engine_transport_toggle_play(Engine *engine) {
|
||||
if (!engine) return;
|
||||
engine_submit_command(engine, CMD_TRANSPORT_TOGGLE_PLAY, 0, 0);
|
||||
}
|
||||
|
||||
void engine_set_clock_source(Engine *engine, ClockSource source) {
|
||||
if (!engine) return;
|
||||
engine_submit_command(engine, CMD_SET_CLOCK_SOURCE, (int)source, 0);
|
||||
}
|
||||
|
||||
void engine_set_bpm(Engine *engine, double bpm) {
|
||||
if (!engine) return;
|
||||
engine_submit_command(engine, CMD_SET_BPM, 0, (jack_nframes_t)(bpm * 100.0));
|
||||
}
|
||||
|
||||
const char* clip_state_to_string(ClipState state) {
|
||||
|
||||
Reference in New Issue
Block a user