Nothing a save wrote was synced to disk, so a power cut or crash of the
system soon after Cmd+S could leave an empty or truncated session file
(the rename can reach the disk before the data), or audio files that
the session points at but that never reached the disk. No earlier
version of the session file was kept either.
New audio files, the Audio Files folder and the session's temporary
file are now synced before the session file is renamed into place, and
the session folder after the rename; templates are written the same
way. A failed write or rename removes its temporary file instead of
leaving it on a full disk. Before a save replaces the session file with
different contents, the previous version is copied to
Backups/<name> backup <n>.scraft beside it, and the newest ten are
kept. Autosaves make no backups.
Save As, Save Copy In, Save As Template and Export Selected Tracks as
New Session wrote over any existing file without asking. The Save As
prompt proposes ~/<name>/<name>.scraft, so every new session suggests
~/Untitled/Untitled.scraft, and saving a second new session with the
suggested path replaced the first one.
These commands now refuse to write over an existing file unless
`replace` is true; Save, Save As and Save Copy In may still write the
open session's own file. Scripts, MCP agents and the control channel
can't replace a session by accident. While the path in the prompt names
an existing file, the prompt asks whether to replace it and its OK
button becomes Replace; Enter alone doesn't replace. Saving also
creates the session's folder, so a new session without audio can be
saved to the suggested path. The CLI's --save names its output
explicitly and keeps replacing it, like --bounce.
Audio that exists only in memory (takes, consolidations, AudioSuite
renders, commits) gets the default path `Audio Files/<name>.wav`, and
Save treated an existing file at that path as the source's own and
rewrote it. Names repeat easily: two sessions in one folder both call
their first take on Audio 1 "Audio 1_02", and consolidating twice on
Kick makes two "Kick_consolidated" sources. Saving silently replaced
the earlier audio, so the other session or the earlier clip played the
wrong audio once reopened.
Save As also trusted a same-named file in the target folder for audio
saved elsewhere, so saving into another session's folder could point
the new session at that session's audio.
New audio now goes to the first free name (`<name>.wav`,
`<name>_01.wav`, ...), created with create_new so no existing file is
ever truncated, or to a file that already holds exactly the same bytes,
such as a take saved again after undoing past a save. Saved audio is
reused only when saving into the folder it was saved in, which the
session now remembers, so undoing past a Save As or saving after Save
Copy In can't pick up another session's files either. Audio that
failed to load keeps pointing at its old folder when the session is
saved elsewhere.
The command cleared every automation lane in the selection and left the clipboard empty, so the cut could not be pasted. It now uses the same copy-and-clear path as Cut Pan Automation.
Export Clips as Files wrote every clip to {name}.wav, so the second clip replaced the first. A name that is already in the destination now gets a -2, -3, … suffix.
Snap to Next and Snap to Previous ignored other selected clips when looking for a destination, so a multi-clip selection piled onto the same later clip or left the gap between the selected clips in place.
There was no way to change a clip's gain from the UI (#2): clip gain is
stored, drawn and settable through `clip.gain`, but no menu item, key or
gesture reached it. This adds the two ways Pro Tools offers.
Keyboard: Pro Tools nudges the selected clips' gain with
Control+Shift+Up/Down, 1 dB a step by default. Two new commands, Nudge
Clip Gain Up and Down, do the same through `clip.gain`'s relative
`delta_db`, so its -144..+36 dB range holds and each nudge is one undo
step. Control+Up/Down extended the edit selection whether or not Shift
was held, which swallowed the new keys; it now leaves the Shift variants
to the command registry.
Fader: in Pro Tools, Clip Gain Info puts a fader icon and the gain value
in the bottom-left of each clip, and pressing the icon pops up a small
fader. With View > Clip > Clip Gain Info on, or once a clip has gain,
audio clips now show the icon and the value, and dragging the icon moves
the clip's gain from -144 to +36 dB (Cmd for fine moves), one undo step
per drag. The icon is drawn in code. The fader follows the pointer from
where it was pressed rather than adding up per-frame deltas, which lose
the first few pixels while egui decides between a click and a drag and
leave the fader out of step with the pointer once it hits an end (it
would stick, then jump).
Clip Gain Info no longer depends on Clip Gain Line being shown, since
the two are separate views in Pro Tools, and the value moved from the
clip's bottom-right to beside the icon. Dragging the icon of a clip when
nothing is selected selects that clip first, because `clip.gain` needs a
selection.
Agents using session.inspect / inspect_session can tell missing media from metadata alone, complementing open_session missing lists and audio_health. Also credit Mawen in people.toml.
The window checks every playlist, including inactive takes, for missing audio, rate mismatches, empty buffers, and clips that extend past their source.
Options > Click set a flag but nothing rendered sound, so a click track
was silent. The mix engine now synthesizes a short blip on every beat of
the tempo map (accent on beat 1), using the click.* settings from
Setup > Click/Countoff, and sums it after the master fader.
The metronome is opt-in per engine: only live playback enables it.
Offline renders (bounces, stems, exports) leave it off so the click is
never printed into a file.
Also: midi_to_hz moves from the engine demo into soundcraft-dsp so the
mix and demo share one tuning formula.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Double-clicking the tempo ruler opened a number dialog whose command was
built as "event.tempo@<position>". No such command exists, so OK failed
and nothing changed. The position was also never passed, so a change
would have landed at the session start.
Add a TempoChange dialog that carries the position and runs
event.tempo with bpm and at. Add an engine test that a tempo change at a
position is applied there.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
There was no way in the app to choose a CLAP, VST3 or Audio Units instrument,
or to open an instrument's editor:
- the insert menus leave instruments out;
- an instrument was only set by track.new;
- the Mix window's INSTRUMENT row looked for instrument inserts rather than the
track's instrument;
- plugin windows opened only for insert slots.
Changes:
- mix.instrument {track?, plugin} sets an instrument track's instrument, with
the plugin's default parameters, and is undoable. It refuses effects and
other track kinds.
- The INST label on an instrument track's header opens an instrument menu: the
current instrument and its editor, then the built-in, CLAP, VST3 and Audio
Units instruments, by vendor.
- The Mix window's INSTRUMENT row shows the track's instrument. A click opens a
hosted instrument's plugin window, and a right-click changes it.
- Plugin windows open for the instrument slot, with only the editor button,
because the bypass, preset and parameter commands address insert slots.
They no longer derive a slot letter for the instrument slot.
Closes#22
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Mix window's insert menu listed each format's plugins as one flat column.
With many installed (53 VST3 and 95 Audio Units effects on a composer's Mac)
it ran past the bottom of the screen and egui menus don't scroll, so the lower
entries couldn't be reached. CLAP, VST3 and Audio Units now open one submenu
per vendor, as Pro Tools does when plug-in menus are organized by manufacturer,
and every list scrolls within the screen.
Choosing an Audio Unit did nothing: the engine's plugin lookup, which
mix.insert uses, covered built-in, CLAP and VST3 plugins only, so "au:" ids were
"unknown plugin" and the UI dropped the error. The lookup now includes Audio
Units (the engine depends on soundcraft-au-host, like the mixer and the UI), and
the menu shows a failed insert in the status bar.
Closes#21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Testing on macOS showed that the exit-handler fallback comes too late for a
plugin that has been used: with HALion Sonic inserted on a track, its bundleExit
segfaults, and both the app (on quit) and soundcraft-cli (after a bounce) exited
with 139. A plain scan was fine. Exit handlers run in reverse order of
registration, so state the plugin set up while in use is presumably torn down
before the host's handler runs.
soundcraft_vst3_host::shutdown() runs the module exits up front, and
soundcraft_engine::shutdown_plugin_hosts() calls it. The app calls that once the
event loop has returned (the player and its plugin instances are gone by then),
and the CLI once its subcommand has. The exit handler stays as the fallback for
other exits, such as test binaries.
The fixture can now set up state lazily the way such plugins do
(VST3_FIXTURE_LATE_STATE), and module_exit_shutdown checks both sides in child
processes: with shutdown() the exit is clean, with only the exit handler the
fixture reports the late teardown. Contract violations now end the process with
status 3 instead of an abort, so that expected failure leaves no crash report.
Refs #16
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>
- File › Export › Sibelius and File › Send To › Sibelius write MusicXML 4.0 (quantized,
chords, ties across bar lines, meter changes, treble or bass clef per part)
- File › Print Score engraves the MIDI tracks to a printable SVG page; File › Score Setup
sets title, composer, bars per system and track names
- File › Import › Clip Groups and Export Clip Groups (.scgrp with its audio)
- Edit › Paste Special › Merge Markers: copy carries the markers in the copied range
- The UI opens a path prompt for any command that needs a path or folder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Surround:
- main and bus formats up to 9.1.6 and Ambisonics; pairwise constant-power
surround panner (divergence, centre, LFE, height) per track and send;
ITU fold-down; multichannel bounce with correct WAV channel masks;
multichannel device output; surround panner and multichannel meters in the
Mix window; setup.main_format, mix.surround_pan, mix.send_surround_pan
VST3:
- soundcraft-vst3-host: isolated unsafe crate (vst3 bindings, MIT), scans
standard folders, hosts components/processors/controllers, params,
latency, notes; VST3 menu; engine.vst3_plugins; e2e tests with a fixture
Hardening:
- positions from parameters clamped; offline renders capped and bounded to
track material; loop/transport limits; hostile-parameter property test
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>
- mix: independent strips process in parallel (rayon, native), parameter
pushes cached, plugin sync/routing order cached per session structure;
plugin and render benchmarks
- UI: Playback Engine, I/O Setup (bus rename/delete), Keyboard Shortcuts
windows; clip gain line + info; folder open/close; tempo changes from the
ruler; Inserts/Sends F-J, EQ curve and comments sections in the Mix window
- docs: control protocol and MCP guides
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>