40 Commits
Author SHA1 Message Date
FredApps 87f46cf23b Merge pull request #782 from SlyLaM/fix/transient-record-monitor-states
fix(engine): keep live input states transient
2026-11-06 07:26:56 +01:00
Sly LaMenace d393730b02 Merge upstream/main and adapt transient live input states 2026-11-05 07:51:42 +00:00
Zinadin ZidanandClaude Opus 5.5 4240ffda42 fix(engine): cap sessions at 256 MiB and parse them without a Value tree (#892)
A session was parsed into a serde_json Value and then converted, which takes
many times the file's size in memory, and nothing limited how big a session
file could be.

Session::from_json now reads only format_version first (every other field is
skipped) and then parses the text straight into the session. A missing
format_version and a newer version give the errors they gave before. Other
type errors now end with the place they were found ("at line L column C").
A field of the document given twice in one object, which used to load with
the last value winning, is now refused with "duplicate field `key`" (a
twice-given format_version too, not "missing format_version"): keeping the
old leniency would need the tree this change removes, and no writer repeats
a field. Keys of maps (plugin params, edit values and the like) and unknown
keys still keep the last value given, as before.

A track's height was read through an untagged enum, which buffers the whole
value before it looks at it; a huge array there took about 27 times its size.
It now has its own reader that takes exactly the forms that loaded before
("Micro" to "Extreme", also as {"Micro": null}, {"Custom": n}, {"Fit": n},
and the old bare "Fit" or {"Fit": null}) and refuses anything else at its
first token.

Opening a session (a .scraft file, the browser's bytes, the session in a
bundle) and Import Session Data refuse one over 256 MiB by its size, before
reading it. Save, Save Copy In, Save As Template, autosave and the browser's
bundle refuse to write a session over that size, so SoundCraft never saves
one it then won't open. Save and autosave check before they write any audio,
so a refused save leaves nothing behind and a refused autosave keeps the last
one and its audio copies as they were; the refusal is reported like any
other autosave failure. Save checks against the longest name each new audio
file can get, so it may refuse a session within a few bytes per new file of
the limit. Crash recovery reads only the name of a leftover autosave, and
none from one over the cap. Audio files are read without that cap, as
before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:47:13 +06:00
Sly LaMenace 92e6973640 Merge upstream/main and fix frame-origin grid snapping 2026-11-04 22:54:15 +00:00
Sly LaMenace bf274560ae fix(grid): share visible lines and match folder visibility 2026-11-03 00:26:25 +00:00
Sly LaMenace 087978a4a0 fix(edit): preserve overlaps in stationary nudge and quantize 2026-11-02 23:42:44 +00:00
Sly LaMenace 71f899baac fix(edit): align grid snapping across time scales 2026-11-01 14:47:41 +00:00
Sly LaMenace 381832447c Merge remote-tracking branch 'upstream/main' into feat/grid-nudge-time-scales 2026-11-01 00:09:45 +00:00
SusieeTheFemboyandClaude Opus 5.5 552607cbee Merge main into fix/clamp-clip-fx-gain-send-level
main split crates/mix/src/lib.rs: the clip-FX gain clamp now lives in
clips.rs and the send-level automation clamp in automation.rs (via
crate::SEND_LEVEL_MAX_DB). Adds tests for the send-automation clamp and
the load-time sanitize clamp (#884).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 06:40:00 +01:00
afa1532aba Mix: clamp clip-FX gain and send-level automation (fixes #884)
A shared session or an MCP clip.effects_set could store clip-FX gain or a
send-level automation point far above any fader (up to the +200 dB cap in
db_to_gain). Clamp clip-FX gain to the slider's +24 dB, send-level lanes to
+12 dB, and every loaded automation point to its AutoParam range on sanitize.

Co-Authored-By: opencode beam-free <noreply@opencode.ai>
Co-Authored-By: MiniMax M3 <noreply@minimax.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:53:47 +01:00
Sly LaMenace 66f305b747 fix(ui): handle Grid and Nudge edge cases 2026-10-25 12:30:31 +00:00
Sly LaMenace b9accf92a8 fix(ui): address Grid and Nudge review feedback 2026-10-25 11:13:22 +00:00
Sly LaMenace d33cac774a Merge remote-tracking branch 'upstream/main' into feat/grid-nudge-time-scales
# Conflicts:
#	crates/engine/src/cmd/view.rs
#	crates/model/src/session.rs
#	crates/time/src/grid.rs
2026-10-25 07:29:27 +00:00
aiongg 35905fb90b Integration: Auto Rename: a loaded scheme leaves room for its number 2026-10-25 03:53:11 +00:00
dotCooCoo fe4cd8095d Clip List: Auto Rename sets the names of the clips separated from a clip
Clip List › Auto Rename… opens Rename Selected Clips for the selected
clips. Name, Starting Number, Number of Places and Suffix set the names
that editing gives the clips it separates from them: the name, as many
zeros as Number of Places, a number counting up from Starting Number,
and the suffix. The piece after each cut is the new clip and takes the
next name. The piece before it keeps its name. The clips separated from
one clip, and from every clip that got the scheme together, count up
one number sequence. The guide does not say how Strip Silence's own
Rename fields and Auto Rename combine. Here, when Strip Silence's own
Rename fields are set, they name the audio pieces it cuts inside its
range, and those pieces take no Auto Rename name and do not count the
Auto Rename number up. In Strip and Extract, only the pieces that Strip
Silence keeps take Auto Rename names, in time order, so their numbers
have no gaps.

clip.auto_rename takes clips, name, number, zeros and suffix, with the
same limits and errors as edit.strip_silence. Without a name the clips
get no scheme. Cut and Copy keep a copy of the copied clips' schemes,
and a paste uses the session's scheme with the same id or adds the
copy under a new key, so a pasted copy keeps its names after an undo
or in another session. Saved sessions hold the schemes, and opening a
session repairs them and drops the ones no clip uses. Import Session
Data brings them in under new keys. Clips placed from a clip group file
get no scheme.
2026-10-24 17:12:22 -07:00
aiongg 9f03eeb354 Merge PR #586: session: reject duplicate ids and an exhausted id space on load 2026-10-24 06:19:10 +00:00
aiongg ec7cfbb8ac Merge PR #519: Load older sessions whose track height is the bare Fit string (#447) 2026-10-24 06:19:09 +00:00
Dylan M. Taylor 64669e9a7c chore: resolve SonarCloud maintainability issues (#1003)
* chore: resolve SonarCloud maintainability issues

* fix(packaging): extract verify_sha256 helper in package.sh

* style: apply rustfmt formatting

* fix(video): ensure prefix is i32 in cabac_mb
2026-10-23 22:33:59 -04:00
Sly LaMenace 352d90a24a fix(engine): keep live input states transient 2026-10-19 09:42:51 +00:00
Brandon Thomas 47e1b0e523 Merge pull request #629 from Silvertree2010/fix/h1599-session-with-a-meter-change-near
Drop out-of-range tempo and meter events when sanitizing a session
2026-10-19 02:54:27 -04:00
aiongg 86b31e71d3 Sessions: follow stored source paths only on this computer or the session's share
Audio source and movie paths stored in a .scraft session go through
io::stored_path (craft-docpath) before they reach the file system: a
relative path is taken from the session's folder, and paths on other
computers, devices and URLs are refused. A refused source is reported
as missing with the reason; a refused movie is marked offline and the
Video track and Video window show why instead of opening it. Import
Session Data and crash recovery resolve the same way.
2026-10-16 17:32:35 +00:00
NetterSilverandClaude Haiku 5.5 705c2b9077 Drop out-of-range tempo and meter events when sanitizing a session
Found while testing crates/time/src/tempo.rs:254

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:46:37 +00:00
Sly LaMenace 7849e8cf51 feat(ui): complete Grid and Nudge time scales 2026-10-14 20:41:55 +00:00
midasmatterhorn 800b27013d session: reject duplicate ids and an exhausted id space on load (#448) 2026-10-14 23:22:31 +03:00
MarekSpodBiedry 9a398d79ff Load older sessions whose track height is the bare Fit string (#447) 2026-10-13 17:52:29 +02:00
Brandon Thomas 857882c740 Merge main into #332 2026-10-13 05:40:16 -04:00
Brandon Thomas 468125aaf4 Merge main into #209 2026-10-13 05:27:22 -04:00
grovebit 328760fd70 Keep positions from files within the range commands use
Commands clamp every position they take to ±2^40 samples so the
arithmetic after them can't overflow, but session and clip group files
skipped that. A clip, MIDI note, marker, automation point or selection
near i64::MAX made Duplicate, Insert Silence, Insert Time, Heal, Trim to
File Boundaries, Quantize to Grid, Separate on Grid, Thin Automation,
Copy, Move Selection, Extract Chords and Track Commit overflow: a panic
in debug builds, wrapped positions in release.

MAX_POSITION moves to soundcraft-time so the model can use it. Loading
a session clamps clip starts, lengths, offsets, sync points, gain
envelopes and MIDI notes, automation points, markers, key changes, the
selection, playhead, pre/post-roll, scroll and timecode start. A clip's
repairs are now Clip::sanitize, which clip group import uses too, and
Session::sanitize visits each collection once. The Edit window adds a
clip's offset to its file's time stamp without overflowing.
2026-10-12 18:40:12 +01:00
grovebit 7ca978fdb9 Cut names from files to a length that can be drawn
A MIDI file whose track name was a million characters long imported
fine, and the next frame crashed the app: drawing that name needed a
320 MB vertex buffer, past wgpu's 256 MB limit, and the panic comes
from painting, outside anything that catches it. Saved, the session
crashed again on every open. A 4 MB status line in ui.json did the
same at every launch.

Names read from files are now cut to 256 characters and comments to
4096: everything Session::sanitize repairs on load (session, track,
playlist, clip, marker, bus, output, group, source, video and key
names), MIDI track names on import and clip names from clip group
files. The status bar draws at most 1024 characters.
2026-10-12 18:40:12 +01:00
grovebit a8a36540d6 Name thousands of same-named tracks and files in linear time
Finding a free name tried "Name 1", "Name 2" and so on, rescanning
every track (or every source path) for each try, so each new name cost
time proportional to the square of the session and a bulk import the
cube. Importing a 119 KB MIDI file with 4,095 tracks all named "Piano"
took 28 seconds; a clip group file with 8,000 empty lanes or 8,000
audio files of one name ran for over a minute, and 100,000 lanes never
finished. Import Session Data from a session with 2,000 same-named
tracks took 7 seconds.

unique_track_name and the new audio file name now collect the numbers
already in use once and take the first free one, which gives the same
names. A clip group file also counts each empty lane and each audio
file against its limit of 10,000. The same imports now take 1 second
(MIDI), 2 seconds (8,000 lanes) and a third of a second (Import Session
Data); 100,000 lanes are refused.
2026-10-12 18:40:12 +01:00
grovebit 1617733a98 Repair a session's sample rate and tempo map when it loads
A session file with a sample rate of 0 or a tempo of 4e9 BPM opened
fine, but Strip Silence on the first and Extract Chords on the second
never finished (one also climbed past 1 GiB). Session::sanitize, which
repairs everything else read from a file, skipped both fields.

On load, a sample rate outside what sessions accept becomes 48 kHz, and
the tempo map keeps only the tempos and meters that set_tempo and
set_meter accept, in tick order, with one of each at tick 0.
2026-10-12 18:40:12 +01:00
Marco Arnulfo 5e7ca6de22 Apply the Feet+Frames reference to counters and typed positions (fixes #235)
Setup > Current Feet+Frames Position stored its origin in
feet_frames.start but nothing read it, so every Feet+Frames reading
still counted from sample 0. format_position/parse_position now take
the origin alongside the timecode start, and Session::format_position
and Session::parse_position pass both, so the counters, rulers,
session.format_time and typed positions share one reference.
2026-10-12 16:36:18 +02:00
Brandon Thomas 4e846b8f02 Merge pull request #75 from grovebit/clip-list-delete
Clear clips and audio files from the Clip List (#4)
2026-10-11 20:40:53 -04:00
grovebit e26a5f5f42 Zoom Toggle: E zooms in on the edit selection, E again zooms back out
In Pro Tools, E (with Commands Keyboard Focus on) zooms the Edit window
to the edit selection and fits the selected tracks to the window, and
pressing it again brings back the view from before. SoundCraft had no
Zoom Toggle.

view.zoom_toggle, in its own command module, stores the zoom, the
scroll and the selected tracks' heights in the session's new
edit.zoom_toggle, so the view to go back to is saved with the session.
It then zooms so the selection fills the timeline and gives the
selected tracks equal shares of the track area: Pro Tools' Fit To
Window height. Hidden tracks are left alone. Run again, it puts all of
that back. With only an insertion point the zoom stays and its tracks
are fitted. The UI passes the track area's size; agents can pass
width_px and height_px.

TrackHeight::Fit, which nothing could reach and which drew 88 points
tall, now carries its height in points, so every place that sizes a
track row reads it unchanged.

The Edit window scrolls the first fitted track to the top when Zoom
Toggle turns on, and back to where it was when it turns off, whichever
way it was toggled (key, button, control channel, undo).

As in Pro Tools, E works in the Edit window while Commands Keyboard
Focus (the a-z button) is on, on every OS. A Zoom Toggle button left of
the Zoomer lights while it is on and toggles it too.

This follows Pro Tools' Zoom Toggle settings for audio editing:
Selection for both zooms, Fit To Window, no track view change. Its
other preferences, Ctrl+E (Pro Tools' key with the focus off),
Alt+Shift+E (cancel) and following the edit selection are left out.

Tests cover the zoom, the fitted heights, going back, insertion points,
hostile sizes and stored views, and E and the toolbar button on macOS,
Windows and Linux.
2026-10-12 01:22:08 +01:00
grovebit 86824b592a Never overwrite audio files when saving
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.
2026-10-11 19:59:43 +01:00
grovebit 42d08558ab Clear clips and audio files from the Clip List
Nothing in the Clip List could be removed: neither its files nor its clips
had a delete action, so everything stayed listed.

- New `clip.clear` (Cmd+Shift+B, the Clip List Clear shortcut in Pro Tools),
  also offered as Clear… on every Clip List row's right-click menu. A clip
  leaves its track; a file leaves the session with its decoded audio and is
  never deleted from disk. One undo step brings everything back.
- As in Pro Tools, Clear asks first. The dialog names what goes, including
  the clips on any track or playlist that still play a file being removed:
  they are removed too. Programmatic calls never open it; they pass
  `with_clips: true` to remove a file still in use, and fail without
  changing anything otherwise.
- File rows can be selected now (`edit.select {sources}`, kept in
  `EditState::selected_sources`); with no ids, `clip.clear` acts on the
  Clip List selection.
- Remove Warp and re-warping ignore a stored original whose file has been
  cleared, so they never point a clip at a missing file.
2026-10-10 02:07:51 +01:00
Brandon ThomasandClaude Opus 5.5 cdefc57c91 Video track, Audio Unit hosting, plugin state and editors, Renderer and UI Customization
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>
2026-10-06 01:41:05 -04:00
Brandon ThomasandClaude Opus 5.5 720373c0c6 Engine parity batch (76% engine, 90% with UI), comping, fades, universe, automation writing, loop record
- ~200 new commands across Edit/View/Track/Clip/Event/Options/Setup/File
  (copy/paste special, playlists, TCE, tempo operations, beat detective,
  chords, MIDI performance, view flags, setup settings, import session data)
- explicit targets bypass selection-based enablement; undo coalescing for
  gestures (faders, knobs, sliders)
- live Write/Touch/Latch volume automation passes; loop recording into
  playlists; playlist lanes with promote-to-main comping; smart-tool fade
  handles; Universe overview; VCA masters; routing-folder busses; edit groups
- bounce reports true peak; README with screenshots; parity report

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:25:09 -04:00
Brandon ThomasandClaude Opus 5.5 63bf6c5b4b xtask gates, parity report, model flags and full channel-format list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:02:15 -04:00
Brandon ThomasandClaude Opus 5.5 755f02fc85 Foundation: time, audio-io, midi, dsp, model, mix, engine, playback crates
Workspace for SoundCraft, a clean-room pure-Rust Pro Tools-class DAW:
timebases and tempo maps, WAV/AIFF/FLAC/symphonia audio IO with peaks,
SMF and MIDI ops, 26 original DSP plugins, the session model, the mix
engine, ~180 registered commands with undo, a cpal player, and the
agent rules (AGENTS.md, ATTRIBUTION.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:27:29 -04:00