Commit Graph
47 Commits
Author SHA1 Message Date
Brandon Thomas 62d2a47878 Merge pull request #977 from MarekSpodBiedry/issue-951
Follow the desktop light/dark setting for the System theme on Linux (#951)
2026-10-24 01:00:34 -04:00
aiongg 67865b23e8 Build: use craft-media for bitstream and isobmff (#776)
* Build: use craft-media for bitstream and isobmff

soundcraft-video re-exports craft_media::{bitstream, isobmff} from
craft-libs instead of carrying a copy of FilmCraft's crates. The module
paths stay, so crate::bitstream and crate::isobmff resolve as before.
The shared copy is newer: it adds APV, .mp2 as MPEG audio, AAC channel
configurations 11-14, SampleEntry::av1/flac, Mp4Writer::set_sample_entry
and the se(v) fix for i32::MIN.

* Build: pin craft-media to craft-libs f0ad6c1e
2026-10-23 16:41:42 -04:00
Superior f0d5acb800 Deps: fix the workspace build after the dependency bump (#954)
* Audio: migrate file decoding to symphonia 0.6

Upstream bumped symphonia to 0.6 without adapting crates/audio-io, so
`soundcraft-audio-io` no longer built: the probe module, codec parameters
and decoded-buffer APIs all moved.

- Probe with `default::get_probe().probe(&Hint, …)`. The 0.5 prober's
  detection-only path is gone, so scan for the container marker ourselves
  before instantiating a reader, and keep running the existing size checks
  (CAF/MP4/FLAC/WAV) on the first marker — the damaged-file regression
  tests depend on those checks firing before any allocation.
- Skip leading ID3v2 tags with `ReadBytes::ignore_bytes` as before.
- Read `Track::codec_params` (now an `Option`), decode through
  `make_audio_decoder` and `GenericAudioBufferRef::copy_to_vecs_planar`,
  and read the new public `Packet` fields.
- Keep the per-packet frame limit and the sample sanitisation.

* Playback: migrate the audio device layer to cpal 0.18

- Sample rates are plain `u32` now: drop `.0` and the `cpal::SampleRate`
  constructor.
- One `cpal::Error` replaces the build/stream/play/pause error types;
  detect `DeviceNotAvailable` through `error.kind()`.
- Stream builders take `StreamConfig` by value, and device names come
  from `Device::description()` (the deprecated `name()` is gone).
- The test `Fake` implements the new `StreamTrait::{buffer_size, now}`.

* Deps: pin egui back to 0.36 until craft-ui supports 0.37

The dependabot group bump (#767) moved the workspace to egui, eframe and
egui_kittest 0.37, but the pinned `craft-ui` revision still builds against
egui 0.36, so the graph carries two incompatible egui versions and
`soundcraft-ui-egui` does not compile. craft-libs#199 updates craft-ui to
0.37 and is still open; pin 0.36 and refresh the lockfile meanwhile. The
other version bumps from #767 stay.

Re-land egui 0.37 together with the craft-ui update.

* Audio: refuse a size-checked container the probe did not validate

The dependency-migration review showed the preflight size checks can
validate one container marker while symphonia's probe selects a different
container, so a misleading marker could skip the FLAC/MP4/CAF/WAVE checks.
Probe 0.6 reports the selected format only through the reader it returns,
so `open()` now records which container's checks ran (including the
RF64/BW64 RIFF forms) and refuses the stream when a size-checked reader
was chosen without its checks, before any packet is decoded. Markers found
in audio data of non-container streams (MP3, Ogg, AIFF) do not reject
valid files.

Adds a regression test with a fake RIFF prefix followed by a valid FLAC
stream, and doc comments for the functions touched by the dependency
migration (the review's docstring-coverage warning).
2026-10-23 12:15:56 -04:00
MarekSpodBiedry 7c690b19e1 Follow the desktop light/dark setting for the System theme on Linux (#951) 2026-10-23 17:44:09 +02:00
Dylan M. Taylor d47abe484d Use chrono instead of hand-rolled UTC conversion (#844)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the civil_from_days copy behind the desktop app's log timestamp with
chrono as a workspace dependency. The output format is unchanged and pinned by
the existing test. AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic logging on a clock outside chrono's range

chrono's From<SystemTime> unwraps internally, so a clock past year 262143
panicked inside the logger. timestamp now reads the duration since the epoch
and goes through the fallible DateTime::from_timestamp, reading an
out-of-range clock as the epoch, exactly as the pre-epoch clamp already did.
The year is written with {:04} rather than %Y, which would prefix a '+' past
year 9999. Tests cover the far-future and year-10000 cases.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* time: preserve old out-of-range log timestamp (no epoch fallback)

`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any clock
past chrono's year range to 1970-01-01, changing output the old hand-rolled
`civil_from_days` converted exactly. Restore the old conversion domain with a
cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact). Keep the pre-1970
epoch clamp; u64 seconds past i64::MAX saturate rather than falling back.

Pinned old outputs in the test (replacing the u64::MAX/2 -> epoch assertion):
  u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z
  10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-23 11:35:06 -04:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 52471b37e9 chore(deps): bump the cargo-dependencies group across 1 directory with 11 updates (#767)
Bumps the cargo-dependencies group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.151` | `1.0.154` |
| [proptest](https://github.com/proptest-rs/proptest) | `1.11.0` | `1.12.0` |
| [symphonia](https://github.com/pdeljanov/Symphonia) | `0.5.5` | `0.6.1` |
| [zip](https://github.com/zip-rs/zip2) | `9.0.1` | `9.0.5` |
| [cpal](https://github.com/RustAudio/cpal) | `0.16.0` | `0.18.2` |
| [egui](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [eframe](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [egui_kittest](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [libloading](https://github.com/nagisa/rust_libloading) | `0.8.9` | `0.9.0` |
| [muda](https://github.com/tauri-apps/muda) | `0.19.3` | `0.21.2` |
| [winresource](https://github.com/BenjaminRi/winresource) | `0.1.31` | `0.1.34` |



Updates `serde_json` from 1.0.151 to 1.0.154
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.151...v1.0.154)

Updates `proptest` from 1.11.0 to 1.12.0
- [Release notes](https://github.com/proptest-rs/proptest/releases)
- [Changelog](https://github.com/proptest-rs/proptest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/proptest-rs/proptest/compare/v1.11.0...v1.12.0)

Updates `symphonia` from 0.5.5 to 0.6.1
- [Release notes](https://github.com/pdeljanov/Symphonia/releases)
- [Commits](https://github.com/pdeljanov/Symphonia/compare/v0.5.5...v0.6.1)

Updates `zip` from 9.0.1 to 9.0.5
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v9.0.1...v9.0.5)

Updates `cpal` from 0.16.0 to 0.18.2
- [Release notes](https://github.com/RustAudio/cpal/releases)
- [Changelog](https://github.com/RustAudio/cpal/blob/master/CHANGELOG.md)
- [Commits](https://github.com/RustAudio/cpal/compare/v0.16.0...v0.18.2)

Updates `egui` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `eframe` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `egui_kittest` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `libloading` from 0.8.9 to 0.9.0
- [Commits](https://github.com/nagisa/rust_libloading/compare/0.8.9...0.9.0)

Updates `muda` from 0.19.3 to 0.21.2
- [Release notes](https://github.com/tauri-apps/muda/releases)
- [Changelog](https://github.com/tauri-apps/muda/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/tauri-apps/muda/compare/muda-v0.19.3...muda-v0.21.2)

Updates `winresource` from 0.1.31 to 0.1.34
- [Commits](https://github.com/BenjaminRi/winresource/commits)

---
updated-dependencies:
- dependency-name: cpal
  dependency-version: 0.18.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: eframe
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: egui
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: egui_kittest
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: libloading
  dependency-version: 0.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: muda
  dependency-version: 0.21.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: proptest
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.154
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: symphonia
  dependency-version: 0.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: winresource
  dependency-version: 0.1.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: zip
  dependency-version: 9.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-22 23:36:57 -04:00
Brandon ThomasandClaude Opus 5.5 e85a4801e9 Release 0.12.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:16:17 -04:00
Brandon ThomasandClaude Opus 5.5 2cfc56d515 Release 0.12.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:38:08 -04:00
Brandon ThomasandClaude Opus 5.5 b74fca0fb0 Release 0.11.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:57:33 -04:00
Brandon ThomasandClaude Opus 5.5 e8940766d7 Release 0.10.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:17:25 -04:00
Brandon ThomasandClaude Opus 5.5 c1bf044a85 Release 0.9.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:43:22 -04:00
Brandon ThomasandClaude Opus 5.5 2578ea69a3 Release 0.8.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:16:15 -04:00
skylar taylor-barrick 52bd33bb06 Build: take craft-docpath from the merged craft-libs rev
craft-libs#169 is merged, so the fork pin is no longer needed. Pinned to
storytold/craft-libs 82a24b006eefa036b43c09a67fb9a3fbd17ffe63 (main after the merge) rather than the older 12281d07,
which predates resolve_absolute and the Windows colon refusal.
2026-10-17 12:04:05 -06:00
aiongg 1e036eaa17 Build: use craft-docpath from craft-libs 2026-10-17 02:20:54 +00: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
Brandon ThomasandClaude Opus 5.5 3a28a65769 Release 0.7.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:33:59 -04:00
Brandon Thomas c2f052715d Merge main into #319 2026-10-13 06:16:54 -04:00
Limestudio90andClaude Opus 5.5 a1e9660e60 Audio device settings: system, devices, sample rate, buffer size; opt-in ASIO
Setup > Playback Engine now chooses the audio system (cpal host), output
and input device, device sample rate and buffer size, kept per machine
with the UI preferences and applied by reopening the devices (refused
while recording; the transport position is kept). ui.audio_device does
the same for agents and lists every system with its devices, channels,
rates and buffer range.

Player and recorder open the chosen device, fall back to the defaults
for anything unavailable (unplugged device, unsupported rate), retry
with the device's default buffer when the chosen one is refused, and
convert 32/16-bit integer devices from and to the f32 mix through
scratch buffers allocated up front (no allocation in the callbacks).

ASIO is behind an opt-in 'asio' feature (playback and app): building it
downloads Steinberg's ASIO SDK (proprietary licence or GPLv3) and needs
LLVM, so it stays off by default. Verified on Windows with an SSL 2+:
WASAPI output/input switching and buffer 256, ASIO duplex at 128.

Docs: G-01 shrunk, hardware-parity rows, roadmap log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:22:41 +02:00
Limestudio90andClaude Opus 5.5 434c60047d clap-host: host embedded plugin editors on Windows
Plugins that offer no floating editor (most CLAP plugins on Windows) now
get a host window of SoundCraft's: the GUI is created embedded, parented
to a top-level HWND owned by the app window, sized from get_size, resized
on request_resize (applied by the editor's idle on the main thread, then
set_size), and torn down when the user closes the window. Floating
editors work as before; other platforms still report embedded editors
as unsupported.

Docs: plugin-parity editor rows and the roadmap log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:44:36 +02:00
Limestudio90andClaude Opus 5.5 b1cb6cb368 vst3-host: host plugin editors on Windows
Adds a Windows implementation of the editor host window: a top-level HWND
owned by the app's active window (no taskbar button), sized from the
view's rect, resized on IPlugFrame::resizeView, hidden on close and torn
down by the editor link's idle like on macOS. The main thread is the one
Rust names "main", where the app runs its message loop.

Also fixes two Windows-only build problems that kept the VST3 tests from
running there: the fixture's bus flags (the vst3 bindings use i32 enums
with MSVC) and an unused variable in a unix-only test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:24:26 +02:00
Brandon ThomasandClaude Opus 5.5 6cf896e76c Release 0.6.1: rebuild with the fixed shared release workflow
The previous release build failed in craft-common-actions, not in this
repo: riscv64 packaging got --formats deb,rpm,tar (fixed in
craft-common-actions#3), Linux jobs lacked the ALSA headers
(craft-common-actions#5), and Windows signing needed the AZURE_* secrets
listed in the caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:11:17 -04:00
Brandon ThomasandClaude Opus 5.5 df0c4790b7 Release prep: 0.6.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:44:38 -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 Thomas 902220e4d4 Merge pull request #148 from grovebit/heal-shortcut
Cmd+H heals instead of hiding SoundCraft on macOS (#140)
2026-10-11 20:05:09 -04:00
skylar taylor-barrick 7c64b27753 UI.9: reconcile upstream before shared UI landing 2026-10-10 18:24:17 -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>
2026-10-10 19:37:37 -04:00
skylar taylor-barrick 63b487589c UI.9: preserve SoundCraft workflows with shared docking 2026-10-10 15:37:36 -06:00
natocool2andClaude Opus 5.5 6964c50d00 Web: open, import, save and export files in the browser (fixes #63, fixes #64)
The web build could not reach any file: File > Open Session…, Import > Audio… and Import > MIDI…
showed a text box for a path the browser can't read, and every save or export failed with
«operation not supported on this platform», so work was lost on close.

On the web now:
- Open Session…, Import Audio… and Import MIDI… show the browser's file picker; files dropped on
  the page open (sessions) or import (audio, MIDI) by extension. Files travel as bytes; imports are
  one undo step each, like the import commands.
- A session is saved as ONE file, a bundle `<name>.scraftz`: a zip (stored entries) holding the
  `.scraft` and its `Audio Files/` as 32-bit float WAV — the same layout as a session folder, since
  the browser has no folders. `io::open_session_bytes` opens a bundle or a bare `.scraft` (its audio
  is then reported missing). Bundles are hostile input: entry count and size are capped, and
  declared sizes are never read past.
- Every single-file output (save, Save As Template, bounce, MIDI/score/text export, track presets)
  goes through `io::write_file`: a file natively, a download in the browser via a delivery hook
  (`io::set_deliver`) that only the web app installs.
- In the browser Save As asks for the bundle's name (`<name>.scraftz`) instead of a folder path.
Native builds keep writing files and session folders as before. Folder outputs (stems, clip
exports) are still not available on the web.

Pairs with #157 (Save on a never-saved session opens Save As), which the browser needs too: the
web app always starts on the unsaved demo.

New dependency: `zip` (MIT), default features off (stored entries only).

Checked: engine tests for the bundle round trip (every sample identical), same-named sources,
bare sessions and hostile bundles; `cargo xtask ci`. In Chromium on the packaged web build:
⌘⇧I picks and imports a WAV; WAV, MP3, FLAC, OGG, M4A, AIFF and MIDI import by drop; Save As →
`Midnight Groove.scraftz` downloads with the imported audio inside; that bundle reopens in a fresh
page with all tracks and no missing media, and saves again with the same content.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 19:17:07 +01:00
grovebit 4aa837e933 Make Cmd+H heal and Cmd+Alt+H copy to send on macOS
On macOS, Heal Separation (Cmd+H) and Copy to Send (Cmd+Alt+H) never
ran (#140). The system menu bar used muda's predefined Hide and Hide
Others items, which carry those keys, and macOS offers a key press to
the menu bar's key equivalents before the window, so egui never saw
them: Cmd+H hid SoundCraft and Cmd+Alt+H hid the other apps.

Pro Tools uses the same keys for Heal Separation and Copy Automation to
Send, so the keys stay and Hide loses its shortcut instead. Hide
SoundCraft and Hide Others are now plain menu items that call
NSApplication's hide: and hideOtherApplications:. These are safe
objc2-app-kit calls, and both crates were already in the build through
muda. Show All keeps muda's item, which has no shortcut. Windows and
Linux have no such menu, so nothing changes there.
2026-10-10 13:38:23 +01:00
coygeek 1f5bc965fe fix(ui): follow live system appearance
Resolve System appearance from egui on every frame and fall back to Dark when the integration cannot report a theme. Keep receiving OS changes during manual modes and apply the selected palette to both widget style branches and the current root UI.

Remove the cached detector and its unused dependency. Cover startup, live transitions, manual overrides, saved System preferences, and user colors with an application-frame regression. Add reproducible offscreen system-theme inputs and document the fallback.
2026-10-09 18:28:07 -07:00
Brandon ThomasandClaude Opus 5.5 522e59f8f5 Bump version to 0.4.0 (from 0.3.0) for release; refresh contributors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:02:26 -04:00
21f9b07074 Add a selectable Light theme (#32)
* Add a selectable Light theme

* Apply the active theme to the menu bar

* Use the system theme by default

* Expose Appearance in the Setup menu

* Add Light theme screenshot

* Remove the Light theme screenshot

Repo policy keeps large binary assets out of the tree, and the PNG had no
ATTRIBUTION row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Appearance: ui.theme command, Dark default, non-blocking system lookup

- ThemeMode derives Default with Dark as the default (fixes clippy's
  derivable_impls); System and Light are opt-in. LIGHT_THEME starts false.
- New UI command `ui.theme {"mode": "system|light|dark"}` (label
  "Appearance") in UI_COMMANDS; with no mode it reports the current one.
  The UI Customization combo box dispatches it instead of writing state.
- The choice lives in UiState.theme, so it persists with the existing UI
  prefs (ui.json) and is exposed through ui.set / ui.inspect. Restoring a
  window configuration keeps the current theme.
- dark_light::detect() is only called in System mode, on a background
  thread (an AtomicU8 holds the answer; dark until it arrives, then a
  repaint). On the web it is the synchronous matchMedia lookup.
- theme::apply runs only when the effective palette changes, not every
  frame.
- window.ui_customization keeps its catalog path (Window > UI
  Customization) so parity is unchanged; Setup > Appearance still opens
  it. The combo row is labelled "Theme" so it no longer repeats the title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Light theme: move hard-coded colours to theme tokens

The Light palette left dark islands and unreadable text where colours
were Color32 literals. New tokens (Dark values are the old literals, so
the Dark theme renders pixel-identical to main):

- menu bar; toolbar group outline; edit-mode cells (no more dark block
  around Shuffle/Spot/Slip/Grid); counter panel outlines, dividers and
  off toggles; Smart-tool bracket
- selected rows in the Tracks/Groups/Clips lists, track-visible dot,
  selected Edit track header, selected Mix strip
- marker names, alternate-playlist lanes, Universe background/outline,
  Big Counter background
- meter background and clip lamp, pan knob body/ring/pointer, Mix insert
  and send slots, Mix section captions

In Light, clip bodies are a paler tint and waveforms a deep shade of the
clip colour instead of black.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Light theme: instrument slot and plugin window text use the current tokens

The instrument picker (#23) landed with Tokens::DARK literals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: t <t@t>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:37:04 -04:00
Brandon Thomas 5f2ce943a6 Merge pull request #10 from reoniazx/fix/macos-native-menu-bar
macOS: move application menus to the system menu bar
2026-10-09 18:27:25 -04:00
Brandon ThomasandClaude Opus 5.5 acf3237bed Merge origin/main into fix/macos-native-menu-bar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:15:50 -04:00
ebbicottandClaude Opus 5.5 2a8c60922f Plugin menus: hosted plugins by vendor in scrollable lists; Audio Units insert
The Mix window's insert menu listed each format's plugins as one flat column.
With many installed (53 VST3 and 95 Audio Units effects on a composer's Mac)
it ran past the bottom of the screen and egui menus don't scroll, so the lower
entries couldn't be reached. CLAP, VST3 and Audio Units now open one submenu
per vendor, as Pro Tools does when plug-in menus are organized by manufacturer,
and every list scrolls within the screen.

Choosing an Audio Unit did nothing: the engine's plugin lookup, which
mix.insert uses, covered built-in, CLAP and VST3 plugins only, so "au:" ids were
"unknown plugin" and the UI dropped the error. The lookup now includes Audio
Units (the engine depends on soundcraft-au-host, like the mixer and the UI), and
the menu shows a failed insert in the status bar.

Closes #21

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:39:23 -04:00
Brandon ThomasandClaude Opus 5.5 c51e5d5ec5 Bump version to 0.3.0 (from 0.2.1) for release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:02:25 -04:00
Brandon ThomasandClaude Opus 5.5 ef47ab59d0 Fix the Windows build of vst3-host; bump version to 0.2.1
The vst3 bindings' enum constants use the C default enum type, which is i32 on
Windows and u32 elsewhere. The bus helpers took u32 and the process-context
flags were assigned to a u32 field directly, so the release build failed on all
three Windows targets. Type the helpers with the bindings' own aliases and cast
the flags once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:39:10 -04:00
Brandon ThomasandClaude Opus 5.5 b9c70400f9 Bump version to 0.2.0 (from 0.1.0) for release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:13:25 -04:00
reoniazx b44aaade42 macOS: move application menus to the system menu bar 2026-10-08 14:10:03 +08:00
Brandon ThomasandClaude Opus 5.5 4627084956 About: contributor and model credits compiled in (grab bag + table, self-submitted names) (#8)
* About: contributors compiled in from contributors.json (grab bag + table, sortable, name toggle)

Adds the craftrules standard (standards/contributors.md) to SoundCraft: the About window gets
About · Contributors · Models tabs. contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as
static tables. Each contributor line: @username, opt-in display/real name, PRs, commits,
lines +/−/Δ, binary assets +/−, first/last commit. Sortable by every column; alphabetical
ignores case and the leading @. Sort arrows use ⏶/⏷ (egui's icon font) since the system UI
font has no ▲/▼.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section

build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: regenerate (verified people.toml entries)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:16:58 -04: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 cc86842abe Surround mixing, VST3 hosting, and hardening against hostile inputs
Surround:
- main and bus formats up to 9.1.6 and Ambisonics; pairwise constant-power
  surround panner (divergence, centre, LFE, height) per track and send;
  ITU fold-down; multichannel bounce with correct WAV channel masks;
  multichannel device output; surround panner and multichannel meters in the
  Mix window; setup.main_format, mix.surround_pan, mix.send_surround_pan

VST3:
- soundcraft-vst3-host: isolated unsafe crate (vst3 bindings, MIT), scans
  standard folders, hosts components/processors/controllers, params,
  latency, notes; VST3 menu; engine.vst3_plugins; e2e tests with a fixture

Hardening:
- positions from parameters clamped; offline renders capped and bounded to
  track material; loop/transport limits; hostile-parameter property test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:39:59 -04:00
Brandon ThomasandClaude Opus 5.5 2fc8e2ecb8 CLAP plugin hosting (isolated unsafe crate), stem bouncing
- soundcraft-clap-host: scans standard CLAP folders, loads plugins via
  clap-sys/libloading, implements the Plugin trait (audio, params, latency,
  notes); the only crate allowed unsafe, every block documented; stubs on wasm
- mixer, engine (mix.insert, engine.clap_plugins) and Mix window CLAP menu
- end-to-end tests against a Rust-built fixture plugin
- file.bounce_stems: one soloed file per track

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:44:22 -04:00
Brandon ThomasandClaude Opus 5.5 04d222069d Release pipeline: signed macOS/Windows, Linux packages, FreeBSD, Web; app icon
- .github/workflows/release.yml: every push to `release` builds a draft
  GitHub Release: macOS universal DMG (Developer ID + notarization),
  Windows x64/x86/arm64 MSI + zips (Azure Trusted Signing), Linux
  x86_64/aarch64 AppImage/deb/rpm/tar.gz + Flatpak manifest, FreeBSD,
  Web/WASM zip; SHA256SUMS; secrets only via the release environment
- packaging/ for every platform; .scraft + audio file associations
- cargo xtask version [set X.Y.Z] and xtask ico
- apps/soundcraft-web (trunk, WebGPU/WebGL2, WebAudio)
- placeholder app icon (original nightingale), window icon, Windows resources
- ROADMAP.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:17:59 -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 f41c1310fc UI, desktop app, CLI and MCP: Edit and Mix windows, playback, agent control
- soundcraft-ui-egui: Edit window (toolbar with edit modes, zoom, tools,
  counters, grid/nudge, transport; rulers; track headers; waveforms, MIDI
  and automation lanes; Smart/Selector/Grabber/Trim/Pencil/Zoom/Scrubber
  gestures), Mix window strips, menus built from the incumbent catalog,
  dialogs, floating windows, JSON control channel, offscreen ui_shot
- soundcraft app: eframe/wgpu, cpal playback, control server, drag-drop
- soundcraft-automation: MCP server (stdio) with headless and remote
  backends; agent acceptance test of 9 realistic tasks
- soundcraft-cli: info, convert, run, script, commands, app, mcp, parity
- engine: AudioSuite offline processing command

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:59:40 -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