/* Copyright 2009 DeSmuME team This file is part of DeSmuME DeSmuME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. DeSmuME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DeSmuME; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "../types.h" #include "metaspu.h" //for pcsx2 method //(havent bothered to get it compiling in gcc yet) #ifdef _MSC_VER #include "SndOut.h" #endif class ZeromusSynchronizer : public ISynchronizingAudioBuffer { public: ZeromusSynchronizer() : mixqueue_go(false), #ifdef NDEBUG adjustobuf(200, 1000) #else adjustobuf(22000, 44000) #endif { } bool mixqueue_go; virtual void enqueue_samples(int16_t *buf, int samples_provided) { for (int i = 0; i < samples_provided; ++i) { int16_t left = *buf++; int16_t right = *buf++; this->adjustobuf.enqueue(left, right); } } // returns the number of samples actually supplied, which may not match the number requested virtual int output_samples(int16_t *buf, int samples_requested) { int done = 0; if (!this->mixqueue_go) { if (this->adjustobuf.size > 200) this->mixqueue_go = true; } else { for (int i = 0; i < samples_requested; ++i) { if (!this->adjustobuf.size) { this->mixqueue_go = false; break; } ++done; int16_t left, right; this->adjustobuf.dequeue(left, right); *buf++ = left; *buf++ = right; } } return done; } private: class Adjustobuf { public: Adjustobuf(int _minLatency, int _maxLatency) : minLatency(_minLatency), maxLatency(_maxLatency), size(0) { this->rollingTotalSize = 0; this->targetLatency = (this->maxLatency + this->minLatency) / 2; this->rate = 1.0f; this->cursor = 0.0f; this->curr[0] = this->curr[1] = 0; this->kAverageSize = 80000; } float rate, cursor; int minLatency, targetLatency, maxLatency; std::queue buffer; int size; int16_t curr[2]; std::queue statsHistory; void enqueue(int16_t left, int16_t right) { this->buffer.push(left); this->buffer.push(right); ++this->size; } int64_t rollingTotalSize; uint32_t kAverageSize; void addStatistic() { this->statsHistory.push(this->size); this->rollingTotalSize += this->size; if (this->statsHistory.size() > this->kAverageSize) { this->rollingTotalSize -= this->statsHistory.front(); this->statsHistory.pop(); float averageSize = static_cast(rollingTotalSize / kAverageSize); //static int ctr=0; ctr++; if((ctr&127)==0) printf("avg size: %f curr size: %d rate: %f\n",averageSize,size,rate); { float targetRate; if (averageSize < this->targetLatency) targetRate = 1.0f - (this->targetLatency - averageSize) / this->kAverageSize; else if (averageSize > this->targetLatency) targetRate = 1.0f + (averageSize - this->targetLatency) / this->kAverageSize; else targetRate = 1.0f; //rate = moveValueTowards(rate,targetRate,0.001f); this->rate = targetRate; } } } void dequeue(int16_t &left, int16_t &right) { left = right = 0; this->addStatistic(); if (!this->size) return; this->cursor += this->rate; while (this->cursor > 1.0f) { this->cursor -= 1.0f; if (this->size > 0) { this->curr[0] = this->buffer.front(); this->buffer.pop(); this->curr[1] = this->buffer.front(); this->buffer.pop(); --this->size; } } left = this->curr[0]; right = this->curr[1]; } } adjustobuf; }; class NitsujaSynchronizer : public ISynchronizingAudioBuffer { private: struct ssamp { int16_t l, r; ssamp() { } ssamp(int16_t ll, int16_t rr) : l(ll), r(rr) { } }; std::vector sampleQueue; // returns values going between 0 and y-1 in a saw wave pattern, based on x static int pingpong(int x, int y) { x %= 2 * y; if (x >= y) x = 2 * y - x - 1; return x; // in case we want to switch to odd buffer sizes for more sharpness //x %= 2*(y-1); //if(x >= y) // x = 2*(y-1) - x; //return x; } static ssamp crossfade(const ssamp &lhs, const 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 = (static_cast(lhs.l) * outNum + static_cast(rhs.l) * inNum) / denom; int rrv = (static_cast(lhs.r) * outNum + static_cast(rhs.r) * inNum) / denom; return ssamp(lrv, rrv); } static void emit_sample(int16_t *&outbuf, const ssamp &sample) { *outbuf++ = sample.l; *outbuf++ = sample.r; } static void emit_samples(int16_t *&outbuf, const ssamp *samplebuf, int samples) { for (int i = 0; i < samples; ++i) NitsujaSynchronizer::emit_sample(outbuf, samplebuf[i]); } public: NitsujaSynchronizer() { } virtual void enqueue_samples(int16_t *buf, int samples_provided) { for (int i = 0; i < samples_provided; ++i) { this->sampleQueue.push_back(ssamp(buf[0], buf[1])); buf += 2; } } virtual int output_samples(int16_t *buf, int samples_requested) { int audiosize = samples_requested; int queued = this->sampleQueue.size(); // 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 = this->crossfade(this->sampleQueue[i], this->sampleQueue[j], i, 0, audiosize); this->emit_sample(buf, 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; static const int worstdiff = 99999999; int beststartdiff = worstdiff; int bestenddiff = worstdiff; for(int i = 0; i < 128; i += 2) { int diff = std::abs(this->sampleQueue[i].l - this->sampleQueue[i + 1].l) + std::abs(this->sampleQueue[i].r - this->sampleQueue[i + 1].r); if (diff < beststartdiff) { beststartdiff = diff; beststart = i; } } for (int i = queued - 3; i > queued - 3 - 128; i -= 2) { int diff = std::abs(this->sampleQueue[i].l - this->sampleQueue[i + 1].l) + std::abs(this->sampleQueue[i].r - this->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 <= samples_requested) oksize += queued * 2; audiosize = oksize; for (int x = 0; x < beststart; ++x) this->emit_sample(buf, this->sampleQueue[x]); this->sampleQueue.erase(this->sampleQueue.begin(), this->sampleQueue.begin() + beststart); } 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 = std::abs(this->pingpong(midpointX - midpointXOffset, queued) - midpointY) - midpointXOffset; if (((a > 0) != (prevA > 0) || (a < 0) != (prevA < 0)) && prevA != 999999) { if ((a + prevA) & 1) // 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) - this->pingpong(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 = this->pingpong(x, queued); this->emit_sample(buf, this->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) this->emit_sample(buf, this->sampleQueue[y]); for (int x = midpointX; x < rightMidpointX; ++x, y += dyMidRight) this->emit_sample(buf, this->sampleQueue[y]); // output the end of the queued sound (section "C") for (int x = rightMidpointX; x < audiosize; ++x) { int i = (queued - 1) - this->pingpong(audiosize - 1 - x + queued * 2, queued); this->emit_sample(buf, sampleQueue[i]); } for (int x = 0; x < extraAtEnd; ++x) { int i = queued + x; this->emit_sample(buf, this->sampleQueue[i]); } queued += extraAtEnd; audiosize += beststart + extraAtEnd; } //end else this->sampleQueue.erase(this->sampleQueue.begin(), this->sampleQueue.begin() + queued); 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) { this->emit_samples(buf, &this->sampleQueue[0], queued); this->sampleQueue.erase(this->sampleQueue.begin(), this->sampleQueue.begin() + queued); return queued; } else { this->emit_samples(buf, &this->sampleQueue[0], audiosize); this->sampleQueue.erase(this->sampleQueue.begin(), this->sampleQueue.begin() + audiosize); return audiosize; } } //end normal speed } //end if there is any work to do else return 0; } //output_samples }; //NitsujaSynchronizer #ifdef _MSC_VER class PCSX2Synchronizer : public ISynchronizingAudioBuffer { public: std::queue readySamples; PCSX2Synchronizer() { SndBuffer::Init(); } virtual void enqueue_samples(int16_t *buf, int samples_provided) { for (int i = 0; i < samples_provided; ++i) { auto so32 = StereoOut32(buf[0], buf[1]); SndBuffer::Write(so32); buf += 2; } } virtual int output_samples(int16_t *buf, int samples_requested) { for (int i = 0; i < samples_requested; ++i) { if (!this->readySamples.size()) { //SndOutPacketSize StereoOut16 temp[SndOutPacketSize * 2]; SndBuffer::ReadSamples(temp); for (int i = 0; i < SndOutPacketSize; ++i) { this->readySamples.push(temp[i].Left); this->readySamples.push(temp[i].Right); } } *buf++ = this->readySamples.front(); this->readySamples.pop(); *buf++ = this->readySamples.front(); this->readySamples.pop(); } return samples_requested; } }; #endif ISynchronizingAudioBuffer *metaspu_construct(ESynchMethod method) { switch(method) { case ESynchMethod_N: return new NitsujaSynchronizer(); case ESynchMethod_Z: return new ZeromusSynchronizer(); #ifdef _MSC_VER case ESynchMethod_P: return new PCSX2Synchronizer(); #endif default: return nullptr; } }