Finding a plugin for an insert took three submenus (format, vendor,
name). The send and I/O bus menus had a search field, but it had to be
clicked before typing, and it kept the last search.
- The send and I/O bus menus take typing as soon as they open, as Pro
Tools' path selectors do. The search starts empty each time; Up/Down
move through the matches, Enter picks one, Escape closes the menu.
- Insert and instrument menus start with the same search field. Typing
lists the plugins whose name or vendor matches: built-in ones first,
then Audio Units and whichever of CLAP and VST3 have been scanned,
with a Scan button for each of the others. Typing never starts a scan
(`cached()` in the CLAP and VST3 hosts returns the last scan without
scanning); opening a format's submenu still scans it.
- A click inside the insert and instrument menus no longer closes them,
so the field can be clicked; picking still does, and errors from
Bypass and "no insert" now reach the status line. The send bus menu
is no longer 600 points wide.
- Insert, send, instrument and I/O slots in the Mix window get
accessibility labels, which the tests use.
Plugins that offer no floating editor (most CLAP plugins on Windows) now
get a host window of SoundCraft's: the GUI is created embedded, parented
to a top-level HWND owned by the app window, sized from get_size, resized
on request_resize (applied by the editor's idle on the main thread, then
set_size), and torn down when the user closes the window. Floating
editors work as before; other platforms still report embedded editors
as unsupported.
Docs: plugin-parity editor rows and the roadmap log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving, bouncing and the two-minute autosave read every third-party
plugin's state through the audio callback: a command made the callback
call each plugin's save_state between two blocks and reply, while the
UI waited for up to 2 s. So:
- CLAP's clap.state and VST3's getState ran on the audio thread,
although both formats want them on the main thread.
- The callback allocated and waited on the plugins; a plugin with a
large state (a sampler) could make the audio drop out on every save
and autosave.
- Without a running callback (a stalled or lost device), every save
waited 2 s and then kept stale plugin states.
Each instance now hands Side::prepare a state reader before it leaves
for the audio thread, the way it hands over its editor handle. CLAP's
GuiLink and VST3's EditorLink (which now holds the component as well)
read the state under their lock; Audio Units get a StateLink that does
the same. Each instance cuts its link off before it is destroyed, so a
reader never calls into a dead plugin. Player::capture_states reads
them on the calling (UI) thread, and the CaptureStates command is gone.
Each host's tests read the state while another thread processes the
instance, and get nothing once it is dropped. Playback reads states
with no audio thread at all.
Imported MIDI keeps its controller events: sustain pedal and other CCs,
pitch bend, channel and poly pressure, program changes. Playback sent
instruments only note-ons and note-offs, so a piano part lost its pedal
and bends went flat.
- Plugin::midi takes any other MIDI 1.0 channel message at a sample
offset. The mix engine sends each clip's controllers at their sample,
after the Real-Time Properties delay (and transpose, for poly
pressure), on channel 1 like the notes.
- After a seek, a stop or a loop wrap, an instrument first gets Reset All
Controllers (CC 121), then the last value each controller had on its
track before the playhead. A pedal held before the start point is
still down.
- CLAP: raw MIDI events when the note port speaks the MIDI dialect.
- VST3: CCs, aftertouch and pitch bend become parameter changes at their
offset, through the plugin's IMidiMapping. It is asked once in prepare,
on the main thread, and there is one point per parameter per block.
Poly pressure is a PolyPressureEvent. Program changes have no VST3
mapping and are dropped.
- AU: MusicDeviceMIDIEvent for instruments and music effects.
- The built-in synth follows the sustain pedal, pitch bend (2 semitones
each way), Reset All Controllers and All Notes Off.
- The sampler ignores the pedal, pitch bend and Reset All Controllers
for now.
The AU check of MIDI volume lives inside the existing DLS test. Creating
two DLSMusicDevice instances at once on two threads crashes inside the
unit, with or without this change. Two instances created one after
another render in parallel fine.
Hosted plugins never learned the session's tempo or where the song was:
- CLAP got a null transport;
- VST3 got a fixed 120 BPM 4/4 context that never played and counted
processed samples as the song position;
- Audio Units had no host callbacks.
Tempo-synced delays, LFOs and arpeggiators ran at 120 BPM and ignored
the transport.
Before each block the mix engine now hands every insert, instrument and
master-fader plugin a soundcraft_dsp::Transport (Plugin::set_transport):
play and record state, tempo and meter at the block start, and the
timeline position in samples, seconds and quarter notes, with the
current bar. Built-in plugins ignore it.
- CLAP: a clap_event_transport (fixed-point beats and seconds, bar start
and number, time signature, play and record flags).
- VST3: ProcessContext tempo, time signature, project time in samples and
quarter notes, bar position, kPlaying and kRecording. Continuous time
stays the processed-sample count.
- AU: kAudioUnitProperty_HostCallbacks (beat and tempo, musical time
location, transport state 1 and 2), read from atomics the instance
owns and frees after disposing the unit.
Loop (cycle) ranges aren't reported yet: playback keeps the loop range
to itself.
- release.yml: new linux-riscv64 job, cross-compiled on ubuntu-24.04 against ports.ubuntu.com
riscv64 libs (the linux job's -dev packages incl. libasound2-dev:riscv64), CLI smoke-tested
under qemu-riscv64; wired into the release job's needs so SHA256SUMS covers it.
- packaging/linux/package.sh: CROSS_ARCH/CROSS_TARGET/CROSS_COMPILE/EMULATOR support.
- clap-host: c_char -> u8 without a cast that is a no-op (clippy error) where c_char is u8.
- Version 0.4.0 -> 0.5.0; docs list the riscv64 tarball.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Video (new standalone crate soundcraft-video):
- Pure-Rust H.264 (Baseline/Main/High), ProRes (all flavours) and Motion JPEG decoding with
ISO-BMFF demux, adapted from FilmCraft's crates; fuzzed with mutated/truncated movies
- TrackKind::Video with ClipContent::Video referencing Session.videos; file.import_video adds
the picture track and the movie's audio; relinking on open; video.offset/online/list
- Video and Video Universe windows, thumbnails on the timeline, background decoding
Third-party plugins:
- Plugin state (CLAP state ext, VST3 component + controller state) saved in sessions
- Hosted instances created, state-loaded and dropped on the UI thread, never the audio thread
- Plugin editors: floating CLAP GUIs and VST3 views on macOS (verified with Melodyne)
- New isolated FFI crate soundcraft-au-host: Audio Units on macOS (effects and instruments,
parameters, latency, MIDI, ClassInfo state), wired into the mixer and plugin browser
UI: Window > Renderer (speaker layout with live levels and object positions), Window > UI
Customization (interface scale, reduce motion), Window > Configurations > Auto-Update.
Fix: saturating sample-index math in clip rendering (hostile stretch values overflowed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- soundcraft-clap-host: scans standard CLAP folders, loads plugins via
clap-sys/libloading, implements the Plugin trait (audio, params, latency,
notes); the only crate allowed unsafe, every block documented; stubs on wasm
- mixer, engine (mix.insert, engine.clap_plugins) and Mix window CLAP menu
- end-to-end tests against a Rust-built fixture plugin
- file.bounce_stems: one soloed file per track
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>