So, I looked at this and it’s more difficult than I thought it was.
Because
The DSPtime is not realtime, it only updates with the buffer.
The timeSamples are not accurate for reading, they also only update with the buffer. They are really only useful for seeking to a sample.
This is very limiting. It means I had to create my own timers to sync with DSPtime and emulate it, are you doing the same or am I missing something?
One thing to keep in mind. Getting the length of the clip is tricky if you are changing the pitch, because changing the pitch changes the length. If you query the clip length, it always gives you pitch 1 length, and if you try to / by a pitch it simply gives you the length of that clip if it was played entirely in that pitch which is obviously not what we want.
Anyway. I think I found two ways of doing this, and they both have drawbacks.
The drawback that is common to both of them is:
You can’t start a pitch shift after scheduling and still expect it to schedule accurately. That’s a given, though.
I am using doubles for everything that I can, I haven’t really checked to see if it makes a difference.
The first method:
Work out the length of the audioclip at pitch: 1
clipPitch1Length = (double)audSauce1.clip.samples / (double)audSauce1.clip.frequency;
Create a timer that fetches the DSPtime and then adds deltatime
currDSPTime = AudioSettings.dspTime;
....
currDSPTime += Time.deltaTime;
Create a clip timer that converts deltatime to pitch 1
clipPitch1PlayingTime += (Time.deltaTime * audSauce1.pitch);
Convert the difference into the current pitch to get the real time difference for schedule
if (((clipPitch1Length - clipPitch1PlayingTime) / audSauce1.pitch) <= bufferTime)
{
audSauce2.PlayScheduled (currDSPTime + ((clipPitch1Length - clipPitch1PlayingTime) / audSauce1.pitch));
Drawbacks of method 1:
Shifting needs to end by the time you schedule.
The second method:
Work out the length of each of the segments old pitch, pitch shift, new pitch.
You can actually still be shifting the pitch into the buffer zone with this, because we are working everything out before hand. You just can’t start a new pitch shift in the buffer zone.
//Work out the length of the segment before the pitch shift
estimateClipLengthTime = pitchshiftDelay / fromPitch;
//Work out the length of the segment where the pitch is shifting
//Because the shift is linear, we can average the length of the old and new pitch
estimateClipLengthTime += ((pitchShiftTime / fromPitch) + (pitchShiftTime / targetPitch)) / 2d;
//Work out the length of the segment after the pitch shift, using the new pitch
estimateClipLengthTime += (clipPitch1Length - pitchshiftDelay - pitchShiftTime) / targetPitch;
clipPlayingTime += Time.deltaTime;
...
if (estimateClipLengthTime - clipPlayingTime <= bufferTime)
{
audSauce2.PlayScheduled (currDSPTime + (estimateClipLengthTime - clipPlayingTime));
Drawbacks of method 2:
You must know the details of the pitch shift(s).
Pitch shifts must be linear
Your pitch shifts can’t overshoot the clip length, which makes sense anyway.
If you don’t want to calculate all the segments, but you do know all the pitch shifting information and just want to calculate the last segment, I think you can probably just work out an average pitch for the buffer segment that will give you the right time.
For example.
You hit your schedule audio condition.
Your pitch shift is still busy, and it will play to the end of the clip (to make the example easy)
Your pitch will shift from 1.1 to 1.2 by the end.
(1.1 + 1.2) / 2 is the average pitch.
That is how long it should take.
Method 1
audSauce2.PlayScheduled (currDSPTime + ((clipPitch1Length - clipPitch1PlayingTime) / averagePitch));
This assumes a linear pitch shift.