Commit Graph
838 Commits
Author SHA1 Message Date
Brandon Thomas f1ff34f533 Merge pull request #132 from vbasky/fix/92-clip-gain-stretch
Bake varispeed into rendered clip gain, then stop stretching it again.
2026-10-11 19:50:49 -04:00
Brandon Thomas 0f32d4d4cc Merge pull request #131 from vbasky/fix/59-bounce-bus
Render a named bus when bouncing, and reject an unknown one.
2026-10-11 19:50:45 -04:00
Brandon Thomas c716584e19 Merge pull request #130 from vbasky/fix/88-audiosuite-gain
Keep clip gain when AudioSuite inverts or duplicates.
2026-10-11 19:50:42 -04:00
Brandon Thomas a8452b908c Merge pull request #129 from vbasky/fix/101-playback-speed
Restore full playback speed when transport stops.
2026-10-11 19:50:38 -04:00
Brandon Thomas f935d6ee79 Merge pull request #127 from vbasky/fix/95-cli-command-ids
Treat dotted command ids as engine.execute in the CLI app shorthand.
2026-10-11 19:50:34 -04:00
Brandon Thomas ceae137642 Merge pull request #126 from vbasky/fix/96-closed-stdout
Keep going when the CLI's stdout is closed.
2026-10-11 19:50:30 -04:00
Brandon Thomas 776b771f59 Merge pull request #128 from vbasky/fix/89-surround-import
Import multichannel audio onto a track of the same width.
2026-10-11 19:49:37 -04:00
Brandon Thomas b76161b698 Merge pull request #125 from vbasky/fix/87-midi-export
Export MIDI controllers and honour the tracks filter.
2026-10-11 19:49:34 -04:00
Brandon Thomas cc0eb1caf5 Merge pull request #124 from vbasky/fix/91-save-copy
Save a session copy without retargeting the open document.
2026-10-11 19:49:31 -04:00
Brandon ThomasandClaude Opus 5.5 adb14703e4 Merge origin/main into fix/89-surround-import
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:49:11 -04:00
Brandon ThomasandClaude Opus 5.5 cb655d9ec4 Merge origin/main into fix/87-midi-export
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:49:07 -04:00
Brandon ThomasandClaude Opus 5.5 6a12573e4f Merge origin/main into fix/91-save-copy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:49:02 -04:00
Brandon Thomas cffc932e57 Merge pull request #123 from vbasky/fix/86-consolidation-files
Keep each consolidation in its own audio file.
2026-10-11 19:48:51 -04:00
Brandon ThomasandClaude Opus 5.5 e7c31321f3 Volume to clip gain: keep the level outside the selection; keep clip gain points; ramps reach their end
- When every lane point lay inside the selection the lane was cleared,
  so the held level outside fell back to the fader (6 dB louder in the
  repro). Clear only when all clips lie inside the selection.
- The clip's own gain points inside the selection are kept, offset by
  the lane, instead of being dropped.
- Volume/clip-gain ramps now end on the block's last sample, so
  interpolate over n-1 steps and actually reach the end value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:44:08 -04:00
Brandon ThomasandClaude Opus 5.5 55dfcc95d1 Close the export-clips test the rebase left open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:43:41 -04:00
Brandon ThomasandClaude Opus 5.5 75430d2472 Close the export-clips test the rebase left open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:43:41 -04:00
Brandon ThomasandClaude Opus 5.5 113292cc1a Close the export-clips test the rebase left open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:43:40 -04:00
Jafz2001 9850a41f29 perf: reuse realtime mix buffers 2026-10-11 19:36:39 -03:00
Jafz2001 eddc846f51 perf: adapt meter polling and repaint rate 2026-10-11 19:33:37 -03:00
Jafz2001 d552da531f fix: keep macOS plugin editors out of the Dock 2026-10-11 19:03:38 -03:00
Brandon ThomasandClaude Opus 5.5 e60c41c2c8 Release workflow: pass the Azure signing secrets so Windows builds are signed
Since the release workflow moved to storytold/craft-common-actions, it has
passed only the Apple and WINDOWS_CERTIFICATE secrets. The signing job reads
values from the release environment, but only for names the caller lists, so
the AZURE_* names that Trusted Signing uses came through empty and Windows
builds would ship unsigned. Same fix as storytold/craft-launcher#174.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:03:21 -04:00
Jafz2001 b9345a3576 feat: improve mixer routing and numeric controls 2026-10-11 18:29:02 -03:00
Fabio LussiandClaude Opus 5.5 e102ed3656 Windows: compile DX12 shaders with FXC
wgpu's default DX12 compiler (Auto) loads whichever dxcompiler.dll the DLL
search path finds. SoundCraft ships none, and without a matching dxil.dll the
unsigned-DXIL warning fails wgpu's indirect-validation pipeline, the device
is lost and the app exits before the first frame. Pin the compiler to FXC,
which is part of Windows; WGPU_DX12_COMPILER still overrides it.

Fixes #185

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:37:56 +02:00
Fabio LussiandClaude Opus 5.5 27d934e726 macOS: build the DMG with hdiutil create so the app passes codesign --strict
hdiutil makehybrid -hfs put a com.apple.FinderInfo on every file in the
image, so the signed app inside the DMG (and any ditto copy of it) failed
codesign --verify --deep --strict. Build the image with
hdiutil create -srcfolder (retried, since create can hit "Resource busy" on
CI), clear build-time xattrs before signing, and verify the app inside the
finished DMG and a ditto copy of it with --strict.

Ported from storytold/pdfcraft#823 and storytold/lightcraft#959.

Fixes #176

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:09:39 +02:00
grovebit 18a2558104 Ask to save changes before closing a modified session
Close Session, New, Open, Revert, Quit, the window's close button,
dropping a session file on the window and opening one from the Workspace
window all replaced or closed the session without asking, so unsaved
edits were lost (#136). They now ask first: Save, Don't Save or Cancel.
Save on a session that has never been saved goes through Save As, then
carries on. Revert asks for confirmation instead, and Revert to Saved is
unavailable until the session has a file. A take being recorded is
stopped first, so it counts as a change. The web build has nowhere to
save, so it offers only Don't Save and Cancel. Calls from the control
channel, MCP and the CLI never ask.

Selecting, locating, zooming and scrolling no longer mark the session
modified, so the question only comes after real edits. Undoing back to
the saved state makes the session clean again. The engine compares the
document with the one last opened or saved, ignoring the selection, the
playhead and the zoom, once per revision.

Fixes #136
2026-10-11 20:13:48 +01:00
grovebit 54e3761d62 Open sessions whose plugin parameters were saved as null
JSON has no NaN or infinity, so serde writes them as null, and loading
null into a plugin parameter failed the whole session with "not a
SoundCraft session: invalid type: null, expected f32". One plugin
reporting a non-finite value, or a value like 1e39 that overflows f32
in an edited file (it loads as infinity), made the session and every
later save and autosave of it unopenable.

Insert and instrument parameters that are null or overflow f32 are now
dropped on load, so they keep the plugin's default and the rest of the
session opens.
2026-10-11 20:03:47 +01:00
grovebit 153381e03b Flush saves to disk and keep backups of the session file
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.
2026-10-11 20:03:47 +01:00
grovebit 62a0b683e5 Ask before Save As replaces another session
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.
2026-10-11 20:03:47 +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 d7c3818859 Let the desktop app sleep when idle or covered
The app sent the window title to egui on every frame. Each viewport
command asks for another frame, so the window redrew at the display's
refresh rate with nothing happening: 35-43% of a CPU core and 13-20% of
the GPU per window on a 120 Hz Mac. While the window was covered or
minimized, eframe runs only App::logic, with no vsync to pace it, and
the same request kept one core at 100%.

The title is now sent only when it changes. The last title sent is kept
in egui's context data rather than compared with the window's, because
winit can't read a title back on X11 or Wayland.

Some work only ran because another frame always came, so an idle window
still wakes once a second, and every 30 ms while a plugin editor is
open. Autosave and the macOS menu bar's throttled refresh run on
frames, and plugin editors are native windows egui never hears from:
their edits (VST3 performEdit, CLAP output events) and main-thread
requests are serviced on frames. egui takes its predicted frame time
off every delay, so it is added back. The menu bar can now trail a
shortcut by up to a second instead of 250 ms.

Playback, falling meters, pending screenshots and scripted input ask
for the next frame from ui() instead of logic(). eframe skips ui() for
a covered or minimized window, so a hidden window runs on the heartbeat
instead of spinning, and egui redraws it as soon as it shows again.

Autosave was timed by egui's frame clock, which stands still while the
window is hidden, so a modified session wasn't autosaved until the
window showed again. It is timed by the wall clock now. Only the
desktop app sets an autosave folder, so the web build never reads it.

Tests check that an unchanged title sends no command, that only a shown
window animates while a hidden one wakes once a second, and that a
hidden window still autosaves.

Measured on a 120 Hz MacBook with a release build, the demo session and
audio off: an idle Edit window went from 35-43% CPU and 13-20% GPU to
1.5% and 0.4% (the insertion point blinks), the Mix window to 0.2%, and
a covered window, idle or playing, from ~100% CPU to 0%.
2026-10-11 17:31:26 +01:00
Dylan M. Taylor 240aa848a0 ci: refactor workflows to use storytold/craft-common-actions (#184)
* ci: refactor workflows to use reusable artcraft-common-actions

* ci: point reusable workflows to storytold/craft-common-actions

* ci: pin storytold/craft-common-actions to commit 27a978f and pass secrets explicitly

* ci: use @main (unpinned) for craft-common-actions with explanation comment
2026-10-11 11:52:00 -04:00
grovebit 534b44d430 Reopen the window at its last size and interface scale
The window opened at 1600x1000 on every launch, and the interface scale
set in UI Customization reset to 100%. The app now keeps the window's
size, whether it was maximized and the scale in its preferences
(ui.json, saved on quit) and restores them at launch. The size is kept
in unscaled points, so a scaled interface doesn't change the window's
size from one launch to the next. The position is left to the system,
so a disconnected monitor can't leave the window off screen. A window
too big for the screen it opens on (sized on a larger monitor) opens
maximized instead and keeps its saved size for that monitor. Runs with
SOUNDCRAFT_NO_PREFS still start at the defaults.
2026-10-11 15:57:39 +01:00
grovebit 992f60c79c Trackpad-friendly Edit window: smooth panning, pinch and Cmd+scroll zoom
- Pinch and Cmd+scroll zoom the timeline around the pointer. egui 0.36 turns
  both into zoom_delta, so the old Cmd+scroll branch never ran; Shift+wheel
  pans sideways again for the same reason.
- Two-finger panning keeps the sub-sample remainder, so slow motion still
  moves at the closest zoom; the scrollbar drag pans the same way (its range
  now takes in the view, so the thumb stays in the bar), and the rulers pan
  too. Panning stays open-ended to the right, as before.
- A swipe keeps to the axis it starts along, so panning doesn't drift between
  tracks and scrolling doesn't drift in time. The larger share of its first
  6 points decides until egui reports the scroll over: the fingers lift, or
  150 ms pass without wheel events.
- The MIDI editor scrolls by the point instead of rounding each frame's delta
  to whole rows (slow swipes did nothing), and its range fits the panel
  (pitch 0 could not be reached; a taller panel showed blank rows).
- Zoom and scroll write through `Engine::zoom_mut` (`view` commands): no
  playback resync (a session copy sent to the audio thread every frame), no
  copy of the whole session per frame, no journal entry per frame, and no
  end to an undo gesture (scrolling during an automation pass made each
  write its own undo step). New `view.zoom_by {factor, x?}`;
  `view.scroll` saturates; Fill Window and Zoom to Selection stay within the
  zoom range (zooming out after them on a long session zoomed in).
- Control channel: `ui.scroll` (trackpad swipe or wheel notches) and
  `ui.zoom` (pinch); `session.inspect` reports zoom.
2026-10-11 15:21:46 +01:00
grovebit 7208c610cd Make Clear All Clip Indicators clear them
Track > Clear All Clip Indicators (Alt+C) was a stub: the red clip
indicators on the meters stayed lit until another session was opened.
The command now clears them on every track's meter and the main meter.
2026-10-11 14:10:10 +01:00
grovebit b2a3b0f861 Keep armed tracks' meters live while the transport is stopped
Record-enabled and input-monitored tracks were heard while stopped, but
their meters froze (#42). The audio thread returned before copying the
meters whenever it wasn't playing. The UI faded every meter to zero while
stopped and stopped redrawing once they reached zero. After a selection
had played to its end, the stale end point also silenced input
monitoring until the next Play.

The audio thread now copies the meters for every block it renders and
forgets the end point and loop when playback stops. While a track is
armed or monitoring input, the UI keeps feeding and redrawing its
meters, and the Mix window's per-channel meters include armed tracks.
2026-10-11 14:10:10 +01:00
grovebit 16b7c38b6b Scroll a plugin window's parameters when they don't fit
A plugin window listed every parameter in one grid. VST3 and CLAP
plugins expose all of theirs, so a plugin with more parameters than fit
on the screen ran off the bottom edge, and the ones below it couldn't be
reached (#6). The parameters now scroll inside the window, below the
header and the EQ curve, and the window stays on the screen.
2026-10-11 14:09:03 +01:00
Royce 99992fe78c fix(mix): push an instrument's stored params to its plugin before each block (#152)
When a session is reopened with a saved instrument-level change such as
'level = -12 dB', the value sat in t.instrument.params but was never
written to the live plugin instance, so the bounce produced the same
audio as the default (-6 dB). The inserts loop already calls
apply_params per block; the instrument branch did not.

Call apply_params(slot, ins, t, INSTRUMENT_SLOT, pos) before each
instrument's process() so a saved level (or any other param set via
'mix.insert_state') reaches the plugin exactly the way insert params do.

Refs #152.
2026-10-11 15:35:02 +08:00
Brandon ThomasandClaude Opus 5.5 75d3901ab4 CI: run FreeBSD and Windows ARM64 checks nightly and on release, not per PR
They ran on every push to main and on any PR touching Cargo.toml/Cargo.lock,
which is nearly every PR. See craftrules release/playbook.md §2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:03:08 -04:00
Coy Geek 592991789e fix(theme): restore Light Mix Instrument label contrast
Use the current populated insert fill and slot outline for Instrument
slots. This restores readable Light and System-Light labels while
preserving the original Dark colors, hover behavior and picker route.

Add a root-UI regression that checks actual emitted colors in four
palettes, preserves session/audio data, replaces the built-in instrument,
checks parameter defaults and restores the session through undo.

Include the authorized SMF match-guard prerequisite and a contract test
for first-name-wins and ignored metadata. All 16 focused SMF tests and
the complete populated Instrument regression passed.

Full CI passed formatting and cleared the SMF lint, then stopped on
unchanged metronome and MP3 Clippy expressions. Later CI stages did not
run. Full local CI before publication was explicitly waived.

Closes #161
2026-10-10 20:46:49 -07:00
skylar taylor-barrick 7414719f37 Merge pull request #167 from skylartaylor/codex/ui-suite-integration-20261010
Add shared docking with SoundCraft theme and MIDI sizing
2026-10-10 19:47:00 -06:00
skylar taylor-barrick 814ae99a32 UI shared controls: isolate palette ownership in rendering tests 2026-10-10 19:27:18 -06:00
skylar taylor-barrick 0434bb1397 UI.9: recover incompatible saved panel layouts independently 2026-10-10 18:52:14 -06:00
skylar taylor-barrick 7c64b27753 UI.9: reconcile upstream before shared UI landing 2026-10-10 18:24:17 -06:00
skylar taylor-barrick 5589d97257 UI.9: reconcile upstream before shared UI landing 2026-10-10 18:14:05 -06:00
Brandon ThomasandClaude Opus 5.5 4410486fc2 Release 0.5.0: linux-riscv64 tarball, version bump (#165)
- release.yml: new linux-riscv64 job, cross-compiled on ubuntu-24.04 against ports.ubuntu.com
  riscv64 libs (the linux job's -dev packages incl. libasound2-dev:riscv64), CLI smoke-tested
  under qemu-riscv64; wired into the release job's needs so SHA256SUMS covers it.
- packaging/linux/package.sh: CROSS_ARCH/CROSS_TARGET/CROSS_COMPILE/EMULATOR support.
- clap-host: c_char -> u8 without a cast that is a no-op (clippy error) where c_char is u8.
- Version 0.4.0 -> 0.5.0; docs list the riscv64 tarball.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
v0.5.0
2026-10-10 19:37:37 -04:00
Vikram Bhaskaran 849cc79675 Keep each consolidation in its own audio file.
A second consolidate of the same track reused Audio Files/{name}.wav, and saving then skipped the rename because that path already belonged to the earlier source. New sources now take a free name, and a save splits sources that already share a path instead of overwriting one file.
2026-10-11 08:49:20 +11:00
Vikram Bhaskaran 2afbfd9477 Save a session copy without retargeting the open document.
Save Session Copy In wrote through the normal save, which renamed the session, marked it clean, and pointed it at the copy. The open session now stays dirty, named, and path-ed as it was, while the copy is a separate folder.
2026-10-11 08:49:18 +11:00
Vikram Bhaskaran 09d64ab2ec Export MIDI controllers and honour the tracks filter.
file.export_midi copied notes only, so controller, pressure, and pitch-bend events were dropped, and the tracks parameter was ignored. The file now includes those events, and a tracks list limits the export. Naming only non-MIDI tracks is an error instead of exporting every other MIDI track.
2026-10-11 08:49:08 +11:00
Vikram Bhaskaran 404940b40d Import multichannel audio onto a track of the same width.
A new track for an imported file was clamped to stereo, so a 5.1 file lost every channel past the left and right. The track now follows the file's channel count. Importing onto an existing track is unchanged.
2026-10-11 08:48:52 +11:00
skylar taylor-barrick 63b487589c UI.9: preserve SoundCraft workflows with shared docking 2026-10-10 15:37:36 -06:00