64 Commits
Author SHA1 Message Date
Brandon Thomas ba86e7eae7 Merge remote-tracking branch 'origin/main' into fix1227
# Conflicts:
#	crates/model/src/automation.rs
2026-10-31 10:54:32 -04:00
Brandon Thomas 6446eb5598 Merge pull request #1215 from Silvertree2010/fix/h4180-automationlane-value-at-panics-w
Fix integer overflow in AutomationLane::value_at for distant breakpoints
2026-10-31 10:53:31 -04:00
NetterSilverandClaude Haiku 5.5 2d88f46f02 Keep plugin parameter id case in AutoParam::parse
Found while testing crates/model/src/automation.rs:86-103 (AutoParam::parse)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 10:16:46 +00:00
NetterSilverandClaude Haiku 5.5 b2a386bc7a Fix integer overflow in AutomationLane::value_at for distant breakpoints
Found while testing crates/model/src/automation.rs:161

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 02:31:18 +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
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
aiongg 045938728f Integration: #674 advance the source offset by delta/stretch on start trims and compare heal offsets in source time 2026-10-24 06:19:09 +00:00
aiongg 2e17315d5d Merge PR #674: Fix source offset when trimming the start of a stretched audio clip
crates/engine/src/edit.rs: kept main's gap-aware heal comparison and doc comment, the PR's source_len helper, and both sets of tests.
crates/model/src/clip.rs: kept main's clamped trim_start_to body (the stretch-aware offset follows in the Integration commit) and both tests.
2026-10-24 06:19:06 +00:00
aiongg a1eafbd1c5 Merge PR #517: Heal keeps each clip's gain envelope and skips clips whose mute or stretch differ (#450)
crates/engine/src/edit.rs: heal combines main's gap-aware join (contiguous, gap, saturating adds) with the PR's same_playback check and join_gain_env call; both heal tests kept.
2026-10-24 06:18:46 +00:00
Brandon ThomasandClaude Opus 5.5 b0f07331a2 Clip::trim_start_to keeps the end when an extreme start would overflow the length
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 00:53:41 -04:00
Brandon Thomas 1053073302 Merge remote-tracking branch 'origin/main' into m/960 2026-10-24 00:53:02 -04: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
Royce ce235c6f80 fix(model): saturate the arithmetic in trim_start_to / trim_end_to (#907)
`Samples` is `i64`, and these two public APIs did plain subtraction:

```rust
let new_start = new_start.min(self.end() - 1);   // end() - 1
let delta = new_start - self.start;               // extreme new_start vs extreme start
self.length -= delta;                            // -=
let new_end = new_end.max(self.start + 1);       // start + 1
self.length = new_end - self.start;               // extreme new_end vs extreme start
```

Command callers clamp to `MAX_POSITION` first, so nothing in the app trips
this today — but the methods are `pub`, and a clip read from a hostile file
can already hold any `i64` before `sanitize` runs. An extreme `new_start`
against an extreme `start` wraps in release and panics in debug.

Each site becomes a saturating operation followed by the same clamp
`sanitize` already applies, so the result is identical for every input a
command can produce.

Test: `trims_saturate_on_extreme_arguments` sweeps `i64::MIN`/`MAX`,
`±2 * MAX_POSITION` and the edges, in both directions, asserting positions
stay within `±MAX_POSITION` and lengths within `1..=MAX_POSITION`.
Verified against the unfixed arithmetic: the test fails with
`attempt to subtract with overflow`.

cargo test -p soundcraft-model: 41 passed, 0 failed.
cargo clippy -p soundcraft-model --lib --no-deps --tests clean.
2026-10-23 20:09:26 +08:00
skylar taylor-barrick 589608a5d3 Merge pull request #812 from Silvertree2010/fix/h840-automationlane-write-range-overw
Keep existing breakpoint at end + 1 in AutomationLane::write_range
2026-10-22 07:48:53 -06:00
NetterSilverandClaude Haiku 5.5 01c8c85ebc Fix integer overflow in AutoParam::label for Pan(255)
Found while testing crates/model/src/automation.rs:73

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 07:12:50 +02:00
NetterSilverandClaude Haiku 5.5 1816f2dc92 Keep existing breakpoint at end + 1 in AutomationLane::write_range
Found while testing crates/model/src/automation.rs:190-203

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 14:30:22 +02:00
Brandon Thomas 582bfb2f7e Merge pull request #423 from webdevtodayjason/thin-step-lanes
Keep mute changes in place when thinning automation
2026-10-19 03:03:29 -04:00
Brandon Thomas 00315b6fb4 Merge pull request #507 from MarekSpodBiedry/issue-468
Count the partial quartet in the base64 decoded-size cap (#468)
2026-10-19 03:02:43 -04: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 a4b6bd0179 Fix source offset when trimming the start of a stretched audio clip
Found while testing crates/model/src/clip.rs:246 (Clip::trim_start_to, used by crates/engine/src/edit.rs:36 split_clip)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 11:09:42 +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
midasmatterhorn 800b27013d session: reject duplicate ids and an exhausted id space on load (#448) 2026-10-14 23:22:31 +03:00
MarekSpodBiedry bddc113f46 Heal keeps each clip's gain envelope and skips clips whose mute or stretch differ (#450) 2026-10-13 17:54:49 +02:00
MarekSpodBiedry 9a398d79ff Load older sessions whose track height is the bare Fit string (#447) 2026-10-13 17:52:29 +02:00
MarekSpodBiedry 9209909ff7 Count the partial quartet in the base64 decoded-size cap (#468) 2026-10-13 17:49:23 +02:00
webdevtodayjason e10b245652 Keep mute changes in place when thinning automation 2026-10-13 06:58:16 -05:00
Brandon Thomas e0deb3f4e5 Merge main into #335 2026-10-13 05:55:06 -04: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
Brandon Thomas 3f3f64746f Merge main into #384 2026-10-13 05:21:33 -04:00
Brandon Thomas 2bba5c334d Merge main into #365 2026-10-13 05:17:58 -04:00
Brandon Thomas b0339a418f Merge pull request #401 from Claudio911015/fix/273-clip-loop-unloop-consistency
Make clip.loop / clip.unloop self-consistent (loop_id tag)
2026-10-13 04:29:19 -04:00
Brandon Thomas 34117efc24 Merge pull request #354 from webdevtodayjason/automation-thin-tolerance
Keep automation thinning within its tolerance
2026-10-13 04:28:36 -04:00
Brandon Thomas 651b9049a8 Merge pull request #353 from webdevtodayjason/trim-keeps-clip-gain
Keep the clip gain line where a clip is trimmed or separated
2026-10-13 04:28:31 -04:00
Claudio bee999584f Make clip.unloop recognise loops made by clip.loop, even after edits
clip.unloop found loop copies only by comparing content of back-to-back
clips. As soon as the source clip's notes were edited, its content no
longer matched the copies and unloop answered "the selection is not a
looped clip" for the very loop that clip.loop had just created.

clip.loop now tags the origin and its copies with a shared `loop_id`
(new optional Clip field, serde default so old sessions load), and
unloop treats equal-length adjacent clips with the same loop_id as one
loop. The content comparison stays as a fallback for loops without a
tag. unloop with mode=flatten clears the tag so the copies become
ordinary independent clips.

Propagating edits from the source to the copies is not part of this
change.
2026-10-12 23:54:33 -06:00
grovebit 1953f2ecc6 Track height: resize live while dragging, Pro Tools modifiers and Ctrl+Up/Down
Dragging a track's bottom edge showed nothing until release; then the
track jumped to the nearest height preset. Pro Tools' track height
modifiers and keys were missing too.

Dragging:
- The edge follows the pointer as in Pro Tools: the track steps through
  Micro…Extreme while the pointer moves, and the tracks below move with
  it. The whole drag is one undo step (execute_merged, ended on release
  like fader moves).
- Cmd (Ctrl on Windows and Linux) drags to any height from Micro to
  Extreme instead of snapping. The model gains TrackHeight::Custom
  (points), and track.height takes a number of points as well as a
  preset name.
- Alt resizes every track the Edit window shows; Alt+Shift resizes the
  selected tracks too. The track menu's Track Height items follow the
  same rule.
- The drag is measured from where it started, not from the edge's
  current place: when the tracks above the dragged one resize they move
  its edge, and edge-relative math flipped between two heights on every
  frame. This also fixes rows showing playlist lanes, which a plain
  click on the edge used to make taller.

Keys:
- New commands track.height_up and track.height_down move each track
  one preset taller or shorter; a track already at Extreme or Micro is
  left alone, with no undo step. Control+Up/Down runs them on macOS, as
  in Pro Tools. Windows and Linux use Ctrl+Up/Down, because the Start
  key Pro Tools uses there isn't available. Alt steps every shown track,
  Alt+Shift the selected ones.
- Ctrl+Up/Down used to run Extend Edit Up/Down, keys Pro Tools doesn't
  use for them. Those commands now have Pro Tools' Control+Shift+P and
  Control+Shift+; on macOS (macOS sends the latter as Control+:).
  Windows and Linux reach them from Edit > Selection.
- Off macOS, menus no longer show Mac Control chords that can't be
  pressed there (Time Lock's Ctrl+Alt+L, Toggle Recent Playlist's
  Ctrl+Alt+Shift+Left, the new Extend Edit keys); the arrow chords that
  fixed() maps to Ctrl keep their labels.

Track headers show each row of controls only once it fits: the keys
from 46 points and the view and automation selectors from 86, where
each row ends. The old 30 and 60 point cutoffs suited the presets only
and cut controls off at heights in between. Every preset draws exactly
as before.

Tests: the model's custom heights, steps and JSON; the engine's point
heights and steps, with no undo step when nothing changes; in the UI,
the live drag and its single undo step, Cmd, Alt and Alt+Shift drags
that hold steady while the pointer does, the height keys on macOS,
Windows and Linux, and the Extend Edit keys on macOS.
2026-10-12 22:11:34 +01:00
grovebit 72288fc77e Make the Shuffle, Slip, Spot and Grid edit modes work like Pro Tools
The edit modes only partly worked. Shuffle changed Cut, Clear and Paste
but not moves or trims. Spot froze drags and had no dialog. Grid snapped
relative to whichever clip was under the pointer, and Relative Grid
acted like Absolute. Behaviour now follows the Edit Modes chapter of the
Pro Tools Reference Guide.

Shuffle
- Grabber drags lift the clips out (later clips close the gap) and land
  them at the nearest clip boundary, pushing later clips right; an empty
  track takes them at the session start. The drag outline shows where
  each track's clips will land.
- Trims move the later clips (a trimmed start stays put and the material
  slides under it). Duplicate, Duplicate and Extend, Repeat and Clip List
  drops make room, and Paste over a selection replaces it.
- Moves lift each clip by id and close the hole it leaves, so crossfade
  partners and unselected clips keep their audio, and a crossfaded pair
  moved together stays a crossfade. Ripples never push clips past the
  session start, never split a crossfade (refused with a reason), and
  drops never land inside a clip or crossfade.
- A time- or edit-locked clip and everything after it stay put when
  Shuffle closes or opens gaps. Edits that would push into one are
  refused ("not enough room before a locked clip"), and trims stop short
  of it. Insert Time and Cut Time are timeline operations and still move
  everything.
- With Automation Follows Edit, automation moves with every ripple, up to
  a locked clip, and travels with moved clips; with it off, Shuffle edits
  leave automation alone. Shuffle Clear used to move the clips but leave
  their automation behind (#232).
- Shuffle Lock: Cmd-click (Ctrl-click on Windows and Linux) the Shuffle
  button while in another mode. While it's on, the button shows a padlock
  and every way into Shuffle (keys, button, commands) is refused.
- A drag works out every lift and landing point once per target track
  (`lift_plan`, `shuffle_slots`, and a `DragPlan` kept for the drag). In a
  release build, dragging 100 clips on a track of 20,000 took 29 ms a
  frame and a 135 ms drop; now it's under a microsecond a frame and a 5 ms
  drop.

Slip, Spot and Grid
- Slip: trims stop at the neighbouring clips instead of overlapping them.
- Spot: a Grabber click or drag, a Trim click or drag and a Clip List drop
  open a Spot dialog: Start, Sync Point and End in any time scale, the
  Duration, and the clip's Original Time Stamp. Trim clicks pick the
  nearer edge.
- Grid: a drag snaps the dragged clip's start (its sync point, when it
  has one inside the clip) to the grid. Relative Grid moves by whole grid
  steps and keeps the clip's offset from the grid; a cut or copy pasted
  in Relative Grid lands as far after a grid line as the copied range
  started after one. Holding Cmd (Ctrl on Windows and Linux) while
  dragging or dropping from the Clip List swaps Grid and Slip. Grid lines
  no longer fall a step short at tempos where a line isn't a whole number
  of samples.
- Choosing Grid from another mode picks whichever of Absolute and
  Relative Grid was used last; F4 in Grid toggles them, like clicking the
  Grid button again. Options > Edit/Tool Mode Keyboard Lock, which did
  nothing, now stops F4 and Alt+4 from switching them (toolbar clicks
  still do).
- Snap to Grid in Shuffle, Slip and Spot: Shift+F4, Shift-click on the
  Grid button or on another mode's button, or hold F4 with F1-F3. The
  Edit cursor and selections snap to the grid while clip moves and trims
  follow the main mode; the Grid button is outlined while it's on.
- Option+1-4 (Alt+1-4 on Windows and Linux) work like F1-F4, and the `
  key cycles Shuffle, Slip, Spot and Grid, skipping a locked Shuffle.
- Insert Silence pushes later material only in Shuffle; in the other
  modes it clears the selection, as in Pro Tools.

Also
- Dragging clips from several tracks keeps each track's distance from the
  grabbed clip's track. Drops past the last track, or onto a track that
  can't play the clips (audio onto MIDI, an aux or the master), are
  refused; clips moved onto an aux used to vanish into its unused
  playlist.
- Undo and Redo keep the Edit mode, Shuffle Lock, Snap to Grid and the
  Grid kind. Start trims keep a clip's sync point on its audio, or drop
  it. Imported BWF time stamps are converted from the file's rate (read
  from its header) to the session's, and the Original Time Stamp no
  longer adds the session start twice. Typed positions that overflow are
  ignored instead of panicking.

Commands: `edit.move_clips` follows the mode, and its `track` moves the
first clip's track there and keeps the others' distances. `edit.trim_clip`,
`edit.snap_to_grid` (Shift+F4) and `edit.shuffle_lock` are new, and
`edit.mode` takes `snap_to_grid`. Mode buttons expose their names to
accessibility tools.

Tests: crates/{engine,ui-egui}/tests/edit_modes.rs and the op tests in
edit.rs; the random-edits property test also toggles clip locks.
2026-10-12 21:49:31 +01:00
webdevtodayjason c39773b62d Keep automation thinning within its tolerance 2026-10-12 15:32:48 -05:00
webdevtodayjason 34d8488284 Keep the clip gain line where a clip is trimmed or separated 2026-10-12 15:32:40 -05:00
reoniazx 5ec8c21b19 Preview waveform amplitudes during fade drags 2026-10-13 02:48:00 +08:00
reoniazx 4ac47b3a7f Add draggable midpoint curvature for clip fades 2026-10-13 02:48:00 +08: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