Skip to content

Remove autocue specific crossfade code. - #4893

Merged
toots merged 8 commits into
mainfrom
cleanup-crossfade
Feb 14, 2026
Merged

toots merged 8 commits into
mainfrom
cleanup-crossfade

Conversation

@toots

@toots toots commented Feb 12, 2026

Copy link
Copy Markdown
Member

We did a lot of conditional change on the crossfade code around the 2.3.x release. The reason for these conditional changes was that we were trying to keep existing behavior while working on a new one with @RM-FM and @Moonbase59

Now it's time to clean it up!

Looking at the code, here's what makes sense to me:

  • We try to buffer the same amount of data from the ending and starting track based on crossfade_duration.
  • If that amount is not the same, we have to keep the extra data outside of the transition function, either at the beginning of the ending track or at the end of the finishing track.
  • On that common crossfaded amount of data we:
    • Check that fade.in duration is not more than the buffered data. If so, we have to reduce the duration.
    • If it's longer, it's okay, we assume the user wants the fade in to hit full volume before the end of the buffered data
  • However, for fade.out:
    • If fade.out duration is longer than the buffered data, same deal, we reduce it
    • But if it's shorter, we assume that the fade out is expected to end at the end of the buffered data so we add a corresponding fade out delay to make sure that the fade out is triggered in time to finish at the end of the buffer.

For these reasons: start_duration and end_duration are also removed.

Lastly, ever since that work happened, we have considered that metadata beginning with liq_ should be considered internal to liquidsoap. If you need to pass your own custom metadata, please avoid this prefix and if you're messing with them, we won't guarantee that what you're expecting won't break from one version to the next.

This is because we want to be able to adjust these values according to our internal implementation, which may change. Typically, for autocue implementation, we provide a return type for the function:

   string) -> 
  {
    amplify? : string,
    cue_in : float,
    cue_out : float,
    extra_metadata? : [string * string],
    fade_in : float,
    fade_in_curve? : float,
    fade_in_type? : string,
    fade_out : float,
    fade_out_curve? : float,
    fade_out_type? : string,
    start_next? : float}?)

And convert that to the appropriate metadata internally.

Here are some generated ASCII diagrams explaining this:

1. Buffering Phase

Buffer crossfade_duration from each track:

  Ending Track (A):                              Starting Track (B):
  ════════════════════════════╦══════════════    ══════════════╦════════════════════════════
                              ║   buffered        buffered     ║
                              ║ <──────────>    <──────────>   ║
                              ║  A_duration      B_duration    ║
                              ╚════════════════════════════════╝
                                      Transition Window

If A_duration ≠ B_duration, the side with more data keeps extra outside the overlap:

Case A: A_duration > B_duration — Extra data on the left (ending track)

          ┌─────────┬──────────────────────────────┐
  Track A │ extra A │           A buffer           │
          └─────────┴──────────────────────────────┘
                    ┌──────────────────────────────┐
  Track B           │           B buffer           │
                    └──────────────────────────────┘
          ├─────────┼──────────────────────────────┤
          │         │                              │
          │         └───── overlap (crossfade) ────┘
          │
          └─ A plays alone

          <────────────────────────────────────────>
                   full transition

Case B: B_duration > A_duration — Extra data on the right (starting track)

          ┌──────────────────────────────┐
  Track A │           A buffer           │
          └──────────────────────────────┘
          ┌──────────────────────────────┬─────────┐
  Track B │           B buffer           │ extra B │
          └──────────────────────────────┴─────────┘
          ├──────────────────────────────┼─────────┤
          │                              │         │
          └───── overlap (crossfade) ────┘         │
                                                   │
                                      B plays alone┘

          <────────────────────────────────────────>

2. Common Crossfade Window

Uses minimum of both buffers:

  │                           crossfade_duration                              │
  │ <───────────────────────────────────────────────────────────────────────> │
  └───────────────────────────────────────────────────────────────────────────┘

3. Fade.in Behavior (Starting Track B)

Case A: fade.in ≤ buffer — OK, reaches full volume before end

  ┌───────────────────────────────────────────────┐
  │                   buffer                      │
  │      ╱▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔  │  B at full volume
  │     ╱                                         │
  │    ╱  fade.in                                 │
  │   ╱ <────────>                                │
  └───────────────────────────────────────────────┘

Case B: fade.in > buffer — Reduce fade.in to fit

  ┌───────────────────────────────────────────────┐
  │                   buffer                      │
  │                                             ╱ │
  │                                            ╱  │  fade.in reduced
  │                                           ╱   │  to buffer length
  │   <─────────────────────────────────────>╱    │
  └───────────────────────────────────────────────┘

4. Fade.out Behavior (Ending Track A)

Case A: fade.out > buffer — Reduce fade.out to fit

  ┌───────────────────────────────────────────────┐
  │                   buffer                      │
  │ ╲                                             │
  │  ╲                                            │  fade.out reduced
  │   ╲                                           │  to buffer length
  │    ╲<───────────────────────────────────────> │
  └───────────────────────────────────────────────┘

Case B: fade.out < buffer — Add delay so fade ends at buffer end

  ┌───────────────────────────────────────────────┐
  │                   buffer                      │
  │ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔╲               │
  │                                ╲              │  A at full volume
  │ <───────────delay─────────────> ╲  fade.out   │  then fades out
  │                                  ╲<────────>  │
  └───────────────────────────────────────────────┘

delay = buffer - fade.out

5. Example with Extra Data

When ending track (A) has more buffered data — extra on the left:

       extra        common crossfade window
     <───────> <─────────────────────────────>

     ┌───────┬───────────────────────────────┐
     │▔▔▔▔▔▔▔│▔▔▔▔▔▔▔▔▔▔▔▔▔╲                 │
     │       │              ╲                │  Track A
     │ kept  │               ╲               │  (ending)
     │outside│                ╲              │
     └───────┴─────────────────╲─────────────┘
                                ╲
                                 ╲
             ┌────────────────────╲──────────┐
             │                     ╲         │
             │                      ╲        │  Track B
             │                       ╱▔▔▔▔▔▔▔│  (starting)
             │                      ╱        │
             └─────────────────────╱─────────┘

Result: Extra data from A plays before crossfade begins

When starting track (B) has more buffered data — extra on the right:

              common crossfade window         extra
     <─────────────────────────────> <───────>

     ┌───────────────────────────────┐
     │▔▔▔▔▔▔▔▔▔▔▔▔▔╲                 │
     │              ╲                │  Track A
     │               ╲               │  (ending)
     │                ╲              │
     └─────────────────╲─────────────┘
                        ╲
                         ╲
     ┌────────────────────╲──────────┬───────┐
     │                     ╲         │▔▔▔▔▔▔▔│
     │                      ╲        │ kept  │  Track B
     │                       ╱▔▔▔▔▔▔▔│outside│  (starting)
     │                      ╱        │       │
     └─────────────────────╱─────────┴───────┘

Result: Extra data from B plays after crossfade ends

Base automatically changed from add-all-meta to main February 12, 2026 00:47
@toots
toots force-pushed the cleanup-crossfade branch from 562f7dd to 12d2fed Compare February 12, 2026 15:33
@toots
toots added this pull request to the merge queue Feb 13, 2026
Merged via the queue into main with commit 8ce87e0 Feb 14, 2026
51 checks passed
@toots
toots deleted the cleanup-crossfade branch February 14, 2026 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant