Merge pull request #259 from magumagu/dtk-rewrite

DTK rewrite
This commit is contained in:
Lioncash 2014-06-26 20:01:07 -04:00
commit 11d304ae29
7 changed files with 180 additions and 253 deletions

View File

@ -28,10 +28,7 @@ namespace AudioCommon
{
SoundStream *InitSoundStream(void *hWnd)
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
delete soundStream;
CMixer *mixer = new CMixer(AISampleRate, DACSampleRate, 48000);
CMixer *mixer = new CMixer(48000);
// TODO: possible memleak with mixer

View File

@ -6,6 +6,7 @@
#include "AudioCommon/Mixer.h"
#include "Common/Atomic.h"
#include "Common/CPUDetect.h"
#include "Common/MathUtil.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/AudioInterface.h"
@ -19,20 +20,8 @@
#endif
// Executed from sound stream thread
unsigned int CMixer::Mix(short* samples, unsigned int numSamples, bool consider_framelimit)
unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, bool consider_framelimit)
{
if (!samples)
return 0;
std::lock_guard<std::mutex> lk(m_csMixing);
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
{
// Silence
memset(samples, 0, numSamples * 4);
return numSamples;
}
unsigned int currentSample = 0;
// Cache access in non-volatile variable
@ -45,7 +34,7 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples, bool consider_
u32 indexR = Common::AtomicLoad(m_indexR);
u32 indexW = Common::AtomicLoad(m_indexW);
float numLeft = ((indexW - indexR) & INDEX_MASK) / 2;
float numLeft = (float)(((indexW - indexR) & INDEX_MASK) / 2);
m_numLeftI = (numLeft + m_numLeftI*(CONTROL_AVG-1)) / CONTROL_AVG;
float offset = (m_numLeftI - LOW_WATERMARK) * CONTROL_FACTOR;
if (offset > MAX_FREQ_SHIFT) offset = MAX_FREQ_SHIFT;
@ -56,29 +45,36 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples, bool consider_
//remember fractional offset
u32 framelimit = SConfig::GetInstance().m_Framelimit;
float aid_sample_rate = AudioInterface::GetAIDSampleRate() + offset;
float aid_sample_rate = m_input_sample_rate + offset;
if (consider_framelimit && framelimit > 2)
{
aid_sample_rate = aid_sample_rate * (framelimit - 1) * 5 / VideoInterface::TargetRefreshRate;
}
static u32 frac = 0;
const u32 ratio = (u32)( 65536.0f * aid_sample_rate / (float)m_sampleRate );
const u32 ratio = (u32)( 65536.0f * aid_sample_rate / (float)m_mixer->m_sampleRate );
if (ratio > 0x10000)
ERROR_LOG(AUDIO, "ratio out of range");
s32 lvolume = m_LVolume;
s32 rvolume = m_RVolume;
// TODO: consider a higher-quality resampling algorithm.
for (; currentSample < numSamples*2 && ((indexW-indexR) & INDEX_MASK) > 2; currentSample+=2) {
u32 indexR2 = indexR + 2; //next sample
s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); //current
s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); //next
int sampleL = ((l1 << 16) + (l2 - l1) * (u16)frac) >> 16;
sampleL = (sampleL * lvolume) >> 8;
sampleL += samples[currentSample + 1];
MathUtil::Clamp(&sampleL, -32767, 32767);
samples[currentSample+1] = sampleL;
s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); //current
s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); //next
int sampleR = ((r1 << 16) + (r2 - r1) * (u16)frac) >> 16;
sampleR = (sampleR * rvolume) >> 8;
sampleR += samples[currentSample];
MathUtil::Clamp(&sampleR, -32767, 32767);
samples[currentSample] = sampleR;
frac += ratio;
@ -87,36 +83,57 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples, bool consider_
}
// Padding
unsigned short s[2];
short s[2];
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]);
s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]);
for (; currentSample < numSamples*2; currentSample+=2)
s[0] = (s[0] * lvolume) >> 8;
s[1] = (s[1] * rvolume) >> 8;
for (; currentSample < numSamples * 2; currentSample += 2)
{
samples[currentSample] = s[0];
samples[currentSample+1] = s[1];
int sampleR = s[0] + samples[currentSample];
MathUtil::Clamp(&sampleR, -32767, 32767);
samples[currentSample] = sampleR;
int sampleL = s[1] + samples[currentSample + 1];
MathUtil::Clamp(&sampleL, -32767, 32767);
samples[currentSample + 1] = sampleL;
}
// Flush cached variable
Common::AtomicStore(m_indexR, indexR);
// Add the DTK Music
// Re-sampling is done inside
AudioInterface::Callback_GetStreaming(samples, numSamples, m_sampleRate);
if (m_logAudio)
g_wave_writer.AddStereoSamples(samples, numSamples);
return numSamples;
}
unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider_framelimit)
{
if (!samples)
return 0;
void CMixer::PushSamples(const short *samples, unsigned int num_samples)
std::lock_guard<std::mutex> lk(m_csMixing);
memset(samples, 0, num_samples * 2 * sizeof(short));
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
{
// Silence
return num_samples;
}
m_dma_mixer.Mix(samples, num_samples, consider_framelimit);
m_streaming_mixer.Mix(samples, num_samples, consider_framelimit);
if (m_logAudio)
g_wave_writer.AddStereoSamples(samples, num_samples);
return num_samples;
}
void CMixer::MixerFifo::PushSamples(const short *samples, unsigned int num_samples)
{
// Cache access in non-volatile variable
// indexR isn't allowed to cache in the audio throttling loop as it
// needs to get updates to not deadlock.
u32 indexW = Common::AtomicLoad(m_indexW);
if (m_throttle)
if (m_mixer->m_throttle)
{
// The auto throttle function. This loop will put a ceiling on the CPU MHz.
while (num_samples * 2 + ((indexW - Common::AtomicLoad(m_indexR)) & INDEX_MASK) >= MAX_SAMPLES * 2)
@ -155,3 +172,23 @@ void CMixer::PushSamples(const short *samples, unsigned int num_samples)
return;
}
void CMixer::PushSamples(const short *samples, unsigned int num_samples)
{
m_dma_mixer.PushSamples(samples, num_samples);
}
void CMixer::PushStreamingSamples(const short *samples, unsigned int num_samples)
{
m_streaming_mixer.PushSamples(samples, num_samples);
}
void CMixer::SetStreamingVolume(unsigned int lvolume, unsigned int rvolume)
{
m_streaming_mixer.SetVolume(lvolume, rvolume);
}
void CMixer::MixerFifo::SetVolume(unsigned int lvolume, unsigned int rvolume)
{
m_LVolume = lvolume + (lvolume >> 7);
m_RVolume = rvolume + (rvolume >> 7);
}

View File

@ -15,29 +15,21 @@
#define LOW_WATERMARK 1280 // 40 ms
#define MAX_FREQ_SHIFT 200 // per 32000 Hz
#define CONTROL_FACTOR 0.2 // in freq_shift per fifo size offset
#define CONTROL_FACTOR 0.2f // in freq_shift per fifo size offset
#define CONTROL_AVG 32
class CMixer {
public:
CMixer(unsigned int AISampleRate = 48000, unsigned int DACSampleRate = 48000, unsigned int BackendSampleRate = 32000)
: m_aiSampleRate(AISampleRate)
, m_dacSampleRate(DACSampleRate)
, m_bits(16)
, m_channels(2)
CMixer(unsigned int BackendSampleRate)
: m_dma_mixer(this, 32000)
, m_streaming_mixer(this, 48000)
, m_sampleRate(BackendSampleRate)
, m_logAudio(0)
, m_indexW(0)
, m_indexR(0)
, m_numLeftI(0.0f)
, m_throttle(false)
, m_speed(0)
{
// AyuanX: The internal (Core & DSP) sample rate is fixed at 32KHz
// So when AI/DAC sample rate differs than 32KHz, we have to do re-sampling
m_sampleRate = BackendSampleRate;
memset(m_buffer, 0, sizeof(m_buffer));
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized (AISampleRate:%i, DACSampleRate:%i)", AISampleRate, DACSampleRate);
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized");
}
virtual ~CMixer() {}
@ -47,7 +39,9 @@ public:
// Called from main thread
virtual void PushSamples(const short* samples, unsigned int num_samples);
unsigned int GetSampleRate() const {return m_sampleRate;}
virtual void PushStreamingSamples(const short* samples, unsigned int num_samples);
unsigned int GetSampleRate() const { return m_sampleRate; }
void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume);
void SetThrottle(bool use) { m_throttle = use;}
@ -87,11 +81,36 @@ public:
void UpdateSpeed(volatile float val) { m_speed = val; }
protected:
class MixerFifo {
public:
MixerFifo(CMixer *mixer, unsigned sample_rate)
: m_mixer(mixer)
, m_input_sample_rate(sample_rate)
, m_indexW(0)
, m_indexR(0)
, m_numLeftI(0.0f)
, m_LVolume(256)
, m_RVolume(256)
{
memset(m_buffer, 0, sizeof(m_buffer));
}
void PushSamples(const short* samples, unsigned int num_samples);
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
void SetVolume(unsigned int lvolume, unsigned int rvolume);
private:
CMixer *m_mixer;
unsigned m_input_sample_rate;
short m_buffer[MAX_SAMPLES * 2];
volatile u32 m_indexW;
volatile u32 m_indexR;
// Volume ranges from 0-256
volatile s32 m_LVolume;
volatile s32 m_RVolume;
float m_numLeftI;
};
MixerFifo m_dma_mixer;
MixerFifo m_streaming_mixer;
unsigned int m_sampleRate;
unsigned int m_aiSampleRate;
unsigned int m_dacSampleRate;
int m_bits;
int m_channels;
WaveFileWriter g_wave_writer;
@ -99,14 +118,7 @@ protected:
bool m_throttle;
short m_buffer[MAX_SAMPLES * 2];
volatile u32 m_indexW;
volatile u32 m_indexR;
std::mutex m_csMixing;
float m_numLeftI;
volatile float m_speed; // Current rate of the emulation (1.0 = 100% speed)
private:
};

View File

@ -50,6 +50,8 @@ This file mainly deals with the [Drive I/F], however [AIDFR] controls
TODO maybe the files should be merged?
*/
#include "AudioCommon/AudioCommon.h"
#include "Common/Common.h"
#include "Common/MathUtil.h"
@ -59,7 +61,6 @@ This file mainly deals with the [Drive I/F], however [AIDFR] controls
#include "Core/HW/DVDInterface.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/StreamADPCM.h"
#include "Core/HW/SystemTimers.h"
#include "Core/PowerPC/PowerPC.h"
@ -145,8 +146,7 @@ void DoState(PointerWrap &p)
static void GenerateAudioInterrupt();
static void UpdateInterrupts();
static void IncreaseSampleCount(const u32 _uAmount);
void ReadStreamBlock(s16* _pPCM);
u64 GetAIPeriod();
static u64 GetAIPeriod();
int et_AI;
void Init()
@ -234,7 +234,10 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
mmio->Register(base | AI_VOLUME_REGISTER,
MMIO::DirectRead<u32>(&m_Volume.hex),
MMIO::DirectWrite<u32>(&m_Volume.hex)
MMIO::ComplexWrite<u32>([](u32, u32 val) {
m_Volume.hex = val;
soundStream->GetMixer()->SetStreamingVolume(m_Volume.left, m_Volume.right);
})
);
mmio->Register(base | AI_SAMPLE_COUNTER,
@ -272,133 +275,6 @@ void GenerateAISInterrupt()
GenerateAudioInterrupt();
}
void Callback_GetSampleRate(unsigned int &_AISampleRate, unsigned int &_DACSampleRate)
{
_AISampleRate = g_AISSampleRate;
_DACSampleRate = g_AIDSampleRate;
}
// Callback for the disc streaming
// WARNING - called from audio thread
unsigned int Callback_GetStreaming(short* _pDestBuffer, unsigned int _numSamples, unsigned int _sampleRate)
{
if (m_Control.PSTAT && !CCPU::IsStepping())
{
static int pos = 0;
static short pcm[NGCADPCM::SAMPLES_PER_BLOCK*2];
const int lvolume = m_Volume.left;
const int rvolume = m_Volume.right;
if (g_AISSampleRate == 48000 && _sampleRate == 32000)
{
_dbg_assert_msg_(AUDIO_INTERFACE, !(_numSamples & 1), "Number of Samples: %i must be even!", _numSamples);
_numSamples = _numSamples * 3 / 2;
}
int pcm_l = 0, pcm_r = 0;
for (unsigned int i = 0; i < _numSamples; i++)
{
if (pos == 0)
ReadStreamBlock(pcm);
if (g_AISSampleRate == 48000 && _sampleRate == 32000) //downsample 48>32
{
if (i % 3)
{
pcm_l = (((pcm_l + (int)pcm[pos*2]) / 2 * lvolume) >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_l, -32767, 32767);
*_pDestBuffer++ = pcm_l;
pcm_r = (((pcm_r + (int)pcm[pos*2+1]) / 2 * rvolume) >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_r, -32767, 32767);
*_pDestBuffer++ = pcm_r;
}
pcm_l = pcm[pos*2];
pcm_r = pcm[pos*2+1];
pos++;
}
else if (g_AISSampleRate == 32000 && _sampleRate == 48000) //upsample 32>48
{
//starts with one sample of 0
const u32 ratio = (u32)( 65536.0f * 32000.0f / (float)_sampleRate );
static u32 frac = 0;
static s16 l1 = 0;
static s16 l2 = 0;
if (frac >= 0x10000 || frac == 0)
{
frac &= 0xffff;
l1 = l2; //current
l2 = pcm[pos * 2]; //next
}
pcm_l = ((l1 << 16) + (l2 - l1) * (u16)frac) >> 16;
pcm_r = ((l1 << 16) + (l2 - l1) * (u16)frac) >> 16;
pcm_l = (pcm_l * lvolume >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_l, -32767, 32767);
*_pDestBuffer++ = pcm_l;
pcm_r = (pcm_r * lvolume >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_r, -32767, 32767);
*_pDestBuffer++ = pcm_r;
frac += ratio;
pos += frac >> 16;
}
else //1:1 no resampling
{
pcm_l = (((int)pcm[pos*2] * lvolume) >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_l, -32767, 32767);
*_pDestBuffer++ = pcm_l;
pcm_r = (((int)pcm[pos*2+1] * rvolume) >> 8) + (int)(*_pDestBuffer);
MathUtil::Clamp(&pcm_r, -32767, 32767);
*_pDestBuffer++ = pcm_r;
pos++;
}
if (pos == NGCADPCM::SAMPLES_PER_BLOCK)
pos = 0;
}
}
else
{
// Don't overwrite existed sample data
/*
for (unsigned int i = 0; i < _numSamples * 2; i++)
{
_pDestBuffer[i] = 0; //silence!
}
*/
}
return _numSamples;
}
// WARNING - called from audio thread
void ReadStreamBlock(s16 *_pPCM)
{
u8 tempADPCM[NGCADPCM::ONE_BLOCK_SIZE];
if (DVDInterface::DVDReadADPCM(tempADPCM, NGCADPCM::ONE_BLOCK_SIZE))
{
NGCADPCM::DecodeBlock(_pPCM, tempADPCM);
}
else
{
memset(_pPCM, 0, NGCADPCM::SAMPLES_PER_BLOCK*2);
}
// our whole streaming code is "faked" ... so it shouldn't increase the sample counter
// streaming will never work correctly this way, but at least the program will think all is alright.
}
static void IncreaseSampleCount(const u32 _iAmount)
{
if (m_Control.PSTAT)

View File

@ -22,10 +22,6 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
void Update(u64 userdata, int cyclesLate);
// Called by DSP emulator
void Callback_GetSampleRate(unsigned int &_AISampleRate, unsigned int &_DACSampleRate);
unsigned int Callback_GetStreaming(short* _pDestBuffer, unsigned int _numSamples, unsigned int _sampleRate = 48000);
// Get the audio rates (48000 or 32000 only)
unsigned int GetAIDSampleRate();

View File

@ -4,6 +4,8 @@
#include <cinttypes>
#include "AudioCommon/AudioCommon.h"
#include "Common/ChunkFile.h"
#include "Common/Common.h"
#include "Common/Thread.h"
@ -208,6 +210,7 @@ u32 g_ErrorCode = 0;
bool g_bDiscInside = false;
bool g_bStream = false;
int tc = 0;
int dtk = 0;
static u64 g_last_read_offset;
static u64 g_last_read_time;
@ -215,10 +218,6 @@ static u64 g_last_read_time;
// GC-AM only
static unsigned char media_buffer[0x40];
// Needed because data and streaming audio access needs to be managed by the "drive"
// (both requests can happen at the same time, audio takes precedence)
static std::mutex dvdread_section;
static int ejectDisc;
static int insertDisc;
@ -262,6 +261,63 @@ void TransferComplete(u64 userdata, int cyclesLate)
FinishExecuteRead();
}
static u32 ProcessDTKSamples(short *tempPCM, u32 num_samples)
{
u32 samples_processed = 0;
do
{
if (AudioPos >= CurrentStart + CurrentLength)
{
AudioPos = LoopStart;
CurrentStart = LoopStart;
CurrentLength = LoopLength;
NGCADPCM::InitFilter();
AudioInterface::GenerateAISInterrupt();
// If there isn't any audio to stream, stop streaming.
if (AudioPos >= CurrentStart + CurrentLength)
{
g_bStream = false;
}
break;
}
u8 tempADPCM[NGCADPCM::ONE_BLOCK_SIZE];
// TODO: What if we can't read from AudioPos?
VolumeHandler::ReadToPtr(tempADPCM, AudioPos, sizeof(tempADPCM));
AudioPos += sizeof(tempADPCM);
NGCADPCM::DecodeBlock(tempPCM + samples_processed * 2, tempADPCM);
samples_processed += NGCADPCM::SAMPLES_PER_BLOCK;
} while (samples_processed < num_samples);
for (unsigned i = 0; i < samples_processed * 2; ++i)
{
// TODO: Fix the mixer so it can accept non-byte-swapped samples.
tempPCM[i] = Common::swap16(tempPCM[i]);
}
return samples_processed;
}
void DTKStreamingCallback(u64 userdata, int cyclesLate)
{
// Send audio to the mixer.
static const int NUM_SAMPLES = 48000 / 2000 * 7; // 3.5ms of 48kHz samples
short tempPCM[NUM_SAMPLES * 2];
unsigned samples_processed;
if (g_bStream)
{
samples_processed = ProcessDTKSamples(tempPCM, NUM_SAMPLES);
}
else
{
memset(tempPCM, 0, sizeof(tempPCM));
samples_processed = NUM_SAMPLES;
}
soundStream->GetMixer()->PushStreamingSamples(tempPCM, samples_processed);
int ticks_to_dtk = int(SystemTimers::GetTicksPerSecond() * u64(samples_processed) / 48000);
CoreTiming::ScheduleEvent(ticks_to_dtk - cyclesLate, dtk);
}
void Init()
{
m_DISR.Hex = 0;
@ -288,6 +344,9 @@ void Init()
insertDisc = CoreTiming::RegisterEvent("InsertDisc", InsertDiscCallback);
tc = CoreTiming::RegisterEvent("TransferComplete", TransferComplete);
dtk = CoreTiming::RegisterEvent("StreamingTimer", DTKStreamingCallback);
CoreTiming::ScheduleEvent(0, dtk);
}
void Shutdown()
@ -369,57 +428,9 @@ void ClearCoverInterrupt()
bool DVDRead(u32 _iDVDOffset, u32 _iRamAddress, u32 _iLength)
{
// We won't need the crit sec when DTK streaming has been rewritten correctly.
std::lock_guard<std::mutex> lk(dvdread_section);
return VolumeHandler::ReadToPtr(Memory::GetPointer(_iRamAddress), _iDVDOffset, _iLength);
}
bool DVDReadADPCM(u8* _pDestBuffer, u32 _iNumSamples)
{
_iNumSamples &= ~31;
if (AudioPos == 0)
{
memset(_pDestBuffer, 0, _iNumSamples); // probably __AI_SRC_INIT :P
}
else
{
std::lock_guard<std::mutex> lk(dvdread_section);
VolumeHandler::ReadToPtr(_pDestBuffer, AudioPos, _iNumSamples);
}
// loop check
if (g_bStream)
{
AudioPos += _iNumSamples;
if (AudioPos >= CurrentStart + CurrentLength)
{
if (LoopStart == 0)
{
AudioPos = 0;
CurrentStart = 0;
CurrentLength = 0;
}
else
{
AudioPos = LoopStart;
CurrentStart = LoopStart;
CurrentLength = LoopLength;
}
NGCADPCM::InitFilter();
AudioInterface::GenerateAISInterrupt();
}
//WARN_LOG(DVDINTERFACE,"ReadADPCM");
return true;
}
else
{
return false;
}
}
void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
{
mmio->Register(base | DI_STATUS_REGISTER,
@ -927,7 +938,6 @@ void ExecuteCommand()
CurrentStart = pos;
CurrentLength = length;
NGCADPCM::InitFilter();
g_bStream = true;
}
LoopStart = pos;
@ -990,11 +1000,13 @@ void ExecuteCommand()
case DVDLowAudioBufferConfig:
if (m_DICMDBUF[0].CMDBYTE1 == 1)
{
// TODO: What is this actually supposed to do?
g_bStream = true;
WARN_LOG(DVDINTERFACE, "(Audio): Audio enabled");
}
else
{
// TODO: What is this actually supposed to do?
g_bStream = false;
WARN_LOG(DVDINTERFACE, "(Audio): Audio disabled");
}

View File

@ -33,11 +33,8 @@ void ClearCoverInterrupt();
// DVD Access Functions
bool DVDRead(u32 _iDVDOffset, u32 _iRamAddress, u32 _iLength);
// For AudioInterface
bool DVDReadADPCM(u8* _pDestBuffer, u32 _iNumSamples);
extern bool g_bStream;
// Not sure about endianness here. I'll just name them like this...
enum DIErrorLow
{