87 Commits
Author SHA1 Message Date
aiongg 42f22369fb Integration: build the batch's new integration tests into each crate's tests/it binary 2026-10-24 06:54:11 +00:00
aiongg b47f544c7a Merge track r1-t02
# Conflicts:
#	contributors/people.toml
#	docs/plugin-parity.md
2026-10-24 06:54:04 +00:00
aiongg bc530b172a Merge PR #763: Mix tests: never load real plug-ins (cargo test hangs on machines with plug-ins installed) 2026-10-24 06:53:56 +00:00
aiongg 76bd7bc3af Merge PR #561: Keep deep Aux and Folder routing chains in dependency order
# Conflicts:
#	contributors/people.toml
2026-10-24 06:53:56 +00:00
aiongg d2d6518a5f Merge PR #557: mix: clamp negative fractional source position in stretched-clip rendering 2026-10-24 06:19:09 +00:00
aiongg 5bd755f604 Merge PR #617: Apply strip mute automation within each processing block
contributors/people.toml: kept both new entries.
2026-10-24 06:17:15 +00:00
Brandon Thomas 6a4bcadf1e Merge pull request #986 from Silvertree2010/fix/889-audit-a-latency-change-reallocat
Keep delay-line storage when the compensated latency changes
2026-10-24 01:01:24 -04:00
Dylan M. TaylorandDylan Taylor fe4bb3fe0c style: replace flagged glob imports with explicit uses (SonarCloud rust:S2208); move release workflow's write permission to the job (S8233) (#988)
* security: move the release workflow's write permission to the job level (SonarCloud githubactions:S8233)

* style: replace wildcard imports with explicit uses (SonarCloud rust:S2208)

Rewrites every glob `use` Sonar flags into explicit imports, so what each
command module consumes is visible at the top of the file.

* release: the git-sources gate moves into craft-common-actions (PR #78)

The check is shared at release.yml@main now; keep only the reusable call.

---------

Co-authored-by: Dylan Taylor <dylanmtaylor@users.noreply.github.com>
2026-10-23 14:53:00 -04:00
aiongg e8abd44712 CI: one test binary per crate, nextest, line tables only (#985)
* Build: line tables only for workspace crates, no debug info for dependencies

Full debug info made most of the dev and test target size and link time. Line tables keep file and line numbers in backtraces; dependency frames lose them.

* Tests: build each crate's integration tests as one binary

Each tests/*.rs file was its own test binary: 98 binaries, each linking the crate and its dependencies. The files are now modules of tests/it/main.rs, one binary per crate, which cuts link time and target size. Filter by module: `cargo test -p <crate> --test it <module>::`.

Binaries with their own main (harness = false) in clap-host and vst3-host stay separate, as do the ui-egui tests that switch the process-wide Light/Dark palette and recovery.rs, whose autosave locks leak into child processes other tests start.

* Xtask: run tests with nextest, doctests apart, and no examples in the test build

cargo xtask ci runs the tests with cargo nextest when it is installed, else cargo test with a notice, and doctests with cargo test --doc. The test build leaves out examples (--lib --bins --tests); clippy --all-targets still compiles them. Test binaries with harness = false run under cargo test: their own main cannot answer nextest's --list. clippy and the wasm check keep going past a failing crate. cargo xtask test runs the test step alone.
2026-10-23 14:00:38 -04:00
NetterSilverandClaude Opus 5.5 cc493b2d8b Keep delay-line storage when the compensated latency changes
Fixes #889

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:56:02 +00:00
Brandon Thomas 6d268d5623 Merge remote-tracking branch 'origin/main' into m/672
# Conflicts:
#	ROADMAP.md
#	docs/gaps.md
2026-10-19 03:09:47 -04:00
Brandon Thomas a8fa1e53d3 Merge remote-tracking branch 'origin/main' into m/672
# Conflicts:
#	ROADMAP.md
2026-10-19 03:04:40 -04:00
Brandon Thomas 798ee7d88f Merge remote-tracking branch 'origin/main' into m/667
# Conflicts:
#	ROADMAP.md
2026-10-19 03:04:40 -04:00
Derek Wright 4110f5ff10 Mix tests: never load real plug-ins
stored_state_comes_first_then_session_params ends with an offline render_range over a vst3: insert. That reached create_plugin and the real VST3 host, which scans the machine's plug-in folders and loads every installed plug-in in-process; some (Native Instruments) hang on unload, so the test binary never exited on a developer machine. CI has no plug-ins installed, so it stayed green.

Under cfg(test), create_plugin now returns None for third-party ids, so a test reaching the real hosts sees an absent plug-in instead of hanging. The rule, stated in the tests module doc: tests adopt fakes under clap:test.* ids and never call the real hosts.
2026-10-18 13:07:27 -04:00
reoniazx ef65ceafb8 fix(mix): exclude failed inserts from aux and master latency 2026-10-16 18:21:31 +08:00
reoniazx da404551b1 fix(mix): keep master automation within render blocks 2026-10-16 14:59:16 +08:00
Billy Coleman III 075104f448 Apply strip mute automation within each processing block
Gate ordinary strip output and pre-fader copies at each represented sample
using the existing mute lane, mode, held values and threshold. Preserve the
fader and pan schedules, static suppression and hosted processing blocks.
2026-10-15 04:35:33 -04:00
Billy Coleman III 1e1458caf1 Compile exact dependency order for deep Aux routing
Prepare longest-path depths for acyclic Aux and Folder input routes so
reverse-created chains beyond eight returns preserve their audio. Count
output and send destinations once per producer, retain depth ties in
session order, and preserve the whole-graph bounded fallback for cycles.

Add public PCM regressions for deep chains, routing edits, callback sizes
and cycle compatibility, plus focused private ordering cases.
2026-10-14 13:14:38 -04:00
midasmatterhorn 76696826cd mix: clamp negative fractional source position in stretched-clip rendering 2026-10-14 18:01:42 +03:00
MarekSpodBiedry 52dc4f9822 Reset clip effect EQ/compressor state on stop and seek (#442) 2026-10-13 17:53:21 +02:00
Brandon ThomasandClaude Opus 5.5 14862a5139 Merge main into #408
render_bus moved into RangeRender (#393); the bus-latency fix now applies there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:45:20 -04:00
Brandon Thomas 6d4f9a41a5 Merge main into #409
# Conflicts:
#	ROADMAP.md
#	docs/architecture.md
2026-10-13 06:44:55 -04:00
Brandon Thomas 58a0482712 Merge pull request #335 from reoniazx/fix/fade-drag-waveform-preview
Fix clip fade editing and add live waveform previews
2026-10-13 06:31:12 -04:00
Brandon Thomas 123ae65e2b Merge pull request #306 from grovebit/plugin-safety
Plugin safety: read plugin state off the audio thread, keep refused settings, contain NaN plugins
2026-10-13 06:30:53 -04:00
Brandon Thomas a8594684a1 Merge pull request #393 from grovebit/bounce-streaming
Stream bounces to disk and keep the app responsive while bouncing
2026-10-13 06:30:29 -04:00
Brandon ThomasandClaude Opus 5.5 baea184bd1 Keep one VST3 controller-sync path after merging #306's latest
The landing branch already syncs host-set values to the controller before
saving state (lock-free atomics + EditorLink::sync_param); the PR's later
ControllerPending table did the same, so keep one. Keeps the PR's new
'a loaded state replaces earlier edits' assertion, which passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:26:54 -04:00
Brandon Thomas 0c30ecffcc Merge #367's latest into the landing branch 2026-10-13 06:25:56 -04:00
Brandon Thomas 7a6df4cbbd Merge #306's latest into the landing branch 2026-10-13 06:25:56 -04:00
grovebit 2895fa7dd0 Merge origin/main into plugin-transport-midi
#304's MIDI Track Offset shifts the block schedule_notes reads, which
now carries this branch's controllers too. chase_controllers takes the
same offset, so a seek chases the controller values in effect where the
offset puts the track, not at the raw transport position. New test: with
the track 0.5 s late, starting at 1.25 s chases the sustain pedal still
down (without the offset it sent the pedal-up that only plays at 1.5 s).

Also: reset() sets the chase flag beside #301's alignment flush, the
VST3 Instance keeps #400's set_normalized beside midi_mapping, and the
mix tests keep #210's Keys plugin beside this branch's Clock and Ears.
2026-10-13 11:06:09 +01:00
grovebit 776a7af06c Merge origin/main into plugin-safety
#400 (fixes #257) made Vst3Plugin::save_state tell the edit controller
the values the host set before taking the snapshot. This branch reads
plugin state on the UI thread through EditorLink instead, and removes
Vst3Plugin::save_state, so keeping its side of that conflict would have
dropped the fix.

The values now wait in a ControllerPending table shared by the instance
and its EditorLink: one AtomicU64 slot per parameter, which set_param
fills on the audio thread without locking or allocating.
EditorLink::save_state sends the waiting values with setParamNormalized
under its lock, before it reads the controller's state. A state load
clears them, as #400's refresh did. Instance::set_normalized, which only
the old save_state used, is gone. #400's round-trip test passes, plus a
check that a loaded state replaces earlier host edits.

#327's a_new_instance_replaces_a_matching_one_in_its_slot told the two
instances apart through MixEngine::capture_states, which this branch
removes (states are read on the UI thread). The two instances now
differ in gain instead (-6 and -20 dB), and the test checks that the new
level plays and that the old instance is retired.

Also: Side::new sets #210's old_sessions field.
2026-10-13 10:58:07 +01:00
Brandon Thomas e0deb3f4e5 Merge main into #335 2026-10-13 05:55:06 -04: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 5051b4a45f Merge main into #393 2026-10-13 05:12:31 -04:00
Brandon Thomas e32c16b82e Merge main into #367 2026-10-13 05:11:18 -04:00
NetterSilver 446a059faf Mix: delay compensation covers the master faders' inserts (#351)
The master faders process the main mix after every track and aux return has been
summed, so a lookahead insert on them (the Maximizer: 71 samples at 48 kHz)
delays the whole main mix. The mix's compensation latency, which a bounce drops
from the head of the render, left it out, so the bounce came out late by the
insert's latency (the voice was 71 samples later than in the same mix without
the Maximizer).

The latency of the master inserts is now part of the mix latency. With Delay
Compensation off nothing changes.
2026-10-13 11:11:12 +02:00
NetterSilver 5cd155bb64 Mix: a bus bounce compensates the aux returns onto it (#246)
render_bus dropped only the track-insert latency from a bus, but an aux that
returns onto a bus delays it by the slowest aux return as well. With Delay
Compensation on, a bounce of such a bus came out late (a 5 ms lookahead left
240 leading silent frames) and its last impulse fell outside the requested end.

The engine now knows which busses an aux returns onto. A bounce of one drops
the aux latency too, and the tracks routed straight to that bus wait for it, as
the tracks routed to the main mix already do, so the two kinds of signal on the
bus arrive together. Other busses and the main mix are unchanged.
2026-10-13 11:01:34 +02:00
Brandon Thomas 9c7e5018b3 Merge pull request #333 from grovebit/strip-silence-long
Stop Strip Silence deleting clips late in long selections
2026-10-13 04:43:41 -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 6dfa00590b Merge pull request #304 from marcoarnulfo/fix/midi-track-offsets
Apply MIDI track offsets when scheduling notes
2026-10-13 04:29:40 -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
Brandon Thomas e5ca13d07f Merge pull request #327 from Limestudio90/fix-replaced-instance
mix: a newly adopted plugin instance replaces a matching one in its slot
2026-10-13 04:29:07 -04:00
Brandon ThomasandClaude Opus 5.5 ad8beac9b4 Look up the MIDI track offset without allocating on the audio thread
schedule_notes ran format!() every block for every instrument track; scan
the edit values by prefix instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:21:20 -04:00
grovebit f188b66209 Leave controller events before a clip's start silent
A MIDI clip whose start was trimmed may keep the events before its new
start at negative ticks, hidden until the start is trimmed out again
(midi-clip-trim does this). The controller loop clamped every event's
tick to 0, so a hidden sustain pedal or pitch bend played at the clip
start, and the chase after a seek counted hidden values as the state
in effect.

Both loops now skip events before tick 0. Nothing changes for clips
without hidden events: notes and controllers made by the app never
start before tick 0.

Test: a sustain-pedal event at tick -480 is neither sent when the clip
starts nor chased after a seek into the clip, while a visible
controller is still chased.
2026-10-13 02:42:15 +01:00
grovebit d19d44cd1b Cap bogus plugin latency so offline renders can't stall
CLAP and VST3 plugins report their latency unchecked (up to u32::MAX
samples), and an offline render skips that many samples before its first
block, inside one call. A plugin reporting garbage made a bounce render
about 25 hours of audio before producing anything, with the UI stuck in
that call: no progress, no Cancel. Freeze and Commit stalled the same
way.

- A strip reports at most 2^20 samples of latency (about 22 s at 48 kHz),
  the most delay compensation could align anyway (MAX_LATENCY, shared
  with the delay lines). That holds for the latency an aux's inserts
  report before the aux is processed, too.
- RangeRender::next_block renders one block per call, returning no frames
  while it skips the latency, so a bounce yields to the UI there too.
2026-10-12 23:47:24 +01:00
grovebit 5755795663 Stream bounces to disk instead of rendering them in memory
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.
2026-10-12 23:46:13 +01:00
reoniazx 4ac47b3a7f Add draggable midpoint curvature for clip fades 2026-10-13 02:48:00 +08: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
grovebit dab2bb956c Don't let Strip Silence delete clips past what one render holds
Strip Silence rendered the whole selection at once, and a render stops
at 2^29 samples over all channels: 93 minutes of stereo at 48 kHz, 46
at 96 kHz. Everything past that read as silence, so the clips there
were deleted. A 105-minute selection on a stereo track lost its clip at
100 minutes.

It now renders at most 2^24 samples at a time, only where unmuted audio
clips play, into a streaming version of the silence detector
(`SilenceScan`, which `non_silent_ranges` now runs on). A window takes
at most 64 MB instead of up to 2 GiB, and more than a day of clips is
refused rather than scanned for minutes. `render_clips` now skips clips
outside its range before looking up their clip effects, so a track of
5,000 clips strips as fast as before. Results are unchanged on the demo
sessions and on 10 minutes of tone bursts that cross window edges.
2026-10-12 18:12:00 +01:00
grovebit c9e53ee704 Look up clip effects and Real-Time Properties without allocating
Once a session has any edit.values entry (click settings, Real-Time
Properties, clip effects), MixEngine::render allocated every block,
although the crate docs say steady-state rendering doesn't: every audio
clip formatted two Strings and collected a Vec to look up its clip
effects, and every MIDI clip formatted four keys for its Real-Time
Properties. Strips render on rayon's workers while the audio callback
waits for them, or on the audio thread itself below 4 strips.

Lookups now format their keys on the stack (Key) and range over
edit.values with &str bounds. A new test checks that Real-Time
Properties still reach the instrument.

A counting allocator on all threads, over 1000 blocks of the demo
session with a clip effect on every clip and Real-Time Properties on
every track, saw 10 to 43 allocations per block before and none after,
apart from rayon's job queue (a new chunk every 63 blocks) and the
one-time setup of each clip's effects when it first plays.
2026-10-12 16:02:37 +01:00