# Audio editor — "fancier" features (Phase 3) — Design **Goal:** Three deferred editing capabilities on the Audio tab, phased: - **3a — Interactive waveform selection** (foundation): drag in/out handles, zoom, moving playhead. - **3b — Per-join crossfade + curves** in the Merge pane. - **3c — Destructive clip editor**: cut/delete/silence/reverse a region with undo/redo. **Builds on:** Phase 1/2 — `AudioWaveform` (read-only strip), `load_region_samples`/`peaks`, extract/audition via `_current_edit_filters`, the Merge pane + `build_crossfade_merge_command`. **Runs in:** Python/Qt client + `core/`. No server/DB change. **Order:** 3a first (unlocks precise selection everywhere), then 3b (small engine extension), then 3c (largest; reuses 3a's widget). Same `audio-tab` branch, reviewed per task. --- ## Phase 3a — Interactive waveform selection Turn `AudioWaveform` into a selection surface that drives the extract region. Extract/audition/add-to-merge already use `_cursor` + `_spn_audio_len`, so they honor the dragged selection with no handler change. **View + selection model (all seconds, absolute in source):** - `_view_start`, `_view_dur` — the decoded, zoomable visible window (peaks are for this window, via the existing capped `load_region_samples`). - `_sel_start`, `_sel_end` — the selection within the window. - Pixel↔time helpers (pure, unit-tested): `t = _view_start + (x/width)·_view_dur`; `x = (t−_view_start)/_view_dur·width`. **Interaction:** - Press within `HANDLE_PX` of an in/out handle → drag that edge; press elsewhere → start a new selection at that time, drag to extend. - Move → update the active edge, clamp to the window, keep `start < end`, repaint. - Release → emit `selection_changed(sel_start, sel_end)`. - Wheel → zoom `_view_dur` about the pointer time (clamped to a min span and source length) → emit `view_changed(view_start, view_dur)`. - Height grows 48→~96 for usable dragging. **Painting (extends current):** peak bars + a translucent selection band + two bright handle lines + an optional playhead line. **Signals:** `selection_changed(float,float)`, `view_changed(float,float)`. **MainWindow wiring:** - Refresh (↻ / on load): decode `[_cursor, _cursor+view_dur]` (view_dur defaults to max(length, a few s)); `set_view(...)`, `set_peaks(...)`, selection = `[_cursor, _cursor+len]`. - `selection_changed` → set `_cursor = sel_start` and `_spn_audio_len = sel_end−sel_start` (which already refreshes the timeline band); block signals while syncing to avoid feedback. - Typing `_spn_audio_len` → update the waveform selection (two-way sync). - `view_changed` → re-decode peaks for the new window (throttled, capped decode). **Playhead (optional within 3a):** during audition a QTimer sets `_playhead = sel_start + elapsed` (ffplay gives no position; elapsed-based is close enough), cleared on stop. Deferrable if it complicates. **Tests:** unit-test the pixel↔time round-trip + clamping (pure); smoke-test that a simulated drag emits `selection_changed` and syncs `_cursor`/length, and that typing a length moves the selection. --- ## Phase 3b — Per-join crossfade + curves - Merge items gain a per-join crossfade + curve (stored `UserRole+2` = float, `UserRole+3` = curve name). New joins default from the global `_spn_crossfade` + a new curve combo; double-click a row to override that join. - `build_crossfade_merge_command` extended: accept `crossfades: float | list[float]` (scalar broadcasts to every join — backward compatible) and `curve: str` (acrossfade `c1=/c2=`; e.g. `tri`/`exp`/`log`/`qsin`). Join *i* → `acrossfade=d={xf[i]}:c1={curve}:c2={curve}`. - The duration guard checks each clip against its own join's crossfade. --- ## Phase 3c — Destructive clip editor (largest) A modal editor over ONE file (the current selection rendered to a temp, a merge output, or an opened file). Detailed plan authored after 3a/3b land. - Full-file interactive waveform (reuse the 3a widget) + a selection. - Region ops, each rendering a NEW temp version (non-in-place → clean undo): - **Delete** — `atrim`+`concat` of `[0,selStart]`+`[selEnd,end]`. - **Silence** — `volume=0`/replace over the region. - **Reverse** — `areverse` (segment via `atrim`+`areverse`+`concat`). - **Trim to selection** — keep only `[selStart,selEnd]`. - **Fade selection** — `afade` on the region. - **Undo/redo:** a stack of rendered version files; undo pops to the prior file, redo re-applies. Robust and simple. - **Save / Save-as.** Pure per-op command builders in `core/ffmpeg.py` (TDD); the dialog wires them. --- ## What this does NOT do - No multi-track timeline / overlap mixing; no spectral editing; no saved editor projects (session/file-based only).