Commit Graph
262 Commits
Author SHA1 Message Date
Vikram Bhaskaran 2afbfd9477 Save a session copy without retargeting the open document.
Save Session Copy In wrote through the normal save, which renamed the session, marked it clean, and pointed it at the copy. The open session now stays dirty, named, and path-ed as it was, while the copy is a separate folder.
2026-10-11 08:49:18 +11:00
Vikram Bhaskaran 09d64ab2ec Export MIDI controllers and honour the tracks filter.
file.export_midi copied notes only, so controller, pressure, and pitch-bend events were dropped, and the tracks parameter was ignored. The file now includes those events, and a tracks list limits the export. Naming only non-MIDI tracks is an error instead of exporting every other MIDI track.
2026-10-11 08:49:08 +11:00
Vikram Bhaskaran 404940b40d Import multichannel audio onto a track of the same width.
A new track for an imported file was clamped to stereo, so a 5.1 file lost every channel past the left and right. The track now follows the file's channel count. Importing onto an existing track is unchanged.
2026-10-11 08:48:52 +11:00
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 718bb5c7fb Open Save As when saving a session that has never been saved
Cmd+S and File > Save Session on an untitled session only left "no path
yet: pass `path` (Save As)" in the status line. From a menu, the command
palette or the keyboard, Save now opens the Save Session As prompt until
the session has a file. Calls from the control channel, MCP and the CLI
still need a `path`.

The prompt suggests ~/<name>/<name>.scraft, but saving a session without
audio there failed with "No such file or directory", because only
writing audio created the session folder. Saving now creates the folder.
2026-10-10 14:20:55 +01:00
grovebit 9ed4bdd4b1 Shift+Cmd+K exports clips as files
Export Clips as Files had no shortcut and no menu entry, so Shift+Cmd+K,
its key in Pro Tools, did nothing (#7). It now opens the command's
folder prompt, and OK exports the selected clips there.

Trying it showed three more gaps:
- Browse… in a folder prompt opened an audio-file picker, so no folder
  could be chosen. Folder prompts (Export Clips, Bounce Stems) now use a
  folder picker, a new `pick_folder` host service; the web build has
  none and keeps the text field.
- A successful export said nothing. The status bar now reads "Exported
  N clip(s) to <folder>", and the command returns the folder.
- With nothing selected the export succeeded and wrote no files. It now
  fails with "no clips selected".

The shortcut lookup moves into `command_for` so a test can check what a
key resolves to.
2026-10-10 11:45:22 +01:00
Brandon Thomas e5b5f9907f Merge remote-tracking branch 'origin/main' into HEAD 2026-10-10 04:01:49 -04:00
Brandon ThomasandClaude Opus 5.5 f9d0d26e5b Merge origin/main into fix/94-cut-all-automation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:59:53 -04:00
Brandon Thomas dba0bea03d Merge remote-tracking branch 'origin/main' into HEAD 2026-10-10 03:56:37 -04:00
Vikram Bhaskaran 9ef16e1e6c Put Cut All Automation on the clipboard.
The command cleared every automation lane in the selection and left the clipboard empty, so the cut could not be pasted. It now uses the same copy-and-clear path as Cut Pan Automation.
2026-10-10 17:40:09 +11:00
Vikram Bhaskaran fe4f59a856 Bake varispeed into rendered clip gain, then stop stretching it again.
Clip gain and clip effects renders wrote the clip as it already sounded and left the old ratio in place, so the new file was stretched a second time and its tail went silent.
2026-10-10 17:38:54 +11:00
Vikram Bhaskaran abcec3938b Render a named bus when bouncing, and reject an unknown one.
file.bounce_mix advertised a source but always wrote the main mix, so an empty bus and a missing bus both succeeded with the whole mix.
2026-10-10 17:38:54 +11:00
Vikram Bhaskaran ec33d4011b Keep clip gain when AudioSuite inverts or duplicates.
Both processes rendered a unity-gain file and then cleared the clip's gain and envelope, so the result was quieter than the clip the user heard. Invert and Duplicate now leave that gain on the new clip. Processes that bake a new level still write a unity-gain clip.
2026-10-10 17:38:54 +11:00
Vikram Bhaskaran 1aad99ff4c Convert only the selected volume automation into clip gain.
The command wrote the lane onto the whole clip and cleared points only inside the selection, so the old curve still played and the clip got quieter on both sides. The selection now holds the fader, with anchors so the surrounding curve stays put. Volume and clip-gain windows take their end value from the last sample inside the window, so a breakpoint on the next sample does not ramp the audio before it.
2026-10-10 17:38:54 +11:00
Vikram Bhaskaran d07624da1f Give same-named clip exports their own files.
Export Clips as Files wrote every clip to {name}.wav, so the second clip replaced the first. A name that is already in the destination now gets a -2, -3, … suffix.
2026-10-10 17:38:53 +11:00
Vikram Bhaskaran 82340cee5c Snap selected clips to their neighbours instead of past them.
Snap to Next and Snap to Previous ignored other selected clips when looking for a destination, so a multi-clip selection piled onto the same later clip or left the gap between the selected clips in place.
2026-10-10 17:38:53 +11:00
reoniazx 0734281104 Stop playback when replacing the session 2026-10-10 12:44:01 +08:00
Scott Campbell fa88fbb942 Add a built-in Sampler instrument
A third built-in instrument next to Subtractive Synth and Drum Synth. It plays one
sample across the keyboard: root note, transpose and fine tune, key tracking, key and
velocity ranges, amp ADSR with velocity sensitivity, sample start, forward or ping-pong
loops, gate or one-shot playback, 32 voices with cubic interpolation. The sample's own
rate is converted as it plays.

- dsp: `Plugin::set_sample` and the `SampleSource` trait hand an instrument shared
  audio without copying; the Adsr from the synth is reused.
- model: `Insert::sample` names the session source the sampler plays.
- mix: sync hands the pool audio to the instrument when it changes, and built-in
  instruments now get their parameter values applied (they always ran on defaults).
- engine: `mix.sampler_load {track?, path, params?}` decodes WAV, AIFF, FLAC, MP3, OGG
  (anything file.import_audio reads) into a session source, resampled to the session
  rate and saved in Audio Files with the session; `mix.sampler_clear`; and
  `mix.instrument_param {track?, param, value}` for any built-in instrument.

Tests generate their audio in code; no bundled assets.
2026-10-09 22:55:24 -04: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
grovebit 42d08558ab Clear clips and audio files from the Clip List
Nothing in the Clip List could be removed: neither its files nor its clips
had a delete action, so everything stayed listed.

- New `clip.clear` (Cmd+Shift+B, the Clip List Clear shortcut in Pro Tools),
  also offered as Clear… on every Clip List row's right-click menu. A clip
  leaves its track; a file leaves the session with its decoded audio and is
  never deleted from disk. One undo step brings everything back.
- As in Pro Tools, Clear asks first. The dialog names what goes, including
  the clips on any track or playlist that still play a file being removed:
  they are removed too. Programmatic calls never open it; they pass
  `with_clips: true` to remove a file still in use, and fail without
  changing anything otherwise.
- File rows can be selected now (`edit.select {sources}`, kept in
  `EditState::selected_sources`); with no ids, `clip.clear` acts on the
  Clip List selection.
- Remove Warp and re-warping ignore a stored original whose file has been
  cleared, so they never point a clip at a missing file.
2026-10-10 02:07:51 +01:00
Brandon Thomas 53e1d3c624 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-09 20:49:56 -04:00
Brandon ThomasandClaude Opus 5.5 50f885e16b Merge origin/main into feature/session-audio-health
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:48:05 -04:00
Brandon ThomasandClaude Opus 5.5 e2bfdd5b73 Merge origin/main into feat/inspect-source-loaded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:37:45 -04:00
Brandon ThomasandClaude Opus 5.5 66a682fba1 Merge origin/main into fix/import-audio-new-tracks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:35:14 -04:00
Brandon ThomasandClaude Opus 5.5 45e32b9781 Merge origin/main into issue-47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:32:52 -04:00
Brandon ThomasandClaude Opus 5.5 3a3c447904 Merge origin/main into issue-57
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:30:26 -04:00
Brandon ThomasandClaude Opus 5.5 a867f05177 Merge origin/main into feature/plugin-menus-instruments
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:23:33 -04:00
Brandon ThomasandClaude Opus 5.5 2e2220c64d Merge origin/main into fix/vst3-module-exit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:13:17 -04:00
Brandon Thomas 4bb041ad5c Merge pull request #26 from Garygunn94/feat/metronome-click
Make the Click option audible: metronome in live playback
2026-10-09 18:11:29 -04:00
grovebit 1720fc7ea1 Change clip gain with Ctrl+Shift+Up/Down and a fader on the clip
There was no way to change a clip's gain from the UI (#2): clip gain is
stored, drawn and settable through `clip.gain`, but no menu item, key or
gesture reached it. This adds the two ways Pro Tools offers.

Keyboard: Pro Tools nudges the selected clips' gain with
Control+Shift+Up/Down, 1 dB a step by default. Two new commands, Nudge
Clip Gain Up and Down, do the same through `clip.gain`'s relative
`delta_db`, so its -144..+36 dB range holds and each nudge is one undo
step. Control+Up/Down extended the edit selection whether or not Shift
was held, which swallowed the new keys; it now leaves the Shift variants
to the command registry.

Fader: in Pro Tools, Clip Gain Info puts a fader icon and the gain value
in the bottom-left of each clip, and pressing the icon pops up a small
fader. With View > Clip > Clip Gain Info on, or once a clip has gain,
audio clips now show the icon and the value, and dragging the icon moves
the clip's gain from -144 to +36 dB (Cmd for fine moves), one undo step
per drag. The icon is drawn in code. The fader follows the pointer from
where it was pressed rather than adding up per-frame deltas, which lose
the first few pixels while egui decides between a click and a drag and
leave the fader out of step with the pointer once it hits an end (it
would stick, then jump).

Clip Gain Info no longer depends on Clip Gain Line being shown, since
the two are separate views in Pro Tools, and the value moved from the
clip's bottom-right to beside the icon. Dragging the icon of a clip when
nothing is selected selects that clip first, because `clip.gain` needs a
selection.
2026-10-09 22:45:39 +01:00
Mawen Salignat-Moandal 822b320d1a Report whether each inspect source has decoded audio loaded.
Agents using session.inspect / inspect_session can tell missing media from metadata alone, complementing open_session missing lists and audio_health. Also credit Mawen in people.toml.
2026-10-09 21:24:17 +02:00
Mawen Salignat-Moandal 8a1c82733c Honor file.import_audio new_tracks instead of silently ignoring it.
Explicit track still wins; new_tracks:false uses the selected audio track or errors clearly. Fixes #60.
2026-10-09 21:03:36 +02:00
MarekSpodBiedry f8a156d255 Warn about missing media when opening a session (#57) 2026-10-09 20:28:46 +02:00
Vikram Bhaskaran 42e6b96734 Report session audio health without changing the session.
The window checks every playlist, including inactive takes, for missing audio, rate mismatches, empty buffers, and clips that extend past their source.
2026-10-10 04:37:45 +11:00
MarekSpodBiedry 43f1fb5681 convert and bounce: refuse output extensions the encoder cannot write instead of saving WAV data (#47) 2026-10-09 19:30:25 +02: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
Gary GunnandClaude Haiku 5.5 c8437c5c61 Fix tempo change from the tempo ruler not saving
Double-clicking the tempo ruler opened a number dialog whose command was
built as "event.tempo@<position>". No such command exists, so OK failed
and nothing changed. The position was also never passed, so a change
would have landed at the session start.

Add a TempoChange dialog that carries the position and runs
event.tempo with bpm and at. Add an engine test that a tempo change at a
position is applied there.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-08 21:24:47 +01:00
ebbicottandClaude Opus 5.5 ed47ec0fbe Instrument picker: choose an instrument track's instrument and open it
There was no way in the app to choose a CLAP, VST3 or Audio Units instrument,
or to open an instrument's editor:
- the insert menus leave instruments out;
- an instrument was only set by track.new;
- the Mix window's INSTRUMENT row looked for instrument inserts rather than the
  track's instrument;
- plugin windows opened only for insert slots.

Changes:
- mix.instrument {track?, plugin} sets an instrument track's instrument, with
  the plugin's default parameters, and is undoable. It refuses effects and
  other track kinds.
- The INST label on an instrument track's header opens an instrument menu: the
  current instrument and its editor, then the built-in, CLAP, VST3 and Audio
  Units instruments, by vendor.
- The Mix window's INSTRUMENT row shows the track's instrument. A click opens a
  hosted instrument's plugin window, and a right-click changes it.
- Plugin windows open for the instrument slot, with only the editor button,
  because the bypass, preset and parameter commands address insert slots.
  They no longer derive a slot letter for the instrument slot.

Closes #22

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:40:37 -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
ebbicottandClaude Opus 5.5 ccd1f995bf Shut plugin hosting down at the end of main, not only from an exit handler
Testing on macOS showed that the exit-handler fallback comes too late for a
plugin that has been used: with HALion Sonic inserted on a track, its bundleExit
segfaults, and both the app (on quit) and soundcraft-cli (after a bounce) exited
with 139. A plain scan was fine. Exit handlers run in reverse order of
registration, so state the plugin set up while in use is presumably torn down
before the host's handler runs.

soundcraft_vst3_host::shutdown() runs the module exits up front, and
soundcraft_engine::shutdown_plugin_hosts() calls it. The app calls that once the
event loop has returned (the player and its plugin instances are gone by then),
and the CLI once its subcommand has. The exit handler stays as the fallback for
other exits, such as test binaries.

The fixture can now set up state lazily the way such plugins do
(VST3_FIXTURE_LATE_STATE), and module_exit_shutdown checks both sides in child
processes: with shutdown() the exit is clean, with only the exit handler the
fixture reports the late teardown. Contract violations now end the process with
status 3 instead of an abort, so that expected failure leaves no crash report.

Refs #16

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:28:27 -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 c125a79a47 Mix window views: Mic Preamps (input gain), Instruments, Object/Bed
Adds track.object for routing a track as an immersive object or to the bed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:24:05 -04:00
Brandon ThomasandClaude Opus 5.5 0751dd31f1 Score export and print, clip group files, Merge Markers, path dialogs for every file command
- File › Export › Sibelius and File › Send To › Sibelius write MusicXML 4.0 (quantized,
  chords, ties across bar lines, meter changes, treble or bass clef per part)
- File › Print Score engraves the MIDI tracks to a printable SVG page; File › Score Setup
  sets title, composer, bars per system and track names
- File › Import › Clip Groups and Export Clip Groups (.scgrp with its audio)
- Edit › Paste Special › Merge Markers: copy carries the markers in the copied range
- The UI opens a path prompt for any command that needs a path or folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:17:42 -04:00
Brandon ThomasandClaude Opus 5.5 856aae6871 Keyboard focus command and the a-z Commands Focus toolbar toggle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:42:00 -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 8269ca77f6 Property test: every command survives hostile parameters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:15:26 -04:00
Brandon ThomasandClaude Opus 5.5 222273590e Property test: mutated session files load or fail cleanly and render without panicking
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:13:37 -04:00
Brandon ThomasandClaude Opus 5.5 7f9f9fff08 Random-edit property test covers the extended command set (400-case run clean)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:12:40 -04:00
Brandon ThomasandClaude Opus 5.5 daca626778 Fix fades outliving trimmed clips; property test of random edit sequences with full undo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:10:53 -04:00
Brandon ThomasandClaude Opus 5.5 0e85d7ee54 Copy Special › Audio as MIDI: YIN pitch tracking into MIDI notes on the clipboard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:08:18 -04:00