Picking a plugin for an insert slot added it but opened nothing, so
every new plugin took an extra click on its slot. As in other DAWs, the
pick now opens it: a hosted plugin's own editor, else its plugin
window (built-in plugins, no audio engine, or an editor that can't open
here, such as CLAP plugins with only an embedded editor on macOS).
The insert menu's picks go through pick_insert, which remembers the
slot when mix.insert succeeds; SoundApp::open_inserted opens it on the
next logic pass, once the audio engine has the instance. Only the menu
opens it, so mix.insert from the CLI, MCP or control channel still
opens nothing. It covers empty and filled slots, in the Mix window and
the Edit window's Inserts column.
Tests: tests/open_on_insert.rs (empty slot, search and Enter,
replacing, Edit window column). menu_search closes the windows the
picks now open before it looks for the menu's Bypass.
Reject oversized batches before any side effects instead of reporting success after truncating calls.
Closes#460
Co-Authored-By: OpenAI Codex <noreply@openai.com>
With the marker present, ui.json, Autosave, Presets and logs go to
<exe dir>/SoundCraftData instead of the user profile. SOUNDCRAFT_CONFIG_DIR
still wins, and a data folder that can't be written falls back to the
profile with a warning in the log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Press a checkbox and drag over its neighbours: each box the pointer
passes takes the state the first one switched to, until the button is
released. A click toggles one box, as before. Same design as VectorCraft
and PhotoCraft.
widgets::checkbox wraps egui's checkbox and adds the drag; every
ui.checkbox call in ui-egui uses it. Only boxes the pointer can see are
set, so rows scrolled out of a list or under another window stay as
they are.
Tests: widgets::tests (drag sets, drag unchecks and stops at release,
hidden rows untouched, a click toggles one) and tests/group_dialog.rs
(dragging down the Group dialog's track list groups every track).
A bounce rendered the whole mix (or bus) into memory and then encoded a
second copy, and the render silently stopped at 2^29 samples: about 93
minutes of stereo at 48 kHz, 31 of 5.1, 15 of 7.1.4. Longer bounces came
out cut short while file.bounce_mix still reported the requested length.
- audio-io: FileEncoder writes WAV/BWF (RF64 above 4 GiB), AIFF and FLAC
block by block into any Write + Seek, byte for byte what encode writes
for the whole buffer. check_room fails up front when an AIFF (4 GiB) or
a FLAC (2^36 frames) can't hold the bounce. AIFF and FLAC encode run on
the same writers; their output is unchanged.
- mix: RangeRender renders the main mix or one bus over a range of any
length, a block at a time; render_range and render_bus (in memory,
still capped) are built on it.
- engine: io::Bounce streams the main mix or a bus, or its stereo
fold-down, into "<name>.part" next to the target, syncs it and renames
it into place, so a failed bounce leaves no partial file and keeps the
old one. In the browser it encodes into memory (failing instead of
aborting when memory runs out) and delivers the file as before.
Normalize renders twice (peak, then the scaled write); levels are
measured as blocks are written. Stems stream the same way and share the
4-hour limit.
A 100-minute bounce of the demo session now writes all 288,000,000
frames with 83 MB peak memory.
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>
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>
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>
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.
- 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.
- 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>
* Progress docs to the craftrules standard; full re-measure against Pro Tools 2026.4.1
Stage: alpha. Menu breadth 482/512 (94.1%, measured), feature breadth ~70%,
ready for real work ~40%; ~350-570 agent-hours to beta, ~950-1,450 to full parity,
calibrated on this repo's PR history.
- ROADMAP.md rewritten: stage banner, two numbers, dimension/feature/language tables
- docs/target-app-parity.md: authoritative assessment (area table moved from ROADMAP)
- docs/gaps.md: 22 ranked gaps with evidence and estimates
- docs/roadmap.md: milestones, current focus, beta plan
- docs/ui-, file-format-, hardware-, plugin-, localization-parity.md: new checklists
- docs/parity.md -> docs/parity-checklist.md (generated by cargo xtask parity)
- architecture.md: video, vst3-host, au-host crates; device I/O limits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stage: pre-alpha under the core-workflow gate
Add the alpha gate to docs/roadmap.md: interface recording at a chosen buffer
size, MIDI recording, and session interchange with Pro Tools fail, so the stage
moves from alpha to pre-alpha (~5 points and ~110-180 agent-hours to alpha).
Mark the alpha blockers in docs/gaps.md; update ROADMAP.md, target-app-parity.md
and README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add mainstream-practitioner and essentials-user readiness numbers
Mainstream practitioner ~36% (weekly-area depth 50.6% x 0.90 interaction x 0.92
stability x 0.85 file exchange) and essentials user ~57% (core-feature depth
72.4% x 0.90 launch/stability x 0.90 discoverability x 0.97 files), with written
weights, discounts and GitHub user evidence. Full ready-for-real-work checked
against its written dimension weights (40.6%); unchanged at ~40%, stage unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Readiness by audience: hours to ~95% for full, mainstream and essentials
Full ~40% / 950-1,450 h, mainstream ~36% / 380-600 h, essentials ~57% / 110-200 h,
calibrated on this repo's PR history. The full number is already an additive
weighted sum over the written dimension table; method unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Agents that only check isError were treating partial failures as success. Also reject a non-array calls value instead of silently running nothing. Fixes#145.
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.