Skip to content

Support N MIDI inputs, each with its own queue#3

Open
jeudine wants to merge 11 commits into
mainfrom
dev_multi_midi_inputs
Open

Support N MIDI inputs, each with its own queue#3
jeudine wants to merge 11 commits into
mainfrom
dev_multi_midi_inputs

Conversation

@jeudine

@jeudine jeudine commented Jun 16, 2026

Copy link
Copy Markdown
Member

Summary

Generalizes MIDI input from a single optional port to N independent inputs, each with its own message queue and consumer thread, and gives handle_input a direct MIDI forwarding channel.

  • run() now takes midi_in: Vec<MidiInParam> instead of Option<MidiInParam> (empty vec = standalone/no input).
  • Conductor::handle_input / Context::handle_input gain a 0-based input_id (matching the input's position in the Vec) so messages can be routed per source.
  • At most one input is the clock/transport source: the first one marked slave. Extra slave inputs are treated as message-only, with a warning.
  • One consumer thread per input, each draining its own queue.

Dual-channel handle_input

Conductor::handle_input now returns an InputResponse with two independent channels:

  • instructions — processed by the MidiController (buffered / step-scheduled), executed only while running and dropped while paused.
  • messages — forwarded straight to the MIDI output, bypassing the controller, sent always (including while paused).

This lets a conductor echo/transform incoming MIDI immediately and even while paused, without the controller's clock-quantized note buffering.

Correctness / cleanup

  • Fixed a condvar lost-wakeup in the consumer threads and the slave loop (predicate loop + drain-into-local via mem::take so the real-time midir callback is never blocked while the conductor runs).
  • Introduced a NotifyQueue struct to replace the (Arc<Mutex<..>>, Arc<Condvar>) tuple.
  • Removed the dead pause field from Context.
  • Added MidiMessage::is_transport; added slave-mode multi-input and pause-forwarding tests.

Breaking changes

  • run() signature: Option<MidiInParam> -> Vec<MidiInParam>.
  • Conductor::handle_input gains an input_id: usize parameter and now returns InputResponse instead of Vec<Instruction>.

Testing

  • cargo fmt --all --check, cargo clippy --workspace (all-features + no-default-features, -D warnings)
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --all-features
  • cargo test --workspace --all-features (unit, integration, doctests)

🤖 Generated with Claude Code

jeudine and others added 8 commits June 9, 2026 21:03
Replace the single optional MIDI input with a list of inputs. Each input
gets its own queue, condvar and midir connection, plus a dedicated consumer
thread that drains only that queue.

- run() now takes Vec<MidiInParam> (empty = standalone/no input)
- Conductor::handle_input and Context::handle_input gain an input_id (0-based
  index of the source input)
- Slave mode: the first input marked slave is the clock/transport source;
  the others are message-only inputs
- Per-input port prompt names the input being selected
- input_test now drives multiple independent inputs and asserts per-input
  routing via input-dependent transposition
- Patch mseq_core/mseq_tracks to local paths so the workspace builds against
  the new (breaking) core API during development

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a test asserting that in slave mode with multiple inputs, only the clock
of the slave input (input 0) advances the step: clocks arriving on a non-slave
input are dropped at routing and never reach the conductor or move the step.

Extract the transport/channel classification into MidiMessage::is_transport so
the production input callback and the test share the same routing logic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Only one clock/transport source is supported, so log a warning when more than
one input has `slave: true`; the first one is used and the rest become
message-only inputs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trigger the CI workflow on push to any branch instead of only main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Declare mseq_core and mseq_tracks with both a version and a local path so the
workspace builds against the local sources during development while still
publishing proper version requirements. Removes the patch.crates-io section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Consumer threads: park on the queue's own mutex/condvar (the pair the
  midir callback notifies) with a predicate loop, so messages that arrive
  before parking can't be lost; drain via an O(1) swap so the real-time
  callback isn't blocked while the conductor runs.
- run_slave: drain the transport queue, release it, then apply transport
  under the run lock alone, removing the only place these locks nested.
- Replace the (Arc<Mutex<InputQueue>>, Arc<Condvar>) tuple with a named
  NotifyQueue struct (queue/condvar fields, new()/push() helpers).
- Remove the unused Context::pause field and its dead branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jeudine jeudine requested a review from feldspath June 16, 2026 21:33
@jeudine jeudine added the enhancement New feature or request label Jun 16, 2026
@jeudine jeudine self-assigned this Jun 16, 2026
jeudine and others added 3 commits June 16, 2026 23:37
Update the README to match the new multi-input API: the multiple-inputs
feature, an "input_id" note on handle_input, a "MIDI Inputs" section, and
the corrected usage example (handle_input gains input_id, midi_in is a Vec).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously process_pre_tick and handle_input called the conductor while
paused but discarded the returned instructions. Always execute them now;
pausing only freezes the step counter (step-driven tracks hold position),
it no longer silences update/handle_input output.

Update the Conductor::update, Conductor::handle_input and Context::pause
docs to describe the new pause semantics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Conductor::handle_input now returns an InputResponse with two channels:
- instructions: processed by the MIDI controller (buffered/step-scheduled),
  executed only while running and dropped while paused.
- messages: forwarded straight to the MIDI output, bypassing the controller,
  sent always including while paused.

This lets a conductor echo/transform incoming MIDI immediately and even while
paused, without the controller's clock-quantized note buffering.

Also revert update's pause behavior to dropping its instructions while paused
(undoing the previous "keep instructions while paused" change for update), and
update the related docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant