#181 already pushes instrument params each block, so drop the duplicate
apply_instrument_params. Import Session Data now brings a Sampler's sample
along and remaps its source id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes#166: dragging a selection up from the last track deselected it and
selected every track above instead. Fixed, along with related Edit-window drag
problems:
- Drag selection spans exactly the tracks between the press and the pointer,
in either direction, and the whole window updates in the same frame.
- Shift+click in another track adds it to the selection with the same range,
as in Pro Tools, so the selected tracks no longer have to be adjacent. A
click outside the range extends it.
- Track names follow Pro Tools' rules in the Edit window, the Mix window and
the Tracks list: Shift+click selects every track between the nearest
selected one and the clicked one, and Cmd+click (Ctrl+click on Windows and
Linux) adds or removes one. A click on a name used to replace the selection;
only the Edit header's background added, and it treated Shift like Cmd.
- Dropping a track's name plate lower down no longer sends the track to the end.
- Drags act where you pressed: a quick drag from a clip edge now trims instead
of moving the clip.
- Dragging past the edge of the track area scrolls, and a flick past the last
track selects down to it.
- The insertion point moves on press, shows immediately, and only on the
selected tracks.
- edit.select no longer makes a new document revision when nothing changed
(each one is resent to the audio engine).
- Fixes a panic when a fade drag's clip shrinks to zero length.
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>
- When every lane point lay inside the selection the lane was cleared,
so the held level outside fell back to the fader (6 dB louder in the
repro). Clear only when all clips lie inside the selection.
- The clip's own gain points inside the selection are kept, offset by
the lane, instead of being dropped.
- Volume/clip-gain ramps now end on the block's last sample, so
interpolate over n-1 steps and actually reach the end value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Close Session, New, Open, Revert, Quit, the window's close button,
dropping a session file on the window and opening one from the Workspace
window all replaced or closed the session without asking, so unsaved
edits were lost (#136). They now ask first: Save, Don't Save or Cancel.
Save on a session that has never been saved goes through Save As, then
carries on. Revert asks for confirmation instead, and Revert to Saved is
unavailable until the session has a file. A take being recorded is
stopped first, so it counts as a change. The web build has nowhere to
save, so it offers only Don't Save and Cancel. Calls from the control
channel, MCP and the CLI never ask.
Selecting, locating, zooming and scrolling no longer mark the session
modified, so the question only comes after real edits. Undoing back to
the saved state makes the session clean again. The engine compares the
document with the one last opened or saved, ignoring the selection, the
playhead and the zoom, once per revision.
Fixes#136
Nothing a save wrote was synced to disk, so a power cut or crash of the
system soon after Cmd+S could leave an empty or truncated session file
(the rename can reach the disk before the data), or audio files that
the session points at but that never reached the disk. No earlier
version of the session file was kept either.
New audio files, the Audio Files folder and the session's temporary
file are now synced before the session file is renamed into place, and
the session folder after the rename; templates are written the same
way. A failed write or rename removes its temporary file instead of
leaving it on a full disk. Before a save replaces the session file with
different contents, the previous version is copied to
Backups/<name> backup <n>.scraft beside it, and the newest ten are
kept. Autosaves make no backups.
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.
Audio that exists only in memory (takes, consolidations, AudioSuite
renders, commits) gets the default path `Audio Files/<name>.wav`, and
Save treated an existing file at that path as the source's own and
rewrote it. Names repeat easily: two sessions in one folder both call
their first take on Audio 1 "Audio 1_02", and consolidating twice on
Kick makes two "Kick_consolidated" sources. Saving silently replaced
the earlier audio, so the other session or the earlier clip played the
wrong audio once reopened.
Save As also trusted a same-named file in the target folder for audio
saved elsewhere, so saving into another session's folder could point
the new session at that session's audio.
New audio now goes to the first free name (`<name>.wav`,
`<name>_01.wav`, ...), created with create_new so no existing file is
ever truncated, or to a file that already holds exactly the same bytes,
such as a take saved again after undoing past a save. Saved audio is
reused only when saving into the folder it was saved in, which the
session now remembers, so undoing past a Save As or saving after Save
Copy In can't pick up another session's files either. Audio that
failed to load keeps pointing at its old folder when the session is
saved elsewhere.
- 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.
A second consolidate of the same track reused Audio Files/{name}.wav, and saving then skipped the rename because that path already belonged to the earlier source. New sources now take a free name, and a save splits sources that already share a path instead of overwriting one file.
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.
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.
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.
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>
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.
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.