Commit Graph
55 Commits
Author SHA1 Message Date
Brandon Thomas 412af03dbf Merge remote-tracking branch 'origin/main' into m/719
# Conflicts:
#	ROADMAP.md
2026-10-19 03:04:41 -04:00
Brandon Thomas 8e196cfe2e Merge pull request #532 from nanox333/fix/440-stream-start-failure
fix(playback): retry the next output config when a stream won't start (#440)
2026-10-19 03:03:36 -04:00
reoniazx 516ef23703 fix(playback): fold both stereo channels into mono output 2026-10-18 00:42:19 +08:00
Marco Arnulfo 783ce8f44b VST3: show the plugin's own text for identity-mapped parameters (fixes #599) 2026-10-16 15:20:11 +02:00
Claude 79adfc0b57 playback: test the stream fallback order through first_started
Build and start each attempt in one helper, so the fallback order can be
tested without an audio device. The first config that builds and starts
wins; a build error or a refused start moves on to the next; none gives
None, which leads to the silent clock.

Claude-Session: https://claude.ai/code/session_014JH3FxAq7VHhaKjG9U3vfA
2026-10-13 18:30:29 +00:00
Claude 90318fd6d6 playback: retry the next stream config when a built stream won't start 2026-10-13 18:28:26 +00:00
Brandon ThomasandClaude Opus 5.5 e65a66c539 Keep one build_input after merging #319's latest (the guarded one with device-loss detection)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:27:17 -04:00
Brandon Thomas 94390f49fa Merge #319's latest into the landing branch 2026-10-13 06:25:57 -04:00
Brandon Thomas c2f052715d Merge main into #319 2026-10-13 06:16:54 -04:00
Limestudio90andClaude Opus 5.5 d8b2961651 Close plugin editors before reopening the audio device
Applying new audio device settings replaced the player while plugin
editor windows were open: the old instances went away with it (possibly
on the audio thread, where windows cannot be closed) and their editor
windows stayed on screen orphaned. Player::close_editors closes them on
the main thread first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:46:55 +02:00
Limestudio90andClaude Opus 5.5 0a2492b610 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-13 11:46:55 +02:00
Brandon ThomasandClaude Opus 5.5 ca1a6d443e Fix build after merging main into #306
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 05:31:48 -04:00
Brandon Thomas 056bc288c4 Merge main into #306 2026-10-13 05:31:14 -04:00
Brandon Thomas a47135b228 Merge main into #369 2026-10-13 05:08:26 -04:00
Brandon Thomas 46399aa729 Merge main into #368 2026-10-13 04:50:04 -04:00
Brandon Thomas 0ef67b795b Merge pull request #210 from grovebit/realtime-safety
Stop allocating and freeing memory on the audio thread
2026-10-13 04:43:00 -04:00
Brandon Thomas 75e019dde3 Merge pull request #301 from marcoarnulfo/fix/seek-flushes-state
Locate clears plugin and delay-compensation state
2026-10-13 04:29:11 -04:00
grovebit 7f9b629964 Draw each take in the Edit window while it records
Pro Tools draws a take's waveform as it records; ours showed nothing
until Stop. Each record-enabled track now shows its take growing as a
red clip with its waveform, drawn from the take's live summary through
the same column grid and channel bands as clips (Peak view, as Pro Tools
always shows while recording).

- The take writer keeps, per take channel, the (min, max) of every 128
  frames it writes (at most 2^22 blocks, three hours at 48 kHz; the file
  records on regardless). Recorder::live_take hands the Edit window what
  a track's take holds so far, while the writer runs. The blocks sit
  behind a mutex the writer thread and the UI lock briefly; the input
  callback is untouched.
- io::live_span places the take as Stop will: from the record start,
  only the pass under way when loop recording, and within the punch
  range.

Tests: the take writer's summary (record.rs), live_span with loop and
punch (io.rs) and draw_live (tests/waveform.rs).
2026-10-12 23:43:58 +01:00
grovebit e44c49cada Record NaN and infinite input samples as silence
A device or driver that sends NaN or infinite samples got them written
into the take, where they poison whatever the clip is mixed with, and
into the input monitor. Both paths now turn them into silence before
they are queued, so a take keeps its length and timing.
2026-10-12 23:33:48 +01:00
grovebit 0b9ead430f Stop the transport when the audio output device goes away
Losing the output device only reached the log: the transport kept
"playing" a frozen playhead, and a recording ran on with nobody told.

The output now reports trouble once: the device was disconnected, or it
stopped asking for audio for two seconds while playing (not checked in
the web build, which has no clock for it), or the engine hit a bug as
before. The app stops the transport, keeps a take in progress, and says
what happened and how to reconnect.
2026-10-12 23:33:48 +01:00
grovebit ddb9c9543d Stop recording and keep the take when the input or the disk fails
A failing input only reached the log. When the interface was unplugged
mid-take (on macOS its callbacks simply stop), when writing a take
failed (a full disk), or when the input callback panicked, recording
went on with the transport running and the take silently ended early.
A panic in the callback could also take the input stream down for good.

The recorder now reports trouble: the device was disconnected, it sent
nothing for two seconds while recording, its callback hit a bug (caught,
after which it does nothing more), or writing a take failed. The app
stops recording at once, keeps the take up to that point and says what
happened and what to do. Input lost to a slow disk is reported while
recording instead of only at Stop. Reconnect audio device now reopens
the input as well as the output, so monitoring works again afterwards.
2026-10-12 23:33:48 +01:00
grovebit 98d543f0d8 Go silent instead of crashing when the audio engine hits a bug
Nothing caught a panic in the audio output callback. On macOS it
unwinds into CoreAudio's thread, which most likely aborts the app; on
Windows and Linux the stream thread dies while the transport keeps
showing playback with no sound. A panic in the silent clock that runs
without an audio device stopped the clock for good.

Each block is now filled under a guard. A panic makes that block and
every later one silent and marks the engine failed. The app then stops
the transport, keeping what was recorded, and says to reconnect the
audio device or restart.
2026-10-12 23:32:47 +01:00
Limestudio90andClaude Opus 5.5 acaa91008c mix: a newly adopted instance replaces a matching one in its slot
When a third-party plugin was removed and re-added (same id, same channel
count) and both session updates reached the audio thread in one drain, sync
saw an unchanged slot, kept the old instance and retired the new one as an
unclaimed leftover. The host-side editor belonged to the new, retired
instance, so reopening it failed with "the plugin is gone", and the old
instance kept running with no editor.

The host only hands over an instance when it no longer tracks the one in
the slot, so an instance waiting for a slot now always replaces it (inserts
and instruments). Debug logs record instance creation and release on the
UI thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:27:12 +02:00
Limestudio90andClaude Opus 5.5 9e84e0e351 Close plugin editors before reopening the audio device
Applying new audio device settings replaced the player while plugin
editor windows were open: the old instances went away with it (possibly
on the audio thread, where windows cannot be closed) and their editor
windows stayed on screen orphaned. Player::close_editors closes them on
the main thread first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:57:05 +02: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
grovebit 2d42cf278c Free replaced session snapshots on the UI thread
Every document change sends the audio callback a new Arc<Session>.
Engine::session_mut clones the document whenever the callback still holds
the previous snapshot, so after a non-undoable edit (selection, scrolling)
and on every step of a merged gesture (a fader drag) the callback held the
only reference to the snapshot it replaced. Replacing it freed the whole
tree (tracks, clips, automation, plugin state strings) inside the audio
callback: 268 frees for the demo session, and more for real ones.

The callback now hands the replaced snapshot back over a bounded channel
(preallocated, so try_send never allocates), and the UI thread drops it in
Player::idle, as it does retired plugin instances. update_session empties
the channel before each send, so it holds one or two at a time; if it is
ever full, the snapshot is dropped in place, as before.
2026-10-12 16:02:29 +01:00
grovebit 56ffce6dec Take a plugin that puts out NaN out of the mix instead of silencing it
One insert or instrument putting out NaN or infinity silenced the whole
mix: the value spread through the busses and the main mix (NaN plus
anything is NaN), and the output guard turned all of it into silence.
It can also poison the state of filters, reverbs and compressors
downstream, which then stay silent after the plugin has calmed down.

After each insert (track or master) and each instrument processes, the
engine checks its block. If the block holds NaN or infinity, the engine
silences that block of the strip and skips the plugin until the next
MixEngine::reset (playback starts or stops), which resets it and tries
it again. Meanwhile the strip plays on without it, as if bypassed. The
check costs about 60 ns per insert for a stereo 512-sample block.

StripMeter::plugin_failures counts how often a strip lost a plugin and
names the slot of the latest, so the UI names that plugin in the status
line each time, also when it fails again after a restart. The AU host
no longer zeroes non-finite output itself, so Audio Units are taken out
and named like CLAP and VST3 plugins.
2026-10-12 15:50:58 +01:00
grovebit 4c21ff8fe4 Keep a plugin's saved settings when it refuses to load them
When a third-party plugin refused its stored state (a newer plugin
version that can't read it, a damaged blob), it ran with its defaults,
and the next save or two-minute autosave read its state back into the
session: the saved settings were gone, even after updating the plugin.

create_instance now says whether the plugin refused the state
(NewInstance::refused_state). Playback leaves such an instance out of
the states it reads for saving, so the session keeps the stored state,
until the plugin's own editor is opened: from then on its state is the
user's work. Parameter values are saved apart from the state, so edits
made with SoundCraft's own controls survive either way.
2026-10-12 15:50:58 +01:00
grovebit 793432bbfc Read plugin state on the UI thread, not the audio thread
Saving, bouncing and the two-minute autosave read every third-party
plugin's state through the audio callback: a command made the callback
call each plugin's save_state between two blocks and reply, while the
UI waited for up to 2 s. So:
- CLAP's clap.state and VST3's getState ran on the audio thread,
  although both formats want them on the main thread.
- The callback allocated and waited on the plugins; a plugin with a
  large state (a sampler) could make the audio drop out on every save
  and autosave.
- Without a running callback (a stalled or lost device), every save
  waited 2 s and then kept stale plugin states.

Each instance now hands Side::prepare a state reader before it leaves
for the audio thread, the way it hands over its editor handle. CLAP's
GuiLink and VST3's EditorLink (which now holds the component as well)
read the state under their lock; Audio Units get a StateLink that does
the same. Each instance cuts its link off before it is destroyed, so a
reader never calls into a dead plugin. Player::capture_states reads
them on the calling (UI) thread, and the CaptureStates command is gone.

Each host's tests read the state while another thread processes the
instance, and get nothing once it is dropped. Playback reads states
with no audio thread at all.
2026-10-12 15:50:58 +01:00
Marco Arnulfo 4235def686 Locate clears plugin and delay-compensation state (fixes #247) 2026-10-12 16:40:16 +02: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 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 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 fc095e7fe5 Merge origin/main into meter-fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:05:57 -04:00
Brandon Thomas 5243f9a2a8 Merge pull request #208 from Jafz2001/perf/reuse-mix-buffers
perf: reuse realtime mix buffers
2026-10-11 20:05:01 -04:00
Brandon Thomas ce8eda7575 Merge pull request #207 from Jafz2001/perf/adaptive-meters
perf: adapt meter polling and repaint rate
2026-10-11 20:04:56 -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
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
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
Vikram Bhaskaran 35a6fbccc0 Restore full playback speed when transport stops.
Half-Speed Playback stored 0.5 on the player, and a manual Stop left that rate in place, so the next ordinary Play stayed at half speed. Stopping now resets the player to 1.0. Half-speed itself still plays at 0.5, and a pass that ends on its own still returns to full speed.
2026-10-10 17:38:54 +11:00
grovebit cfe6b42d00 Record takes straight to disk
The input callback appended every block to one growing Vec: it
reallocated on the audio thread as the take grew, kept every input
channel in RAM, and the take existed only in memory until Stop, so a
crash during a take lost it.

- The input callback copies each block into a preallocated lock-free
  ring (atomic slots, no unsafe) and never locks or allocates. Samples
  that don't fit are paid back as silence before the next block, so the
  take keeps its timing; the loss is reported in the status line.
- A writer thread drains the ring every 10 ms into one 32-bit float WAV
  per armed track, holding only that track's input channels. Files go
  to the session's Audio Files folder, or to its autosave folder while
  the session has never been saved, named like the incumbent's takes
  (<track>_<NN>.wav). An existing file is never overwritten.
- The header is rewritten every second, so a file left by a crash plays
  up to the last update. Above 4 GiB it switches to RF64 in place.
- If a take file can't be created, Record doesn't start and says why;
  a write error ends that take where it failed.
- At Stop the files become the takes' sources. Loop recording keeps one
  file per track, one clip per pass (at most 1000) on its own playlist,
  in one undo step.
- Saving uses a file already in the target's Audio Files folder in
  place instead of rewriting it, so neither Save nor autosave copies a
  take. A take recorded at another rate than the session's is saved once
  at the session's rate.

soundcraft-audio-io gains wav_float_header (its length doesn't depend
on the frame count, so it can be rewritten in place); encode shares the
header code. Splitting a loop take into passes moved from the UI into
soundcraft_engine::io::add_recording, where it is tested.
2026-10-10 02:22:18 +01:00
Brandon ThomasandClaude Opus 5.5 5824042856 Merge origin/main into desktop-log-file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:12:03 -04:00
Brandon Thomas e9820fac30 Merge pull request #28 from Garygunn94/claude/recording-audio-playback-b2db9a
Fix input monitoring pitch and dropouts when input and session rates differ
2026-10-09 18:11:35 -04:00
Gary GunnandClaude Haiku 5.5 b3669c767e Fix input monitoring pitch and dropouts when input and session rates differ
The monitor ring fed raw input-device samples to the output callback as if
they were at the session rate. A 16 kHz or 44.1 kHz input played back too
fast (higher pitch) and ran dry between bursts, so it sounded quiet.

InputRing now knows the input rate and converts to the session rate with
linear interpolation in pop_into, allocation-free and non-blocking. The
buffer cap is computed once in new(). Tests cover rate up-conversion,
down-conversion, equal rates and underrun.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-08 22:18:32 +01:00
Gary GunnandClaude Haiku 5.5 368c3df276 Make the Click option audible: metronome in live playback
Options > Click set a flag but nothing rendered sound, so a click track
was silent. The mix engine now synthesizes a short blip on every beat of
the tempo map (accent on beat 1), using the click.* settings from
Setup > Click/Countoff, and sums it after the master fader.

The metronome is opt-in per engine: only live playback enables it.
Offline renders (bounces, stems, exports) leave it off so the click is
never printed into a file.

Also: midi_to_hz moves from the engine demo into soundcraft-dsp so the
mix and demo share one tuning formula.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-08 21:26:40 +01:00
Gert Pellin 74926df527 Desktop: write log records to standard error and a rotating log file
The desktop app installed no logger, so every log::warn!/log::error! in
the workspace was discarded: the engine's panic report, audio devices
that failed to open or broke, plugins that refused their stored state or
failed to activate, CLAP plugins' own log messages, files that failed to
open. A launch from a desktop menu has no terminal either, so there was
nothing to attach to a bug report.

apps/soundcraft/src/logging.rs (no new dependency):
- standard error plus <settings dir>/logs/soundcraft.log (beside
  ui.json, Autosave/ and Presets/); each launch rotates the previous log
  to soundcraft.1.log and .2.log, so the log of a run that crashed
  survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
  (bounded) and written once the file is attached
- default levels: info for soundcraft*, warn for everything else;
  RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
  error as the only sink; a poisoned lock is tolerated
- runs with SOUNDCRAFT_NO_PREFS (agents' test runs) log to standard
  error only, so they don't rotate away the user's own logs

Realtime audio: a record takes a lock and does I/O, which the audio
callback must never do, yet code on the audio thread logs (the cpal
error callbacks, the synth's full event queue, a hosted plugin's failed
process, a CLAP plugin calling the host's log, the panic hook). The cpal
callbacks now mark their thread (soundcraft_mix::mark_audio_thread,
re-exported by soundcraft_playback), and the mix engine carries the mark
into the rayon workers that render strips for it. A record from a marked
thread is not written there: the first is kept in a fixed buffer
(try_lock, no allocation, never waits), the rest are counted, and the UI
thread writes them every frame (any other thread before its own next
record).

The logger is installed first in main (before the engine's panic hook)
and the file is opened after argument parsing (--version leaves no
file). The settings directory moves out of prefs_path into config_dir.
The control server's eprintln!s go through the logger (the
SOUNDCRAFT_CONTROL_PORT line on standard output stays). Documented in
README.md (Getting started > Logs) and docs/architecture.md (Audio).
2026-10-08 12:25:49 +02: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