Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transition between Sources not working propertly #695

Closed
PB-SF opened this issue Jan 21, 2019 · 14 comments
Closed

Transition between Sources not working propertly #695

PB-SF opened this issue Jan 21, 2019 · 14 comments
Labels

Comments

@PB-SF
Copy link

PB-SF commented Jan 21, 2019

We use Liquidsoap to manage moderator access and to compensate for failures of our automation through a backup list.

As an automation mAirList is used on an external server.

The moderators connect to Liquidsoap via
input.harbor

In order to make the transition between automation and moderator pleasant for the listeners, we try to build a transition between the two sources.

Currently the code is as follows:

def jtrans(j,old,new)
  old = fade.final(old)
  new = fade.initial(new)
  sequence([old,j,new])
end

s = fallback(id="Mod <-> mAirList",track_sensitive=false,transitions=[jtrans(j),jtrans(j)],[mod,mairlist])

When a moderator connects, the automation hides clean, the jingle is played, and the moderator cleanly fades in.

Unfortunately, this does not work backwards. The moderator is cut off with a hard cut, the jingle plays and the automation is faded in.

We have already tried many options on transitions (add, merge, sequence, etc.) unfortunately so far unsuccessful.

Is there a way to solve it?

Liquidsoap 1.3.4 via OPAM on a dedicated Ubuntu 16.04 server

@toots
Copy link
Member

toots commented Jan 22, 2019

Hi,

This is most certainly due to the fact that liquidsoap is caught off guard when the moderator disconnects and doesn't have any buffered data for the transition.

The other issue you will run into is that the harbor input cannot pre-buffer any data since it is coming in real time.

One thing you could try is to use the buffer operator. I cannot test right now but something like this is worth trying:

s = input.harbor(...)
s = mksafe(s)
s = buffer(s)

This will plug the harbor input into a buffered source, I believe that, when using it in a fallback it could work as intended.

I'll test later, let us know if that works.

@PB-SF
Copy link
Author

PB-SF commented Jan 22, 2019

Hi. Thank you for your prompt reply.

Unfortunately that did not work. With your suggestion, Liquidsoap will not switch to the automation input anymore and it will only be silent when no moderator is connected.

Nevertheless, I tested it further. The moderator is still hard cut off with no fade.

I forgot to mention that we are already using a buffer solution in harbor input.
input.harbor(id="mod",buffer=30.,max=60.,"mount",port=0815,password="hackme")

@hmorneau
Copy link

I'm having the same issue. Would be nice to have a script example on how to achieve this.

@hmorneau
Copy link

hmorneau commented Jan 23, 2019

I just tested as well, and mksafe make it go silent here as well.
In the log I see: 2019/01/23 01:18:02 [mksafe:3] Switch to safe_blank.
Seems like mksafe make it play nothing, but it still play so it doesn't fallback to anything else. And if I connect the input harbor, it will fade in, but on the fade out it just cut. Then it doesn't fallback.

@toots
Copy link
Member

toots commented Jan 23, 2019

Hi,

After further inspection, this is not currently possible. The problem here has a couple of facets:

  • fallback transitions are really rudimentary. In particular, they do not keep a buffer of the remaining data on the old source. Thus if transition is triggered at the end of a track, there's no data for the old source and, thus, no possible operation on it. These are called forgetful transitions in the logs
  • When using input.harbor, there's no way to know in advance when the DJ will disconnect. Thus, when a disconnect happen, it appears just as a sudden end of track and triggers a forgetful transition.

The solution to this would be:

  • To add an operator that buffers and keep a fixed amount of data ahead of the source's current position. This would delay the source by, says 5 seconds before it goes live and keep these 5 seconds when the source disconnects, allowing for a proper transition to be computed.
  • Use smart_crossfade which is designed to actually compute transitions ahead of time

I will implement this and come back here with a solution. I have no guarantee on the timeline. I do agree, however, that this is something we ought to be able to do.

@dhannyz
Copy link
Contributor

dhannyz commented Jan 26, 2019

@PB-SF @hmorneau on the earlier suggested use of buffer, get rid of mksafe() and use buffer(fallible=true,...)

@dhannyz
Copy link
Contributor

dhannyz commented Jan 26, 2019

@toots can you confirm how smart_crossfade works for a live source? would it attempt to crossfade the DJs (already mixed) tracks? In particular the chained ogg case? Or only the connect/disconnect events?

@toots
Copy link
Member

toots commented Feb 1, 2019

@dhannyz currently smart_crossfade will not work with live sources as it needs to pre-buffer data for the transition. This is a feature that I want to add to the next release.

toots added a commit that referenced this issue Feb 2, 2019
@toots
Copy link
Member

toots commented Feb 2, 2019

Hey all! So more testing shows that I was wrong. buffer() sounds like a solution to have proper transitions between live and playlists. Here's the example I'm testing:

# This is to make sure that we always do a fade transition:
def smart_crossfade (~start_next=5.,~fade_in=3.,~fade_out=3.,
                     ~default=(fun (a,b) -> sequence([a, b])),
                     ~high=-15., ~medium=-32., ~margin=4.,
                     ~width=2.,~conservative=true,s)
  fade.out = fade.out(type="sin",duration=fade_out)
  fade.in  = fade.in(type="sin",duration=fade_in)
  add = fun (a,b) -> add(normalize=false,[b, a])
  log = log(label="smart_crossfade")

  def transition(a,b,ma,mb,sa,sb)
    list.iter(fun(x)-> log(level=4,"Before: #{x}"),ma)
    list.iter(fun(x)-> log(level=4,"After : #{x}"),mb)
    log("Transition: crossed, fade-in, fade-out.")
    add(fade.out(sa),fade.in(sb))
  end

  smart_cross(width=width, duration=start_next, conservative=conservative,
              transition,s)
end

live = input.harbor("test")

live = buffer(fallible=true,buffer=10.,live)

pl = playlist("~/Music")

s = fallback(track_sensitive=false,[live,pl])

s = on_metadata(print,s)

s = smart_crossfade(s)

Couple things to note:

  • The buffer needs to have data for both the transition in and out of the source. Here, we buffer 5. seconds each time so I set it to 10.. You can do more for good practice. Remember, though, that this will delay the time the live sources goes on the air by as much time.
  • There was a slight bug preventing a proper live->playlist transition. It's fixed with the latest master

If any of y'all could test with the latest master that would be great. I'm leaving this open as I want to make sure this is in the doc somewhere before closing.

@toots
Copy link
Member

toots commented Feb 2, 2019

Oh, I forgot, this will only with if you remove track marks on the live source, otherwise it would also cross-fade the live tracks, which will not work. Fixed script:

# This is to make sure that we always do a fade transition:
def smart_crossfade (~start_next=5.,~fade_in=3.,~fade_out=3.,
                     ~default=(fun (a,b) -> sequence([a, b])),
                     ~high=-15., ~medium=-32., ~margin=4.,
                     ~width=2.,~conservative=true,s)
  fade.out = fade.out(type="sin",duration=fade_out)
  fade.in  = fade.in(type="sin",duration=fade_in)
  add = fun (a,b) -> add(normalize=false,[b, a])
  log = log(label="smart_crossfade")

  def transition(a,b,ma,mb,sa,sb)
    list.iter(fun(x)-> log(level=4,"Before: #{x}"),ma)
    list.iter(fun(x)-> log(level=4,"After : #{x}"),mb)
    log("Transition: crossed, fade-in, fade-out.")
    add(fade.out(sa),fade.in(sb))
  end

  smart_cross(width=width, duration=start_next, conservative=conservative,
              transition,s)
end

live = input.harbor("test")

live = merge_tracks(live)

live = buffer(fallible=true,buffer=10.,live)

pl = playlist("~/Music")

s = fallback(track_sensitive=false,[live,pl])

s = on_metadata(print,s)

s = smart_crossfade(s)

@gammaw
Copy link

gammaw commented Jul 6, 2019

@toots thanks, looks promising. Fading out the playlist before the live source worked indeed. But fading in the playlist after the live source disconnects did not (playlist re-starts without fading), this is what I see in the log:

[smart_cross_6626:3] Not enough data for crossing.

Do you have an idea why this happens? I have a different playlist configuration though, where radio is an Azuracast configured source and this is my custom Liquidsoap config:

radio = switch(track_sensitive=false, [ 
    #Live DJ
    ({!live_enabled}, live),
    #Monday
    ({1w and 5h-6h}, once(playlist_xyz)),  
...
    #Tuesday
...
    ({true}, radio) ])

Also, we need jingles in the transitions: fade-out, jingle, fade-in. I use simple transitions like this, which I use in the above radio switch definition (now removed):

def transition_simple(j,a,b)
  add(normalize=false,
	  [ sequence([fade.final(a),j,fade.initial(b)]) ])
end

How would you add jingles to your solution above?

gammaw referenced this issue in lahmacunradio/liquidsoap Jul 7, 2019
@PB-SF
Copy link
Author

PB-SF commented Jul 17, 2019

Sorry, dont have much time last months.

@toots

I tried to solve the problem with an installation of the 1.4.0-beta2. Unfortunately, I could not find a way to make it work as it should.

I've tried transitions with it, as I currently use in my production script on 1.3.7 and I have tried it with the revised crossfade.

It is also in the 1.4.0-beta2 that the fade.out of the playlist works, but no fade.in the input.http or input.harbor takes place. the live sources "jump in" with full volume without fade.

Any idea or approach, what am I doing wrong?

I use this code at the moment:

def crossfade (a, b)
  def add_transition_length (_) =
    [( 'Liq_transition_length "." 15 ")]
  end

  transition =
    add (normalize = false,
      [Sequence ([blank (duration =. 2),
        fade.in (duration = 4, b)]),
      fade.out (duration = 4, a)])

  map_first_track (map_metadata (add_transition_length), transition)
end

With that, I trigger transitions in fallback.

Previously I had set the new crossfade at the end of the script before the output.

As mentioned, unfortunately, both do not work.

I'm grateful for every note.
Many Thanks.

@stale
Copy link

stale bot commented Jan 13, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jan 13, 2020
@stale
Copy link

stale bot commented Jan 20, 2020

This issue was closed for lack of activity. If you believe that it is still relevant, please confirm that it applies to the latest released version of liquidsoap and re-open the ticket. Thanks!

@stale stale bot closed this as completed Jan 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants