- clip.audioGain takes mode set|adjust|normalizeMax|normalizeAll (the
old {db, relative} form still works); peaks are measured on the
clips' source ranges. Normalize Max Peak applies one adjustment so the
loudest clip lands on the target; Normalize All Peaks sets each clip.
- clip.audioPeak reports the selection's peak amplitude (with gain).
- Clip > Audio Gain... (G) opens the dialog with the four options, dB
fields and the peak amplitude; automation ids audioGain.*.
- effects.setDefaultTransition sets the default video or audio
transition (Constant Power / Constant Gain / Exponential Fade) that
sequence.applyAudioTransition (Cmd+Shift+D) uses.
- tests: gain modes and peaks, dialog by automation id, transition
curves (power / amplitude preservation, end points).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Audio Track Mixer: strips for audio tracks, submixes and the Mix with
effect/send slots (disclosure), input mapping, output assignment, pan
knob, automation mode, M/S/R (solo safe on submixes), fader with dB
scale and our own taper, stereo meters fed by the playing mix, value
hot text, footer transport; horizontal/vertical scrolling. Fader and
knob drags are mixer.touch while held and mixer.release on let go, so
they are heard live and recorded in Latch/Touch/Write.
- Audio Clip Mixer: per-track strips for the clip under the playhead
(clip Volume / Panner, write-keyframes toggle), clipMixer.set with one
undo step per drag (Session::edit_merged).
- Timeline: per-track "Show Keyframes" button picks clip keyframes or a
track lane (volume, panner, mute, sends, insert params); the lane is
drawn across the track, Pen / Cmd-click adds, diamonds drag in time and
value, right-click deletes.
- Playback starts an automation pass and stopping writes it; the audio
callback reads the newest project snapshot (LiveMix), so mixer moves
are heard while playing. Audio Meters read the graph's live meters
instead of re-mixing at the playhead.
- Automation ids on every control; headless kittest tests drive the
panels by id and by multi-frame drags (FILMCRAFT_UI_SNAPSHOT_DIR renders
offscreen PNGs through wgpu).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Engine shortcut set (crates/engine/src/shortcuts.rs): bindings as data
(command, canonical chord, optional panel), panel shortcuts override app ones
while the panel has focus; per-OS modifiers (Cmd = Cmd/Ctrl, Ctrl = macOS
Control, folded into Ctrl elsewhere and reported as conflicts); conflict
detection, reassignment, undo/redo, Cancel restores; frontend commands
registered so tools/transport/panels are bindable too.
- Presets: FilmCraft Default, Premiere Pro / Final Cut Pro / Avid Media
Composer Compatible (key tables mapped onto FilmCraft command ids); custom
presets as JSON in <data dir>/Keyboard Shortcuts/, active set persisted,
Save As / Delete / Export / Import (Windows-notation files converted).
- shortcuts.* commands for agents/MCP (list/get/set/clear/undo/redo/conflicts/
forKey/resolve/presets/loadPreset/savePreset/deletePreset/export/import/audit).
- Premiere-default audit: commands without a shortcut get Premiere's key when
free (Shift+D Apply Default Transitions, Shift+J/L slow shuttle, Shift+K Play
Around, Project > Shift+O Open in Source Monitor); Audio Clip/Track Mixer
window shortcuts corrected (Shift+9 / Shift+6).
- Edit > Keyboard Shortcuts... (Cmd+Alt+K): keyboard drawn in code with
app/panel/both colouring and modifier filter, searchable command list with a
record-on-click Shortcut column and +, Key: detail list, conflict warnings,
Undo/Redo/Clear/Cancel/OK; automation ids shortcuts.*.
- Menus (in-app and native macOS key equivalents), command.list and the input
loop follow the active bindings live.
- Tests: 7 engine tests; scripted kittest test (record keys, live menu update,
reassignment, undo/redo, cancel, OK, panel-specific Left in History).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Trim Monitor: with edit points selected the Program monitor shows the
outgoing/incoming frames, Out/In timecodes and shift counters, -N/-1/+1/+N
buttons (N = Preferences > Trim > Large Trim Offset), Apply Default
Transitions, play-around loop and exit; drag in a picture trims that side,
between them rolls. Automation ids trimMonitor.*.
- Dynamic trimming in the engine (trim.shuttle / trim.tick / trim.shuttleStop /
trim.cancelDynamic), driven by a caller-supplied clock: L forward, J back,
again = 2x..8x, Shift = slow, K commits as ONE undo step, Esc cancels; holds
at limits; any other command commits first. Live updates re-apply the offset
to the pre-trim project without touching history or the journal.
- trim.playAround: loop/once from preroll to postroll (new Preferences >
Playback preroll/postroll, Trim > playhead determines loop).
- trim.monitor query, trim.applyDefaultTransition (Shift+D); timeline draws
selected edit points (red ripple / yellow roll-trim brackets) and clicking an
edge with Selection/Ripple/Rolling selects it; selecting clips leaves trim mode.
- J/K/L/Space/Shift+K route to trim mode in the UI; new Shuttle Slow L/R and
Play Around commands.
- Tests: 6 engine tests on an exact simulated clock; scripted kittest test
(buttons, L/K/J, one undo step, Space loop, exit); ignored wgpu screenshot test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- project: ItemKind::Graphic (transparent canvas source, hidden from the Project panel,
unlimited duration); graphic layers are hidden effect instances graphic_text /
graphic_shape on the clip (serde for free, keyframes on every animatable property,
Source Text holds), with appearance (fill, two strokes outer/centre/inner, background
box with radius, drop shadow) and transform (position, anchor, scale, rotation,
opacity); eval_layer gives a plain LayerSpec. No schema migration: all additive.
- render: graphic_clip rasterises each layer straight to the output transform (crisp at
any Motion scale/rotation), caches layer rasters, composes a tight image for the GPU
plan; standard effects on a graphic run at source resolution first.
- engine: graphics.newText (Cmd+T) / newShape (new clip above the footage at the playhead
or a layer in an existing clip), setText (typing coalesces into one undo step), set,
selectLayer, deleteLayer, arrangeLayer, align (frame / selection), distribute,
graphics.list, fonts.list. EditorState.graphic_layers holds the layer selection.
- goldens: graphic_title, graphic_shapes (CPU and GPU parity).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- project: MixerStrip on every audio track, submix tracks and the Mix
(automation mode Off/Read/Latch/Touch/Write, lanes for volume/pan/mute/
send levels, effect-parameter automation in the insert's own keyframes,
sends pre/post fader, output routing, record arm, solo safe, input map);
pre/post-fader flag on inserts; serde defaults (no schema migration).
- render: mixer graph (tracks -> submixes -> Mix) in Premiere's order:
pre-fader inserts, pre-fader sends, mute, fader, meter, post-fader
inserts, post-fader sends, pan/balance. Per-sample automation, effect
params on an absolute 64-sample grid (output independent of request
cutting), route-level latency compensation, cached continuation state
with pre-roll, solo/solo-safe logic, -3 dB constant-power pan law for
mono tracks, balance for stereo. LiveMix carries held controls (Touch
ramps back over the automatch time), meters and the newest project.
- engine: mixer.* commands (setStrip, setValue, touch/release,
recordStart/recordStop, submixes, inserts, sends, track keyframes,
writeAutomation, inspect) and the automation recorder (gesture streams
thinned into keyframes, one undo step per pass, Switch to Touch after
Write); Preferences > Audio (automatch time, keyframe thinning).
- tests: gain/pan law, block-boundary automation, solo/mute/solo-safe,
sends, latency alignment, render-vs-playback identity, recorder modes,
perf (24 tracks x EQ+Dynamics+Reverb + submix: 6.3x realtime, one core).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Engine autosave module: Preferences (autoSave.enabled/intervalMinutes=5/maxVersions=20/
saveCurrentProject/recoveryJournal/recoveryIntervalSeconds), persisted in the per-user data
dir; prefs.get/set/reset commands.
- Background worker (filmcraft-autosave thread): the UI thread only hands over the Arc<Project>
snapshot; JSON encoding and atomic writes happen on the worker. Premiere-style auto-saves to
<project dir>/Auto-Save/<name>-YYYY-MM-DD_HH-MM-SS.fcproj (local time), only when changed,
rotated to Maximum Project Versions, optionally also saving the project.
- Recovery journal: debounced snapshot of unsaved changes in <data>/Recovery/<session>/ guarded
by an OS file lock; on launch, sessions whose lock is free and that left a snapshot are offered.
Clean quit removes the journal (or keeps it, marked cleanExit, if changes were unsaved).
- Commands: file.recover, file.discardRecovery, file.recoveryList, file.autoSaveNow,
file.autoSaveStatus, file.listAutoSaves.
- UI: Preferences ▸ Auto Save page (Edit ▸ Preferences, Cmd+,; FilmCraft ▸ Settings… on macOS),
"Recover Unsaved Changes" dialog at startup, Revert confirmation; all with automation ids.
- Desktop: --recover / --no-recover / --data-dir; local-time offset via chrono (clock).
- Verified in the app: edits, kill -9, relaunch, dialog, Recover restores the edits (unsaved, original path).
- docs/project-files.md: auto-save, recovery and measured costs (2,000 clips: 2.5 ms encode,
1.4 MB, ~30 ms atomic write+fsync, all off the UI thread). ROADMAP M11 updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New crate filmcraft-format (L3): files are an envelope {format, schema_version, generator,
project}; a chain of single-step JSON migrations (v1 bare Project → v2 envelope) upgrades old
files on load; newer-than-supported files are refused with a clear "update FilmCraft" error;
damaged/foreign files report as such.
- atomic_write: temp file in the same dir + fsync + rename + dir fsync; used for every project
write (FsServices), so a crash mid-save never corrupts the project.
- Engine: file.save/saveAs/open go through the format crate; new file.saveCopy (Save a Copy…,
Cmd+Alt+S) and file.revert; the first save over an upgraded file keeps
"<name> (schema vN backup).fcproj". Project.version dropped (the envelope carries it).
- Hand-made v1 fixtures (minimal + edit without later serde-default fields) in
crates/format/tests/fixtures; 2,000-clip encode/decode timing test.
- docs/project-files.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>