Why is the calculation of end time for an audio clip always slightly different every frame?

In our audio plugin, when turning on “gapless song transitions”, every frame we calculate the end time of the current song so we can schedule a play of the next song. If the end time has changed from when we scheduled it last, we stop and reschedule the next song. Currently, about 90% of the time, the calculation is different than the one calculated on the previous frame. Occasionally this causes very very slight gaps between songs. I see no problem in the code logic, but why would this happen? Can anyone think of a way to rectify this logic?

// Calculate start time of next song.
var timeRemainingOnMainClip = (_activeAudio.clip.length - _activeAudio.time) / _activeAudio.pitch;
return AudioSettings.dspTime + timeRemainingOnMainClip;

We need the re-calculation and check in case you jumped position in the song or changed pitch of the song through code, which would change the projected start time of the next song.

Several successive projected start times:

  1. 245.889887247721
  2. 245.890220672607
  3. 245.889552739461

I am under the impression that the calculated value should not change at all if the pitch and position of the song weren’t messed with. So how can it be?

My first bet: You’re working with floating point numbers here. That’s normal behavior.

So you don’t think there’s a way to be more accurate about it so it would never have a tiny gap on occasion? That’s pretty lame…

Well, if this really is the issue, you’d have to work around this issue in another way.
The way good multimedia applications transition between songs is by “crossfading”. At a fixed time before the song ends (say 2 seconds) the currently played songs is faded out, while the next song is starting to play while being faded in. (Image)

If you want it to sound “gapless”, what you do is crossfading over a short timespan. What you need:

  • Two AudioSources (A: current track, B: next track)
  • A plays, B doesn’t
  • Shortly before the end (say 1 second) the volume of A starts to decrease
  • At the same time B starts playing with a volume of 0 which increases
  • Once A stops, its volume is kept at 0 and the next track is set as the clip
  • Once B nears its end, you repeat the same thing fading from B to A
  • Repeat

Start with linear interpolation first. If it doesn’t sound right, you can use “equal power crossfading” which is a bit more complicated but gives better results.

I hope this helps.

We already have crossfading with all those steps as an option. Some customers don’t want any crossfading at all. They just want to seamlessly connect a bunch of mini-songs with no gap and no crossfading. I’m beginning to wonder if it’s consistently possible though.

TheSniperFan: That solution won’t work. Crossfading needs to be done manually for every situation in order to guarantee there won’t be phase cancellation issues. jerotas is correct that it should be done by scheduling it on the DSP thread.

jerotas: The inaccuracy is most likely a result of that you’re making a calculation based on multiple numbers that are related to the current state of another thread. Either way, using _activeAudio.time isn’t going to be a working solution. The fact that you retrieve the remaining time on a given frame, you’re already taking it out of the DSP thread context. This negates the ability to calculate a value that can reliably be used for DSP time purposes. If the pitch didn’t change during playback, the next sound’s scheduled time would be solvable using the current clip’s length, pitch and the DSP time when it started playing. If you absolutely need realtime pitch changes, the complexity increases drastically. You should look into implementing your own sampler as a custom Audio Filter. It will let you implement a queuing system which give you the control you need.

Our software does not have pitch change stuff built in, I just didn’t want to not allow users to do that. If it’s necessary for glitchless gapless, I will make it so.

However, what if the active clip started playing not at the very beginning of the clip? Would your logic still work? Some song transition modes do not start at beginning of clip…

The info would only be safe if you have the exact DSP time when it started to play from that exact offset. I haven’t actually looked into that, but maybe Unity lets you set the start offset before calling PlayScheduled?

Making your own sampler has the advantage that it’ll make it easier to start from an offset, as well as pause and resume, and doing realtime pitch changes. A downside is that the software becomes much more vulnerable to the garbage collector, as garbage is collected across all threads, including the audio thread when you rely on OnAudioFiterRead. This will result in audio artifacts, especially in the editor which always produces a bit of garbage. However, your built applications will work super nicely, unless your own code produces lots of garbage of course.

I don’t believe you can start from an offset with the DSP.

We’re not going to build our own sampler. Thanks for the info though.

This is my thread, so I’m asking the question again. Is this still really not solvable, 5 years later? A customer just asked me about this issue and I looked up my thread. Since it’s mine, this is probably not a “necro”, right?

Hi Did you Find a solution eventually?

No, it’s unsolvable.

I was able to do it, seamlessly adjust end time with varying pitch value. I used current dsp time everytime pitch changed to calculate the end time. And I set dsp buffer size to best latency. With this approach it might fail rarely(around %5 of the time). Next I will try updating end time in audio filter read, which I am not sure will work or not.

Cool. If you are able to get our Master audio plugin to do it, we may be able to make it worth your while. We could also provide you a gratis copy if you don’t already own it. PM me if interested.

I explained what I did, setting DSP buffer size to best latency is the key here. There is a catch here, if dsp buffer size to best latency, you will hear crackling sound on editor, but it will work all fine in builds for some reason. I am still working on it, dont have much hope for a fool proof solution. I am trying to get the next best thing possible.