private static Ssamp Crossfade(Ssamp lhs, Ssamp rhs, int cur, int start, int end) { if (cur <= start) { return(lhs); } if (cur >= end) { return(rhs); } // in case we want sine wave interpolation instead of linear here ////float ang = 3.14159f * (float)(cur - start) / (float)(end - start); ////cur = start + (int)((1-cosf(ang))*0.5f * (end - start)); int inNum = cur - start; int outNum = end - cur; int denom = end - start; int lrv = ((lhs.L * outNum) + (rhs.L * inNum)) / denom; int rrv = ((lhs.R * outNum) + (rhs.R * inNum)) / denom; return(new Ssamp((short)lrv, (short)rrv)); }
public int OutputSamples(short[] buf, int samplesRequested) { Console.WriteLine("{0} {1}", samplesRequested, _sampleQueue.Count); // add this line int bufcursor = 0; int audiosize = samplesRequested; int queued = _sampleQueue.Count; // I am too lazy to deal with odd numbers audiosize &= ~1; queued &= ~1; if (queued > 0x200 && audiosize > 0) // is there any work to do? { // are we going at normal speed? // or more precisely, are the input and output queues/buffers of similar size? if (queued > 900 || audiosize > queued * 2) { // not normal speed. we have to resample it somehow in this case. if (audiosize <= queued) { // fast forward speed // this is the easy case, just crossfade it and it sounds ok for (int i = 0; i < audiosize; i++) { int j = i + queued - audiosize; Ssamp outsamp = Crossfade(_sampleQueue[i], _sampleQueue[j], i, 0, audiosize); EmitSample(buf, ref bufcursor, outsamp); } } else { // slow motion speed // here we take a very different approach, // instead of crossfading it, we select a single sample from the queue // and make sure that the index we use to select a sample is constantly moving // and that it starts at the first sample in the queue and ends on the last one. // // hopefully the index doesn't move discontinuously or we'll get slight crackling // (there might still be a minor bug here that causes this occasionally) // // here's a diagram of how the index we sample from moves: // // queued (this axis represents the index we sample from. the top means the end of the queue) // ^ // | --> audiosize (this axis represents the output index we write to, right meaning forward in output time/position) // | A C C end // A A B C C C // A A A B C C C // A A A B C C // A A C // start // // yes, this means we are spending some stretches of time playing the sound backwards, // but the stretches are short enough that this doesn't sound weird. // this lets us avoid most crackling problems due to the endpoints matching up. // first calculate a shorter-than-full window // that has minimal slope at the endpoints // (to further reduce crackling, especially in sine waves) int beststart = 0, extraAtEnd = 0; { int bestend = queued; const int Worstdiff = 99999999; int beststartdiff = Worstdiff; int bestenddiff = Worstdiff; for (int i = 0; i < 128; i += 2) { int diff = Abs(_sampleQueue[i].L - _sampleQueue[i + 1].L) + Abs(_sampleQueue[i].R - _sampleQueue[i + 1].R); if (diff < beststartdiff) { beststartdiff = diff; beststart = i; } } for (int i = queued - 3; i > queued - 3 - 128; i -= 2) { int diff = Abs(_sampleQueue[i].L - _sampleQueue[i + 1].L) + Abs(_sampleQueue[i].R - _sampleQueue[i + 1].R); if (diff < bestenddiff) { bestenddiff = diff; bestend = i + 1; } } extraAtEnd = queued - bestend; queued = bestend - beststart; int oksize = queued; while (oksize + (queued * 2) + beststart + extraAtEnd <= samplesRequested) { oksize += queued * 2; } audiosize = oksize; for (int x = 0; x < beststart; x++) { EmitSample(buf, ref bufcursor, _sampleQueue[x]); } // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + beststart); _sampleQueue.RemoveRange(0, beststart); // zero 08-nov-2010: did i do this right? } int midpointX = audiosize >> 1; int midpointY = queued >> 1; // all we need to do here is calculate the X position of the leftmost "B" in the above diagram. // TODO: we should calculate it with a simple equation like // midpointXOffset = min(something,somethingElse); // but it's a little difficult to work it out exactly // so here's a stupid search for the value for now: int prevA = 999999; int midpointXOffset = queued / 2; while (true) { int a = Abs(Pingpong(midpointX - midpointXOffset, queued) - midpointY) - midpointXOffset; if (((a > 0) != (prevA > 0) || (a < 0) != (prevA < 0)) && prevA != 999999) { if (((a + prevA) & 1) != 0) // there's some sort of off-by-one problem with this search since we're moving diagonally... { midpointXOffset++; // but this fixes it most of the time... } break; // found it } prevA = a; midpointXOffset--; if (midpointXOffset < 0) { midpointXOffset = 0; break; // failed to find it. the two sides probably meet exactly in the center. } } int leftMidpointX = midpointX - midpointXOffset; int rightMidpointX = midpointX + midpointXOffset; int leftMidpointY = Pingpong(leftMidpointX, queued); int rightMidpointY = (queued - 1) - Pingpong((int)audiosize - 1 - rightMidpointX + (queued * 2), queued); // output the left almost-half of the sound (section "A") for (int x = 0; x < leftMidpointX; x++) { int i = Pingpong(x, queued); EmitSample(buf, ref bufcursor, _sampleQueue[i]); } // output the middle stretch (section "B") int y = leftMidpointY; int dyMidLeft = (leftMidpointY < midpointY) ? 1 : -1; int dyMidRight = (rightMidpointY > midpointY) ? 1 : -1; for (int x = leftMidpointX; x < midpointX; x++, y += dyMidLeft) { EmitSample(buf, ref bufcursor, _sampleQueue[y]); } for (int x = midpointX; x < rightMidpointX; x++, y += dyMidRight) { EmitSample(buf, ref bufcursor, _sampleQueue[y]); } // output the end of the queued sound (section "C") for (int x = rightMidpointX; x < audiosize; x++) { int i = (queued - 1) - Pingpong((int)audiosize - 1 - x + (queued * 2), queued); EmitSample(buf, ref bufcursor, _sampleQueue[i]); } for (int x = 0; x < extraAtEnd; x++) { int i = queued + x; EmitSample(buf, ref bufcursor, _sampleQueue[i]); } queued += extraAtEnd; audiosize += beststart + extraAtEnd; } // end else // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + queued); _sampleQueue.RemoveRange(0, queued); // zero 08-nov-2010: did i do this right? return(audiosize); } else { // normal speed // just output the samples straightforwardly. // // at almost-full speeds (like 50/60 FPS) // what will happen is that we rapidly fluctuate between entering this branch // and entering the "slow motion speed" branch above. // but that's ok! because all of these branches sound similar enough that we can get away with it. // so the two cases actually complement each other. if (audiosize >= queued) { EmitSamples(buf, ref bufcursor, _sampleQueue.ToArray(), 0, queued); // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin() + queued); _sampleQueue.RemoveRange(0, queued); // zero 08-nov-2010: did i do this right? return(queued); } else { EmitSamples(buf, ref bufcursor, _sampleQueue.ToArray(), 0, audiosize); // sampleQueue.erase(sampleQueue.begin(), sampleQueue.begin()+audiosize); _sampleQueue.RemoveRange(0, audiosize); // zero 08-nov-2010: did i do this right? return(audiosize); } } // end normal speed } // end if there is any work to do else { return(0); } } // output_samples
private static void EmitSample(short[] outbuf, ref int cursor, Ssamp sample) { outbuf[cursor++] = sample.L; outbuf[cursor++] = sample.R; }