diff --git a/README.md b/README.md
index 4358fe7..63d42a8 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,13 @@ All clips are exactly 8 seconds — the standard length for foley sound datasets
- **Waveform preview** — a read-only strip of the current area (↻ to refresh; decode length-capped so long areas don't stall)
- **Audition** — Play/Stop the current area with the edits applied
+### Audio merge (crossfade)
+
+- **Merge pane** — assemble multiple clips into one, crossfading every join (ffmpeg `acrossfade`)
+- **Add clips** — the current audio area (with its edits) or audio files from disk; reorder or remove them
+- **Crossfade length** — one global crossfade applied at each join (0 = butt-join); clips shorter than the crossfade are flagged before rendering
+- **Preview / Save** — audition the merged result, or render and save-as (WAV/MP3/FLAC/…)
+
### Audio scanning
- **Embedding models** — WAV2VEC2 (base/large), HuBERT (base/large/xlarge), BEATs
diff --git a/main.py b/main.py
index 12821d0..46c38b4 100755
--- a/main.py
+++ b/main.py
@@ -5031,9 +5031,17 @@ class MainWindow(QMainWindow):
# ── Changelog ────────────────────────────────────────────
- APP_VERSION = "1.3"
+ APP_VERSION = "1.4"
_SPLIT_HEADER_H = 22 # deck split-column header height (keep both deck spots in sync)
CHANGELOG: list[tuple[str, list[str]]] = [
+ ("1.4", [
+ "Merge (crossfade) — a new Merge pane in the Audio tab "
+ "assembles multiple clips into one, crossfading every join. Add the "
+ "current audio area (with its edits) or files from disk, reorder or "
+ "remove them, set the crossfade length, then Preview to "
+ "audition or Merge & Save to render. Clips shorter than "
+ "the crossfade are flagged before rendering.",
+ ]),
("1.3", [
"Audio workspace tab — the Scan control-deck tab is "
"now Audio, a QToolBox with two panes. "
@@ -8646,6 +8654,14 @@ class MainWindow(QMainWindow):
def closeEvent(self, event):
_log("Shutting down…")
+ # Stop any running audition and clean up merge temp WAVs.
+ self._stop_audition()
+ for _t in list(self._merge_temps):
+ try:
+ os.remove(_t)
+ except OSError:
+ pass
+ self._merge_temps.clear()
# Save file-list tabs for resume (per-profile).
self._save_playlist_tabs()
# Cancel background workers to prevent callbacks into dead objects.