You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit db7eb99
Browse filesBrowse the repository at this point in the historyBrowse files
Let the outputs be changed without restarting anything
Phase 5's exit criterion is "launch to synced output without documentation, a text
editor, or a restart", so the window will have to switch Link on, point OSC
somewhere and pick a MIDI port while the app is open — and the transports were
built once from a config and never touched again. This makes them settable, and
gives the output thread the same way in that the tracker already has.
**Link is now always built, whether or not it is switched on**, and that is the
load-bearing decision here. `BeatEngine::setHostTimeSource` is handed the session
and the audio thread reads it every hop (§4.3); building it on demand would mean
destroying one under a running audio thread the first time an operator switched
Link off, and no ordering makes that safe. Built once, switched on and off, never
replaced — so `OutputRunner::hostTimeClock()` is stable for the runner's life and
that hazard cannot occur. It costs nothing idle: Link's own header says
construction "does not touch the network; nothing is visible to peers until the
session is enabled", and a publisher with no targets sends to nobody. MIDI is the
exception and stays on demand, because it is a named device rather than a switch.
Switching Link on while stopped says what to do rather than doing it: the session
joins at startOutputs, so ticking a box before Start does not put takt4 in front of
peers before it is tracking anything. Switching it off and on again resends the
tempo on the next beat, because a rejoined session knows nothing and the
"unchanged, do not resend" rule would otherwise leave peers waiting for a change.
`OutputCommand` and `OutputRunner::post` are `engine::Command` and
`BeatEngine::post` again, for the same reason: the transports belong to the output
thread, and a caller must not reach into something another thread is using. Posting
while stopped applies immediately instead — an operator ticking Link with nothing
running should not have to press Start to find out whether it took. The runner owns
the `Transports` now rather than borrowing them, which turns "one thread touches
these" from a comment into something the type system enforces: `transports()` hands
back a const reference, and the mutating members are not on it.
A MIDI port that will not open is the failure that actually happens. `Transports`
builds the new port before tearing the old one down, so a bad name leaves a working
clock exactly where it was; `OutputRunner` catches it and keeps the message in
`lastError()`, because the transports carrying on is precisely what makes the
failure invisible otherwise. A port opened mid-set starts its clock from now rather
than from when the outputs started — `advance` would otherwise try to emit every
tick of the intervening set at once.
`OscPublisher::clearTargets` forgets the published state along with the targets.
Only changed values are sent between beats, so a target typed in mid-set would
otherwise wait for the tempo to move before learning what it is.
Eight more tests: that the Link session is the same object across a switch, that
switching on before start does not join, that replacing OSC targets resends the
state, that a bad MIDI port leaves the transports usable and is reported rather
than swallowed, and that a change posted while running actually travels.
174 tests with the UI off, 190 with it on. `track` over the excerpt still ends
"499 frames, 21 beats (5 downbeats), ending at 127.7 BPM in 4/4, locked", and a
live run with --link and --osc still sends and shuts down clean.
0 commit comments