Skip to content

Fix standard crossfade falling back to a hard cut on some tracks#4253

Merged
marcelveldt merged 3 commits into
devfrom
fix-standard-crossfade-no-output
Jun 16, 2026
Merged

Fix standard crossfade falling back to a hard cut on some tracks#4253
marcelveldt merged 3 commits into
devfrom
fix-standard-crossfade-no-output

Conversation

@marcelveldt

Copy link
Copy Markdown
Member

What does this implement/fix?

Since 2.9, Standard Crossfade silently dropped to a hard cut for some tracks, logging
Crossfade mixer failed ... produced no output.

FFmpeg's acrossfade emits zero output (with a success exit code) when an input is even a
fraction of a sample shorter than the requested duration d. StandardCrossFade computed the
overlap as a float but sliced the PCM buffers on frame boundaries, so a non-integer crossfade —
e.g. when a quiet outro is trimmed from the outgoing track — left the buffer a sliver shorter
than d and produced nothing. Tracks ending on an exact second were unaffected, which made it
look intermittent.

Changes

  • Derive a single frame-aligned overlap size in StandardCrossFade and use it for both the
    buffer slice and the filter, so they can no longer disagree.
  • Let CrossfadeFilter emit acrossfade=ns=<samples> (exact sample count) instead of
    d=<seconds>; the standard path now uses it. Smart crossfade is unchanged.
  • Fix the misleading "Smart crossfade" wording in the shared FFmpeg error message.
  • Add regression tests for the fractional-overlap case.

Types of changes

  • Bugfix (non-breaking change which fixes an issue) — bugfix
  • New feature (non-breaking change which adds functionality) — new-feature
  • Enhancement to an existing feature — enhancement
  • New music/player/metadata/plugin provider — new-provider
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — breaking-change
  • Refactor (no behaviour change) — refactor
  • Documentation only — documentation
  • Maintenance / chore — maintenance
  • CI / workflow change — ci
  • Dependencies bump — dependencies

Checklist

  • The code change is tested and works locally.
  • pre-commit run --all-files passes.
  • pytest passes, and tests have been added/updated under tests/ where applicable.
  • For changes to shared models, the companion PR in music-assistant/models is linked.
  • For changes affecting the UI, the companion PR in music-assistant/frontend is linked.
  • I have read and complied with the project's AI Policy for any AI-assisted contributions.
  • I have raised a PR against the documentation repository targeting the main or beta branch as appropriate.

FFmpeg's acrossfade emits no output (with a success exit code) when an input is
even a fraction of a sample shorter than the requested duration. StandardCrossFade
computed the overlap as a float but sliced the PCM buffers on frame boundaries, so a
non-integer crossfade left the buffer a sliver short of `d` and produced nothing —
common when a quiet outro is trimmed from the outgoing track. Tracks ending on an
exact second were unaffected, which made it look intermittent.

Drive both the buffer slice and the filter from a single frame-aligned overlap size
and use acrossfade's integer `ns=` form, so the requested length always matches the
buffer exactly. Also fix the misleading "Smart crossfade" wording in the shared
FFmpeg error message (the standard path uses the same code).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an intermittent failure mode in the standard crossfade path where FFmpeg’s acrossfade can return success but emit zero output if the requested overlap slightly exceeds the provided PCM buffer. It does this by quantizing the overlap to an integer frame-aligned size and driving both buffer slicing and the FFmpeg filter length from that same integer (via acrossfade=ns=), plus adds regression tests.

Changes:

  • Update StandardCrossFade to compute a single frame-aligned overlap size once and reuse it for both slicing and filter construction.
  • Extend CrossfadeFilter to support sample-count overlaps (acrossfade=ns=) and enforce “exactly one of duration or samples” at construction time.
  • Add/adjust tests to validate ns= emission and guard the fractional-overlap regression.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
tests/core/test_smartfade_transition_timings.py Adds regression coverage for fractional overlaps and asserts the standard path uses sample-count crossfade length.
tests/controllers/streams/smart_fades/test_filters.py Adds unit tests for acrossfade=ns= output and constructor validation.
music_assistant/controllers/streams/smart_fades/filters.py Enhances CrossfadeFilter to emit d= or ns= depending on how it’s constructed.
music_assistant/controllers/streams/smart_fades/fades.py Makes standard crossfade overlap frame-aligned and updates FFmpeg error wording to “Crossfade …”.

Comment thread tests/core/test_smartfade_transition_timings.py Outdated
Comment thread tests/core/test_smartfade_transition_timings.py
CI mypy flagged the int | None sample count in a multiplication, and Copilot noted
the test fed a non-frame-aligned byte length (not a valid PCM buffer). Narrow the
type and build the fade-out length on a frame boundary — still a fractional number
of seconds, so it keeps exercising the no-output regression.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread music_assistant/controllers/streams/smart_fades/fades.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

crossfade_size now defaults to 0, which is also a valid built state (silent/tiny
buffer), so guard apply() on the filter chain set by _build() — restoring the
fail-fast on incorrect usage, consistent with SmartFade._get_ffmpeg_filters().
@marcelveldt marcelveldt merged commit 9058d49 into dev Jun 16, 2026
9 checks passed
@marcelveldt marcelveldt deleted the fix-standard-crossfade-no-output branch June 16, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants