Commit Graph
630 Commits
Author SHA1 Message Date
TheMojoMan 5e1c247eda fix(ui): horizontally scroll the Edit toolbar
Groups clipped when the window was narrower than the toolbar; wrap the whole row in a horizontal ScrollArea so every tool stays reachable.
2026-10-12 10:10:00 +02:00
NetterSilver e1e15cf3ab Compute timecode frame counts in i128 and clamp (#248)
Timecode::to_frames multiplied caller-supplied hours in i64 without a
check, so "9223372036854775807:00:00:00" panicked under overflow checks
and wrapped without them. The count is now computed in i128 and clamped
to i64, and samples_at_frame clamps instead of returning 0 on overflow,
so huge labels saturate toward the right end of the timeline.
2026-10-12 08:53:51 +02:00
NetterSilver cfce845f70 Saturate the bar offset for bars before 1 (#249)
bar_start_tick subtracted 1 from the bar before its saturating multiply,
so the minimum i64 bar panicked under overflow checks. Parsing
"-9223372036854775808|01|000" as Bars|Beats now returns a saturated
negative position instead.
2026-10-12 08:52:59 +02:00
Jafz2001 87b348fecc Complete routing folder management 2026-10-11 23:50:06 -03:00
grovebit acd9e7d54e Play MIDI controllers through instruments
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.
2026-10-12 02:57:22 +01:00
grovebit 33ad8264c8 Give plugins the session's tempo and transport
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.
2026-10-12 02:56:55 +01:00
grovebit 75fae5535f Share the session with the UI instead of copying it every frame
The Edit window copied the whole session once per frame for the rulers and
twice for every visible track (track_row and lane_interaction), plus once more
for each track shown as playlists and once for the Timecode 2 ruler. The MIDI
editor copied it once per frame, and every Edit row and Mix strip copied its
track. A copy includes every plugin's parameters and saved state: in a 23-track
session with three plugins per track (200 parameters and 64 KB of state each),
one copy took 0.2 ms and each Edit window frame 15.6 ms, which is paid on every
frame of playback, dragging and scrolling.

These now hold the engine's Arc<Session> (session_arc). It is still a snapshot:
a command run later in the frame copies on write, as it already does to keep
its undo step. timebase_ruler takes the frame rate as a parameter, so the
Timecode 2 ruler no longer needs a copy with another frame rate.

Median frame times in a release build, with 21 of 23 tracks on screen:

- The demo session with its tracks tripled: Edit window from 1.25 to 0.68 ms.
- Plus 5,000 volume automation points per audio track: from 2.03 to 0.68 ms.
- Plus three plugins per track: Edit window from 15.6 to 0.69 ms, Mix window
  from 1.14 to 0.71 ms.

The Edit window (every ruler, a playlist lane and an automation lane), the MIDI
editor and the Mix window render byte-identical images before and after.
2026-10-12 02:31:02 +01:00
Fabio LussiandClaude Opus 5.5 092b3cc890 Consolidate covers the whole edit selection
Consolidate clamped its range to the track's last clip end plus one
second, so on an empty track a 0-5 s selection made a 1 s clip and a
2-7 s selection made nothing. It now renders, clears and places exactly
the selection, and refuses a selection too long to render instead of
placing a shorter clip. Prepare DPE keeps the old per-track bound.

Fixes #164

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:02:56 +02:00
Brandon Thomas b9dec547df Merge remote-tracking branch 'origin/main' into sampler 2026-10-11 21:02:27 -04:00
Brandon Thomas 525f00b719 Merge pull request #157 from grovebit/unsaved-changes
Ask before discarding unsaved changes; Save opens Save As for new sessions
2026-10-11 20:59:57 -04:00
Brandon Thomas 48c3f65e12 Merge pull request #160 from grovebit/menu-fixes
Clip > Rename opens its dialog; Undo and Redo name their step; the Window menu's keys work
2026-10-11 20:59:52 -04:00
Brandon Thomas 6754f04a05 Merge pull request #192 from grovebit/idle-repaint
Let the desktop app sleep when idle or covered
2026-10-11 20:59:48 -04:00
Brandon Thomas 7f2a6977b5 Merge pull request #56 from grovebit/trackpad-scrolling
Trackpad-friendly Edit window: smooth panning, pinch and Cmd+scroll zoom
2026-10-11 20:59:45 -04:00
Brandon Thomas c5a8fd2f52 Merge pull request #49 from grovebit/drag-selection
Fix drag selection and the insertion point in the Edit window
2026-10-11 20:59:41 -04:00
Brandon Thomas 7c7e8deaaa Merge pull request #158 from natocool2/fix/web-files
Web: open, import, save and export files in the browser (fixes #63, fixes #64)
2026-10-11 20:59:37 -04:00
Brandon ThomasandClaude Opus 5.5 c5efedd740 Merge drag-selection (#49) into trackpad
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:57:06 -04:00
Brandon ThomasandClaude Opus 5.5 ef0ce90b66 Merge origin/main into drag-selection
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:56:43 -04:00
Brandon ThomasandClaude Opus 5.5 2ec2698331 Grid moves snap the grabbed clip, not whatever is under the pointer now
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:56:37 -04:00
Brandon ThomasandClaude Opus 5.5 f855836af1 Idle test: playing repaints at the adaptive meter rate from #207
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:51:00 -04:00
Brandon ThomasandClaude Opus 5.5 7a1035a3d5 Merge origin/main into sampler-instrument
#181 already pushes instrument params each block, so drop the duplicate
apply_instrument_params. Import Session Data now brings a Sampler's sample
along and remaps its source id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:45:12 -04:00
Brandon ThomasandClaude Opus 5.5 b01abf6dd7 rustfmt the mix.send spec
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:43:27 -04:00
Brandon ThomasandClaude Opus 5.5 d37386721a Merge origin/main into unsaved-changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:42:42 -04:00
Brandon ThomasandClaude Opus 5.5 1c52ad1c6e Merge origin/main into idle-repaint
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:42:34 -04:00
Brandon ThomasandClaude Opus 5.5 e7e4a19cfa Merge origin/main into fix/web-files
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:42:26 -04:00
Brandon Thomas 03bebb572d Merge remote-tracking branch 'origin/main' into menu-fixes 2026-10-11 20:41:39 -04:00
Brandon Thomas 5a34f4118c Merge pull request #200 from Jafz2001/fix/mixer-routing-qol
Improve mixer routing menus and numeric controls
2026-10-11 20:41:06 -04:00
Brandon Thomas 59476adfd3 Merge pull request #202 from Jafz2001/fix/macos-plugin-editor-windows
Keep macOS plugin editors out of the Dock
2026-10-11 20:41:02 -04:00
Brandon Thomas 2d7311197d Merge pull request #188 from grovebit/window-fixes
Scroll long plugin windows; reopen the main window at its last size and scale
2026-10-11 20:40:58 -04: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
Brandon Thomas 3e97e49bfb Merge pull request #70 from grovebit/export-clips-shortcut
Shift+Cmd+K exports clips as files
2026-10-11 20:40:48 -04:00
Brandon Thomas ef04444f69 Merge pull request #84 from grovebit/record-to-disk
Record takes straight to disk
2026-10-11 20:40:44 -04:00
Brandon Thomas 4e1c18f949 Merge pull request #181 from rollroyces/fix/soundcraft-152-instrument-level-bounce
fix(mix): push an instrument's stored params to its plugin before each block (#152)
2026-10-11 20:40:40 -04:00
Brandon ThomasandClaude Opus 5.5 3055cbb0c0 Measure the #152 regression test past parameter smoothing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:37:56 -04:00
Brandon ThomasandClaude Opus 5.5 e27a7e8ea4 Merge origin/main into idle-repaint
Keep the adaptive repaint rates from #207 and meters_live() from #187 in ui().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:34:51 -04:00
Brandon ThomasandClaude Opus 5.5 b25a49ee5a Merge origin/main into fix/mixer-routing-qol
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:34:16 -04:00
grovebit 157e2a9547 Fix drag selection and the insertion point in the Edit window
Fixes #166: dragging a selection up from the last track deselected it and
selected every track above instead. Fixed, along with related Edit-window drag
problems:

- Drag selection spans exactly the tracks between the press and the pointer,
  in either direction, and the whole window updates in the same frame.
- Shift+click in another track adds it to the selection with the same range,
  as in Pro Tools, so the selected tracks no longer have to be adjacent. A
  click outside the range extends it.
- Track names follow Pro Tools' rules in the Edit window, the Mix window and
  the Tracks list: Shift+click selects every track between the nearest
  selected one and the clicked one, and Cmd+click (Ctrl+click on Windows and
  Linux) adds or removes one. A click on a name used to replace the selection;
  only the Edit header's background added, and it treated Shift like Cmd.
- Dropping a track's name plate lower down no longer sends the track to the end.
- Drags act where you pressed: a quick drag from a clip edge now trims instead
  of moving the clip.
- Dragging past the edge of the track area scrolls, and a flick past the last
  track selects down to it.
- The insertion point moves on press, shows immediately, and only on the
  selected tracks.
- edit.select no longer makes a new document revision when nothing changed
  (each one is resent to the audio engine).
- Fixes a panic when a fade drag's clip shrinks to zero length.
2026-10-12 01:32:39 +01: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
Brandon Thomas 7c26ea4f44 Merge remote-tracking branch 'origin/main' into clear-clips 2026-10-11 20:21:45 -04:00
Brandon ThomasandClaude Opus 5.5 3b947b94bd Deselect All also clears the Clip List's file selection
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:21:45 -04:00
Brandon ThomasandClaude Opus 5.5 7b2f1ac200 Merge origin/main into export-clips-shortcut
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:20:34 -04:00
Brandon Thomas 69f76ca0a5 Merge remote-tracking branch 'origin/main' into rec-disk 2026-10-11 20:19:50 -04:00
Brandon ThomasandClaude Opus 5.5 d87d0373ee Record to disk: keep a partial frame between pops so channels never shift
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:19:50 -04:00
Brandon ThomasandClaude Opus 5.5 6a35fcd3a9 Re-targeting a send keeps its level, pan, mute and pre/post
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:19:19 -04:00
Brandon ThomasandClaude Opus 5.5 df49660bb5 Merge origin/main into window-fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:18:16 -04:00
Brandon ThomasandClaude Opus 5.5 ec920cd975 Instrument params: never read insert slot A's automation; regression tests for #152
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:17:53 -04:00
Brandon ThomasandClaude Opus 5.5 8f432ee0c1 Merge origin/main into fix/web-files
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:16:27 -04:00
Brandon ThomasandClaude Opus 5.5 69570e5b54 Cap session bundles at 1 GiB: 4 GiB is all of wasm32's address space
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:16:15 -04:00
Brandon ThomasandClaude Opus 5.5 66bf162aa9 Drop UtilityWindow: AppKit documents it only for NSPanel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:15:57 -04:00
Brandon ThomasandClaude Opus 5.5 8d294c08f8 Merge origin/main into save-safety
Keep the never-overwrite writer, and also give sources that share a path
their own files (#123). Save Copy In checks replace, then uses
save_session_copy (#124).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:15:56 -04:00
Brandon Thomas cc36a3eba6 Merge remote-tracking branch 'origin/main' into menu-fixes 2026-10-11 20:14:54 -04:00