The landing branch already syncs host-set values to the controller before
saving state (lock-free atomics + EditorLink::sync_param); the PR's later
ControllerPending table did the same, so keep one. Keeps the PR's new
'a loaded state replaces earlier edits' assertion, which passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#304's MIDI Track Offset shifts the block schedule_notes reads, which
now carries this branch's controllers too. chase_controllers takes the
same offset, so a seek chases the controller values in effect where the
offset puts the track, not at the raw transport position. New test: with
the track 0.5 s late, starting at 1.25 s chases the sustain pedal still
down (without the offset it sent the pedal-up that only plays at 1.5 s).
Also: reset() sets the chase flag beside #301's alignment flush, the
VST3 Instance keeps #400's set_normalized beside midi_mapping, and the
mix tests keep #210's Keys plugin beside this branch's Clock and Ears.
#400 (fixes#257) made Vst3Plugin::save_state tell the edit controller
the values the host set before taking the snapshot. This branch reads
plugin state on the UI thread through EditorLink instead, and removes
Vst3Plugin::save_state, so keeping its side of that conflict would have
dropped the fix.
The values now wait in a ControllerPending table shared by the instance
and its EditorLink: one AtomicU64 slot per parameter, which set_param
fills on the audio thread without locking or allocating.
EditorLink::save_state sends the waiting values with setParamNormalized
under its lock, before it reads the controller's state. A state load
clears them, as #400's refresh did. Instance::set_normalized, which only
the old save_state used, is gone. #400's round-trip test passes, plus a
check that a loaded state replaces earlier host edits.
#327's a_new_instance_replaces_a_matching_one_in_its_slot told the two
instances apart through MixEngine::capture_states, which this branch
removes (states are read on the UI thread). The two instances now
differ in gain instead (-6 and -20 dB), and the test checks that the new
level plays and that the old instance is retired.
Also: Side::new sets #210's old_sessions field.
Applying new audio device settings replaced the player while plugin
editor windows were open: the old instances went away with it (possibly
on the audio thread, where windows cannot be closed) and their editor
windows stayed on screen orphaned. Player::close_editors closes them on
the main thread first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Setup > Playback Engine now chooses the audio system (cpal host), output
and input device, device sample rate and buffer size, kept per machine
with the UI preferences and applied by reopening the devices (refused
while recording; the transport position is kept). ui.audio_device does
the same for agents and lists every system with its devices, channels,
rates and buffer range.
Player and recorder open the chosen device, fall back to the defaults
for anything unavailable (unplugged device, unsupported rate), retry
with the device's default buffer when the chosen one is refused, and
convert 32/16-bit integer devices from and to the f32 mix through
scratch buffers allocated up front (no allocation in the callbacks).
ASIO is behind an opt-in 'asio' feature (playback and app): building it
downloads Steinberg's ASIO SDK (proprietary licence or GPLv3) and needs
LLVM, so it stays off by default. Verified on Windows with an SSL 2+:
WASAPI output/input switching and buffer 256, ASIO duplex at 128.
Docs: G-01 shrunk, hardware-parity rows, roadmap log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>