/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
* Developed and maintained by the Pcsx2 Development Team.
*
* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
*
* SPU2-X is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SPU2-X 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SPU2-X. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <algorithm>
#include "../types.h"
struct StereoOut16;
struct StereoOutFloat;
struct StereoOut32
{
static StereoOut32 Empty;
int32_t Left, Right;
StereoOut32() : Left(0), Right(0)
{
}
StereoOut32(int32_t left, int32_t right) : Left(left), Right(right)
{
}
StereoOut32(const StereoOut16 &src);
explicit StereoOut32(const StereoOutFloat &src);
StereoOut16 DownSample() const;
StereoOut32 operator+(const StereoOut32 &right) const
{
return StereoOut32(this->Left + right.Left, this->Right + right.Right);
}
StereoOut32 operator/(int src) const
{
return StereoOut32(this->Left / src, this->Right / src);
}
};
// Number of stereo samples per SndOut block.
// All drivers must work in units of this size when communicating with
// SndOut.
const int SndOutPacketSize = 512;
// edit - zeromus 23-oct-2009
// this is hardcoded differently for metaspu
const int SndOutVolumeShift = 0;
// Samplerate of the SPU2. For accurate playback we need to match this
// exactly. Trying to scale samplerates and maintain SPU2's Ts timing accuracy
// is too problematic. :)
// this is hardcoded differently for metaspu
// edit - zeromus 23-oct-2009
//const int SampleRate = 48000;
//const int SampleRate = 44100;
// edit - nitsuja: make it use the global sample rate define
#include "../SPU.h"
const int SampleRate = DESMUME_SAMPLE_RATE;
struct StereoOut16
{
int16_t Left, Right;
StereoOut16() : Left(0), Right(0)
{
}
StereoOut16(const StereoOut32 &src) : Left(src.Left & 0xFFFF), Right(src.Right & 0xFFFF)
{
}
StereoOut16(int16_t left, int16_t right) : Left(left), Right(right)
{
}
void ResampleFrom(const StereoOut32 &src)
{
// Use StereoOut32's built in conversion
*this = src.DownSample();
}
};
struct StereoOutFloat
{
float Left, Right;
StereoOutFloat() : Left(0.0f), Right(0.0f)
{
}
explicit StereoOutFloat(const StereoOut32 &src) : Left(src.Left / 2147483647.0f), Right(src.Right / 2147483647.0f)
{
}
explicit StereoOutFloat(int32_t left, int32_t right) : Left(left / 2147483647.0f), Right(right / 2147483647.0f)
{
}
StereoOutFloat(float left, float right) : Left(left), Right(right)
{
}
};
// Developer Note: This is a static class only (all static members).
class SndBuffer
{
private:
static bool m_underrun_freeze;
static int32_t m_predictData;
static float lastPct;
static std::unique_ptr<StereoOut32[]> sndTempBuffer;
static int sndTempProgress;
static std::unique_ptr<StereoOut32[]> m_buffer;
static int32_t m_size;
static int32_t m_rpos;
static int32_t m_wpos;
static int32_t m_data;
static float lastEmergencyAdj;
static float cTempo;
static float eTempo;
static int freezeTempo;
static void _InitFail();
static void _WriteSamples(StereoOut32 *bData, int nSamples);
static bool CheckUnderrunStatus(int &nSamples, int &quietSampleCount);
static void soundtouchInit();
static void timeStretchWrite();
static void timeStretchUnderrun();
static int32_t timeStretchOverrun();
static void PredictDataWrite(int samples);
static float GetStatusPct();
static void UpdateTempoChange();
public:
static void Init();
static void Write(const StereoOut32 &Sample);
// Note: When using with 32 bit output buffers, the user of this function is responsible
// for shifting the values to where they need to be manually. The fixed point depth of
// the sample output is determined by the SndOutVolumeShift, which is the number of bits
// to shift right to get a 16 bit result.
template<typename T> static void ReadSamples(T *bData)
{
int nSamples = SndOutPacketSize;
// Problem:
// If the SPU2 gets even the least bit out of sync with the SndOut device,
// the readpos of the circular buffer will overtake the writepos,
// leading to a prolonged period of hopscotching read/write accesses (ie,
// lots of staticy crap sound for several seconds).
//
// Fix:
// If the read position overtakes the write position, abort the
// transfer immediately and force the SndOut driver to wait until
// the read buffer has filled up again before proceeding.
// This will cause one brief hiccup that can never exceed the user's
// set buffer length in duration.
int quietSamples;
if (CheckUnderrunStatus(nSamples, quietSamples))
{
assert(nSamples <= SndOutPacketSize);
// [Air] [TODO]: This loop is probably a candidate for SSE2 optimization.
int endPos = m_rpos + nSamples;
int secondCopyLen = endPos - m_size;
const StereoOut32 *rposbuffer = &m_buffer[m_rpos];
m_data -= nSamples;
if (secondCopyLen > 0)
{
nSamples -= secondCopyLen;
for (int i = 0; i < secondCopyLen; ++i)
bData[nSamples + i].ResampleFrom(m_buffer[i]);
m_rpos = secondCopyLen;
}
else
m_rpos += nSamples;
for (int i = 0; i < nSamples; ++i)
bData[i].ResampleFrom(rposbuffer[i]);
}
// If quietSamples != 0 it means we have an underrun...
// Let's just dull out some silence, because that's usually the least
// painful way of dealing with underruns:
memset(bData, 0, quietSamples * sizeof(T));
}
};