* mix.volume: say what each track got and when a value was clamped
The fader stops at -144 and +12 dB, and mix.volume replied only with a track
count, so +15 dB played at +12 with nothing to show it. The reply now lists
each target's volume and sets clamped when a target or a group member was
held at a stop.
* mix.volume: list every requested track in the reply
A target moved as a group sibling of an earlier one was skipped before it was
added to volumes, so tracks [3, 6] in one group listed only track 3. Build
volumes after the loop from each requested track's final volume.
* Add missing VST3 paths for Linux
* Add missing VST3 paths for Windows
* Add missing VST3 paths for macOS
* Make os_search_paths private
* Use hermetic search_paths in unix test and remove os_search_paths
* Rewrote the Unix test to use the hermetic search_paths with a fixed executable, a None case, and no process state.
* Replaced os_search_paths with default_search_paths_with, injecting the environment lookup and executable so tests
now cover the COMMONPROGRAMW6432 → COMMONPROGRAMFILES fallback, COMMONPROGRAMFILES(X86), LOCALAPPDATA, app-local folders, and VST3_PATH.
* Add docstring to env_from
---------
Co-authored-by: Gennadij Yatskov <gennadij@yatskov.de>
On Windows the client's 800 ms connect timeout can expire before a closed
loopback port is refused, so the CLI reports "connection timed out" without
an "(os error N)" suffix. Both assert_refused helpers now accept either
reason, and still check exit 1, empty stdout and the exact address prefix.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The help for mix.send said `level_db?: 0`, but a new send has always started
at -144 dB (silent): SendSlot::new sets it, the Mix window relies on it, and
Preferences > Mixing shows "Sends Default to -inf dB". The help now gives the
range and the real default, and a test checks the help and a new send agree.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A line that was not JSON got a "bad JSON" error reply and the server kept
reading the same connection. A JSON value that was not an object, or had no
string `method`, was forwarded to the UI as a request with an empty method.
Now a line that is not a JSON object with a string `method` gets one error
reply and the connection is closed, as in the other Craft apps' control
servers. Control clients send one request object per line, so a peer that
sends anything else is not one, and nothing it sends after that line is run.
Valid requests and blank lines are handled as before.
The protocol doc says so next to the error format.
* Strip Silence: the sliders move a tenth as far while Ctrl or Command is held
While Ctrl (Command on a Mac) is held, a drag on one of the four Strip
Silence sliders moves the value a tenth as far as the same drag
without the key, on the slider and on its value box. Pressing or
releasing the key during a drag leaves the value where it is, and the
drag goes on from there. Releasing the mouse button leaves the value
too. A drag without the key works as before.
The new widgets::fine_slider uses the modifier test and the tenth of
widgets::fader. Threshold and Clip Start Pad show one decimal place,
and a drag on the slider during which the key was held rounds the
value to the places the box shows.
* Plugins: parameter sliders move a tenth as far while Ctrl or Command is held
While Ctrl (Command on a Mac) is held, a drag on a parameter slider in
the plugin window, the Clip Effects window or an AudioSuite plugin's
window moves the value a tenth as far as the same drag without the key,
as the Strip Silence sliders do. A logarithmic slider moves a tenth as
far along the slider, and a whole-number slider, such as the Sampler's
Root Note, stays on whole numbers. A drag without the key works as
before.
widgets::FineSlider is a new builder with the slider options these
windows use (no value box, logarithmic, whole numbers, a salt for
unlabeled sliders); widgets::fine_slider keeps its signature and calls
it.
* Plugins: Alt-click returns a parameter to its default value
An Alt-click (Option-click on a Mac) on a parameter in the plugin window,
the Clip Effects window or an AudioSuite plugin's window sets the
parameter's default value, as an Alt-click sets a fader to 0 dB. It works
on a slider, on a slider's value box and on a choice list. The click on a
value box does not open it for typing, and the click on a choice list does
not open the list. In the plugin window and the Clip Effects window the
reset runs the same command as a drag, so it is one undo step. While Alt
is held, a drag on one of these sliders leaves the value where it is.
widgets::FineSlider has a new default_value option for this.
* Plugins: FineSlider::default_value describes what a held Alt does
While Alt is held, a drag leaves the value where it is, and an Alt-click
on the value box does not open it for typing. An Alt-click on the rail
sets the default.
* Dialogs: Enter on a focused Cancel or Don't Save no longer runs OK
Enter on a focused button clicks it and also presses the dialog's OK.
In Strip Silence, Enter on Cancel closed the dialog and still stripped
the selected clips. Cancel and Escape now drop the command and the save
that OK asked for, in every dialog. In Unsaved Changes, Enter on a
focused Don't Save closes without saving instead of opening Save.
* Read send destinations only while the menu is open
* Tests: pressing Ctrl during a value-box drag keeps the value
The rest of the drag on the Threshold value box moves as far as a drag
with Ctrl held from the start.
* Bounce Mix: Cancel starts no bounce; fine adjust on more AudioSuite sliders
Enter on a focused Cancel also pressed Bounce, which started the bounce
before the Cancel check ran. The bounce now starts after that check.
The Normalize, Time Shift and Pitch Shift sliders in the AudioSuite
window move a tenth as far while Ctrl or Command is held.
* Tests: give the Enter-on-Cancel bounce test its own scratch folder
It shared bounce-cancel with cancel_or_escape_stops_a_bounce_and_leaves_no_file,
so the two tests removed each other's folder when run in parallel.
* Clip Effects: log-taper parameter sliders use a logarithmic rail
The EQ and Dynamics sliders in the Clip Effects window now follow the
parameter's taper, as the plug-in window does, so a frequency, Q, ratio,
attack or release slider moves along a logarithmic rail.
* Dialogs: leave Enter on a focused Cancel or Don't Save to its own pull request
The dialog changes for Enter on a focused Cancel or Don't Save, the
Bounce Mix change that started the bounce after the Cancel check, and
their tests in save_changes.rs, strip_silence_dialog.rs and bounce.rs
are removed from this branch. The fine adjustment on the sliders stays.
* Plugins: tests cover Alt reset and Ctrl fine adjustment on more slider kinds
Tests now cover Ctrl without Command, Alt pressed during a drag, one
undo step for a choice-list reset, whole-number and logarithmic sliders,
Alt-click and Ctrl-drag on the AudioSuite and Clip Effects value boxes,
and Alt on a Strip Silence slider, which behaves like a plain click or
drag.
The ROADMAP entry limits the choice-list reset to the plugin window, and
the docs/ui-parity.md fine-adjustment row names the Clip Effects and
AudioSuite sliders.
* Plugins: a short Ctrl-drag at the low end of a log slider with a value box moves the value
A fine drag rounded to at most three decimal places, so a one-point
drag near the bottom of a range such as Attack at 0.01 ms rounded back
to where it started. The rounding now allows up to six places.
* Strip Silence: the fine slider's response reports each change of a drag
The value moved by a drag that the key has touched is set before the
slider reads it, so the Response's changed() stayed false on those
passes. fine_slider now marks the response when the value changed.
Tests: a Control-only drag, and changed() on every pass of a Ctrl-drag.
The value box test releases the button once and checks the value after.
* Tests: plugin sliders with Control alone and with Ctrl toggled during a drag
The plugin window's logarithmic and whole-number sliders keep their value
when Ctrl is pressed or released during a drag, and a drag with Control
alone is fine. The tenth-as-far test names the parameters it drags and
checks the others do not change, and the low-end test checks the value
after the release.
* Review fixes after the craft-audio switch; pin craft-audio at craft-libs main
Follow-up to #1506, for its review comments:
- convert: a decode that concealed damaged packets is reported as a warning,
and --in-place refuses to replace the source with it.
- convert: the decoded speaker layout goes to the encoder, so 5.1 with side
surrounds stays side surrounds in WAV and FLAC.
- FileFormat::of names an MP4 or QuickTime file by its codec: AAC or HE-AAC
is Aac, ALAC is Alac, anything else (Opus, AC-3, PCM) is Other.
- Bounce keeps a picked extension that already names the format (Mix.aifc,
Mix.AIFF), so the file written is the one the save dialog confirmed.
- rust-version is 1.95: craft-audio, craft-media and craft-ui declare it, so
1.90 was not a version the workspace builds with.
craft-audio and craft-media move to craft-libs main 7db4645d.
* MIDI: take Standard MIDI Files and the sequence model from craft-midi
craft-midi in craft-libs was moved from this crate (storytold/craft-libs#884):
smf.rs, sequence.rs, the error type and their tests. soundcraft-midi now
re-exports them, so every caller keeps its imports, and keeps what is
SoundCraft's own: the editing operations, the display names and XorShift.
The SMF and Sequence tests are deleted here; they run in craft-midi, which
also adds one for the track limit. The serde names do not change (the same
derives behind craft-midi's serde feature), so sessions read as before.
craft-audio in craft-libs was built from audio-io and FilmCraft's codecs, so
each format has one implementation that every Craft app shares. Call sites use
its API directly: EncodeOptions::new(format).with_*(), EncodeFormat,
DECODE_EXTENSIONS, ENCODE_EXTENSIONS, encode_format_for, AudioInfo's container,
codec and layout.
soundcraft_model::FileFormat stays as the kind of file a session records for
each source: its variant names are part of the session format. It maps from
craft-audio's AudioInfo.
craft-audio and craft-media are pinned to the craft-libs branch of
storytold/craft-libs#824, which adds the extension tables SoundCraft had in
FileFormat and fixes the quadratic WAV chunk scan (#893).
Behaviour that changes: AAC, ALAC, Opus, AC-3/E-AC-3 and MP2 decode with
craft-audio's own decoders; MP4 and Matroska tracks import at their
presentation length; .opus, .ac3, .eac3, .ec3, .mov, .mka and .mpa are
importable names; .aifc is a writable name; FLAC files come from craft-audio's
encoder.
release.yml also runs nightly on main with craft-common-actions' dry-run: every release build
except macOS, with nothing signed, pushed or drafted. Breakage that only the release build hits
(riscv64, Flatpak, FreeBSD, packaging) then shows up the next morning instead of on release
day, as it did on 11 Nov 2026.
* fix(engine): create output files exclusively and check read access before probing
* address review: canonicalize through the checked io gateway, treat any occupied export name as taken
* address review: read group audio through the validated file handle
* address review: document that a write-only root writes a new numbered WAV per save
* address review: clip-group audio is opened without waiting for a FIFO writer
* Strip Silence: cut and remove pieces in one pass over the playlist
* address review: document the sorted-points order and test stretched audio offsets
* fix(ui): check movie read access and file type; private take folder without settings
* address review: make the fallback take folder exclusively under an unguessable name; qualify the owner-only claim to Unix
* address review: unique folder prefix must be one name; no fallback take folder for a saved session
* add tests for the take folder and the movie open checks
* fix(ui): bound MIDI grid lines and Video Universe thumbnails per repaint
* address review: grid fallback stays on snap positions and bars; thumbnail positions in f64
* address review: no thumbnails for an empty shown span
* fix(audio-io): bound packet samples and MP4 audio timing runs
* address review: decide MP4 audio timing bound from the stsd entry and bound PCM packets by their byte size
* address review: the last stsd in a stbl decides whether the stts bound applies
* fix(video): check MJPEG frame size from its header and budget H.264 frame requests
* address review: check the sample budget before reading, keep the decoder from skipping a refused sample, stop on a failed read
* address review: restore lenient MJPEG sizing and unreadable-sample skipping; per-request sample cap 2 GiB
* address review: an unreadable sample counts toward the no-picture budget
Adds step-security/harden-runner (audit, pinned to v2.22.4 like
craft-common-actions) as the first step of each job that did not have it,
so every workflow in the org's public repos reports its egress to
StepSecurity. Audit, not block: block-mode allow lists come from each
job's observed traffic.
* Read volume automation at every sample, not once per block
Track and master faders read the dB curve at a block's first and last
sample and ramped linearly in gain between them, so a breakpoint acted up
to a block early or late and a short crossfade came out differently
depending on where the block edges fell. Both now take the gain at each
sample: the curve is read at its breakpoints and interpolated in dB in
between, as the lane defines it. Blocks with no change keep one gain.
Fixes#1354.
* docs: volume breakpoints no longer depend on the render block (G-04)
* Step fader ramps by a ratio and scale channels in contiguous runs
Reading the fader at every sample converted dB to gain per sample on
every ramp, and applied it sample by sample across channels, even when
nothing moved. A stretch that is linear in dB is geometric in gain, so
each stretch now converts its two ends exactly and steps the samples in
between by one ratio (in f64, so it does not drift), with the -144 dB
floor and the cap checked per sample. Each channel is scaled over the
whole stretch at once, and a stretch with no change is a plain multiply.
Tests: stepped ramps against db_to_gain sample by sample (floor, cap,
NaN, one or two samples, a 16384-sample ramp), and summed volume, trim
and VCA lanes with a muted track, read from renders that start inside a
ramp or on block edges.
* fix(mix): recover extreme automation ramps from silence
* fix(mix): preserve extreme ramp endpoints and silence NaNs
---------
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* Compensate a hosted instrument's latency
Delay compensation summed only insert latency, so a synth reporting
latency played late against everything else in playback and bounces.
run_inserts now seeds the strip latency with the instrument's.
Fixes#1474. Added a mix test that fails without the change: a latent
hosted instrument now contributes its latency to the mix.
Signed-off-by: Vladimir Babin <vovababin@gmail.com>
* test(mix): verify rendered instrument latency alignment
---------
Signed-off-by: Vladimir Babin <vovababin@gmail.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* CLAP host: a reset keeps the queued note-offs and releases held notes (#1464)
ClapPlugin::reset kept only parameter changes in its event queue and cleared
the held keys, so the note-offs all_notes_off had just queued on Stop, Play
and Locate never reached the plugin. A plugin whose own reset leaves its
voices sounding (Surge XT) played them on at the next Play, and All MIDI
Notes Off had nothing left to release.
Like the VST3 host since #441, reset now keeps the pending releases (CLAP
and MIDI note-offs, one per key, at the start of the next block) and
releases whatever is still held. The fixture synth's reset no longer
silences it, so the integration test sees what Surge XT does.
* CLAP host: keep queue room for the releases a reset sends (#1464)
A queue that distinct parameter changes filled had no room left for the
releases reset queues, so a held note could still sound on. The last 128
queue slots now take only note_off releases, and reset queues one release
per held key that has none yet, which always fits in that reserve.
* CLAP host: a release the full queue drops leaves its key held (#1464)
note_off cleared the key's held flag even when its release did not fit in
the queue, so the reset after all_notes_off had nothing left to release.
The flag is now cleared only when the release was queued; the reset then
sends it, after keep_releases has made room.
Behaviour-preserving: `[[ ]]` in place of `[ ]` for bash tests (SonarCloud shell:S7688). Every script has a bash shebang; no test uses `-a`/`-o`, escaped parentheses or an unquoted pattern on the right of `=`. `bash -n` passes on each script; the rest is left to CI.
- `packaging/linux/flatpak-bundle.sh` (4)
* Consolidate: run clip effects over the clip and a bounded tail
render_clips allocated and ran a clip-effects workspace spanning the
whole selection for every clip with effects, however short the clip.
Many tiny effect clips in a long selection multiplied allocation and
effect processing by the selection length.
Process each clip's effects in reused 4096-frame blocks from the clip's
start (the effects start from silence, so nothing comes out before it)
through its end, then keep rendering the tail until a block falls below
-180 dBFS or 10 s past the clip's end.
* address review: settle clip effect smoothers on a bounded silent lead-in
* address review: skip the lead-in for gain-only clip effects; keep the full-span chunk grid
Display labels only; command ids, catalog paths, session tokens and processor ids are unchanged.
- Avid-listed names and bundled plug-in products get our own names (Beat Analysis, Punch Recording, Room Reverb, Compressor/Limiter, Saturator, Bit Crusher, Rendered (High Quality), ...).
- Features we don't have stay in the menu as disabled placeholders with "Not available yet." (Tempo Sync, No ARA plug-ins, Analog Saturation).
- AudioSuite, Elastic Audio, TrackPunch, Pitch Shift Legacy and Time Compression Expansion keep their names until RFC-0029 settles.
- parity_floor and catalog dispatch unchanged; docs/feature-naming.md has the crosswalk.
Moves the craft-libs crates (craft-update, craft-docpath, craft-media,
craft-ui) from c5a1cbfb5f to a branch that merges storytold/craft-libs
#687 and #696.
- The result dialog shows whenever the updater reports a status, so the
first start after an update says whether it installed or failed.
Closing it dismisses the status; the startup prompt still takes over.
- Restart to Update in the dialog asks the app to close through the
usual close path.
- on_exit starts the helper of a ready update, since the system Quit
ends the process before the updater is dropped.