8.1 KiB
Audio Timeline Picker And Heal Cut Editor Design
Goal
Make audio editing practical by splitting the workflow across two clear surfaces:
- The main timeline is the fast, precise audio region picker.
- The clip editor is the finishing surface for one selected clip, centered on removing short noise artifacts and rejoining the audio cleanly.
This pass focuses on the common workflow: select a bad 1-2 second artifact, remove it, heal the join, audition the seam, then save the result.
Current Context
Recent audio work made the Audio tab first-class and added an audio region band, waveform selection, a clip library, merge support, and a destructive clip editor. The existing implementation is functionally broad, but the interaction model is still weak:
- The timeline audio band has small edge hit targets and poor discoverability.
- The timeline has a playhead, but in Audio mode the relationship between the yellow cursor and teal audio region is not clear enough.
- The editor supports Delete, Silence, Reverse, Trim, undo/redo, playback, and Save As, but it lacks a purpose-built artifact-removal operation.
- The merge/crossfade pane is useful, but the immediate need is better one-clip cleanup rather than multi-clip assembly.
Approved Scope
In scope:
- Upgrade the existing single-lane timeline audio band rather than adding a separate audio lane.
- Make the teal audio band easy to move, resize, and create with the mouse.
- Keep the yellow playhead/cursor visible and visually distinct in Audio mode.
- Keep the timeline band, length spinbox, and waveform selection synchronized.
- Add a primary
Heal Cutoperation toAudioEditorDialog. - Keep existing editor operations as secondary tools: Hard Delete, Silence, Reverse, Trim.
- Add join-focused loop audition after Heal Cut.
- Add clearer save actions: Save to Library and Save As.
- Store Heal Cut outputs in the existing temp-version undo/redo stack.
- Add a pure ffmpeg command builder in
core/ffmpeg.pyso heal behavior is testable outside Qt.
Out of scope for this pass:
- A separate timeline audio lane.
- A full multitrack editor.
- Saved editor projects.
- Spectral repair or ML audio inpainting.
- Major changes to the merge/crossfade pane.
- Database or schema changes.
Timeline Picker Design
TimelineWidget remains the audio picking surface in Audio mode.
Interaction changes:
- Increase the audio band edge hit target and draw larger visible handles.
- Change the cursor on hover:
- left/right edge: horizontal resize
- inside band: move
- outside band: set/create region
- Drag an edge to resize the region.
- Drag inside the region to move it while preserving length.
- Click outside the region to set the audio region start at that time while preserving length.
- Drag from empty timeline space to create a new region.
- Emit
audio_region_changed(start, end)on completed user edits, as today.
Visual changes:
- Keep the teal band as the audio region.
- Use brighter/solid handle styling in Audio mode.
- Keep the yellow cursor/playhead visible at the current start/play position.
- Avoid showing the normal blue export clip span in Audio mode.
- Preserve scan-region edge behavior and normal non-audio export behavior outside Audio mode.
Sync behavior:
MainWindowremains the owner of synchronization between:TimelineWidget._audio_region_cursor_spn_audio_lenAudioWaveformselection
- A timeline audio edit updates
_cursorand audio length. - A length spinbox edit updates the timeline audio band and waveform selection.
- A waveform selection edit updates
_cursor, the length spinbox, and the timeline audio band.
Editor UI Design
AudioEditorDialog becomes a focused one-clip cleanup tool.
Layout:
- Large waveform area with selection handles.
- Primary action panel with
Heal Cut. - Secondary destructive tools:
- Hard Delete
- Silence
- Reverse
- Trim
- Playback controls:
- Play clip
- Play selection
- Loop Join
- History:
- Undo
- Redo
- Save:
- Save to Library
- Save As
Behavior:
- Heal Cut is the recommended/default artifact-removal action.
- Hard Delete remains available for cases where no smoothing is wanted.
- After a successful Heal Cut, the editor sets a join preview range and starts or enables looping around the healed seam.
- Save to Library writes the current edited version into the managed audio library folder and adds it to
AudioLibraryTab. - Save As keeps the existing file-dialog path.
Heal Cut Behavior
Primary workflow:
- User selects the artifact region in the editor waveform.
- User clicks
Heal Cut. - The editor renders a new temp version.
- The selected artifact is removed.
- The before/after audio is rejoined with an automatic short equal-power crossfade.
- The editor reloads the new version, pushes it onto the undo stack, and prepares a loop preview around the seam.
Command builder:
Add this pure function in core/ffmpeg.py:
def build_audio_heal_delete_command(
input_path: str,
start: float,
end: float,
out_path: str,
crossfade: float | None = None,
) -> list[str]:
Automatic crossfade:
- If
crossfadeis omitted, choose it automatically. - Use a small practical range: 40-250 ms.
- Base the chosen duration on the selected artifact length and nearby available material.
- Clamp crossfade so it fits before
startand afterend. - If no crossfade can fit, fall back to the shortest valid fade or hard delete and report that in editor status.
Implementation shape:
- Build the output from the kept region before the artifact and the kept region after the artifact.
- Overlap the join by the chosen crossfade duration.
- Use ffmpeg
acrossfadewithqsinfor both sides (c1=qsin:c2=qsin), matching the existing curve list in the merge code. - Keep output codec behavior aligned with the existing audio command helpers.
Zero-crossing:
- Zero-crossing edge adjustment is desirable, but should not block the first useful version.
- First pass should deliver automatic equal-power Heal Cut via ffmpeg.
- Treat sample-level zero-crossing adjustment as a deferred enhancement after the ffmpeg-based Heal Cut is working and tested.
Error Handling
Heal Cut guardrails:
- If no selection exists, show
Select an artifact first. - If the selection is too short, show a status message and do not edit.
- If the selection would remove the whole clip, block it.
- If the selected region is too close to the start or end for the preferred crossfade, use a shorter valid fade.
- If ffmpeg fails, keep the current version unchanged and remove the temp output.
- If the output is empty or missing, treat it as failure.
Timeline guardrails:
- Audio-mode mouse behavior must not affect non-audio export mode.
- Audio-band interactions must not interfere with Shift-drag scan-region resizing.
- Re-entrant synchronization between timeline, waveform, and spinbox must stay guarded.
Testing Plan
Unit tests in tests/test_utils.py:
build_audio_heal_delete_commandnormal case uses a crossfade join.- Explicit crossfade duration is respected.
- Automatic crossfade is clamped to a practical range.
- Near-start and near-end selections do not produce negative trim times.
- Whole-clip or invalid selections are rejected by the caller or produce a clear error path.
- Existing Delete/Silence/Reverse command tests continue to pass.
UI tests in tests/test_ui_structure.py:
- Timeline audio band exposes larger handle behavior through direct helper tests.
- Clicking/dragging empty audio-mode timeline space can create or reset an audio region.
- Timeline audio edit still syncs
_cursorand_spn_audio_len. AudioEditorDialoghas a primary Heal Cut button.- Heal Cut invokes the new command builder and pushes a new undo version on success.
- Heal Cut failure leaves the current version unchanged.
- Save to Library adds the saved current version to the library.
Verification commands should keep the repo's existing isolation rule:
LD_PRELOAD=/usr/lib/libstdc++.so.6 QT_QPA_PLATFORM=offscreen python -m pytest tests/test_ui_structure.py -v
LD_PRELOAD=/usr/lib/libstdc++.so.6 QT_QPA_PLATFORM=offscreen python -m pytest tests/test_utils.py -v
Do not combine the Qt structure tests and utility tests in one process.