make Audio Interface more clear. Should have no behavioral changes. The comment block in AudioInterface.cpp lays out how real hardware is...

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@6427 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
Shawn Hoffman 2010-11-16 20:36:57 +00:00
parent 4f81997c14
commit 9dd60c6115
5 changed files with 176 additions and 166 deletions

View File

@ -15,12 +15,40 @@
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
// This file is ONLY about disc streaming. It's a bit unfortunately named.
// For the rest of the audio stuff, including the "real" AI, see DSP.cpp/h.
/*
Here is a nice ascii overview of audio flow affected by this file:
// AI disc streaming is handled completely separately from the rest of the
// audio processing. In short, it simply streams audio directly from disc
// out through the speakers.
(RAM)---->[AI FIFO]---->[SRC]---->[Mixer]---->[DAC]---->(Speakers)
^
|
[L/R Volume]
\
(DVD)---->[Drive I/F]---->[SRC]---->[Counter]
Notes:
Output at "48KHz" is actually 48043Hz.
Sample counter counts streaming stereo samples after upsampling.
[DAC] causes [AI I/F] to read from RAM at rate selected by AIDFR.
Each [SRC] will upsample a 32KHz source, or pass through the 48KHz
source. The [Mixer]/[DAC] only operate at 48KHz.
AIS == disc streaming == DTK(Disk Track Player) == streaming audio, etc.
Supposedly, the retail hardware only supports 48KHz streaming from
[Drive I/F]. However it's more likely that the hardware supports
32KHz streaming, and the upsampling is transparent to the user.
TODO check if anything tries to stream at 32KHz.
The [Drive I/F] actually supports simultaneous requests for audio and
normal data. For this reason, we can't really get rid of the crit section.
IMPORTANT:
This file mainly deals with the [Drive I/F], however [AIDFR] controls
the rate at which the audio data is DMA'd from RAM into the [AI FIFO]
(and the speed at which the FIFO is read by its SRC). Everything else
relating to AID happens in DSP.cpp. It's kinda just bad hardware design.
TODO maybe the files should be merged?
*/
#include "Common.h"
@ -37,7 +65,7 @@
namespace AudioInterface
{
// internal hardware addresses
// Internal hardware addresses
enum
{
AI_CONTROL_REGISTER = 0x6C00,
@ -46,6 +74,15 @@ enum
AI_INTERRUPT_TIMING = 0x6C0C,
};
enum
{
AIS_32KHz = 0,
AIS_48KHz = 1,
AID_32KHz = 1,
AID_48KHz = 0
};
// AI Control Register
union AICR
{
@ -54,64 +91,68 @@ union AICR
struct
{
u32 PSTAT : 1; // sample counter/playback enable
u32 AIFR : 1; // AI Frequency (0=32khz 1=48khz)
u32 AISFR : 1; // AIS Frequency (0=32khz 1=48khz)
u32 AIINTMSK : 1; // 0=interrupt masked 1=interrupt enabled
u32 AIINT : 1; // audio interrupt status
u32 AIINTVLD : 1; // This bit controls whether AIINT is affected by the AIIT register
// matching AISLRCNT. Once set, AIINT will hold
u32 AIINTVLD : 1; // This bit controls whether AIINT is affected by the Interrupt Timing register
// matching the sample counter. Once set, AIINT will hold its last value
u32 SCRESET : 1; // write to reset counter
u32 DACFR : 1; // DAC Frequency (0=48khz 1=32khz)
u32 AIDFR : 1; // AID Frequency (0=48khz 1=32khz)
u32 :25;
};
u32 hex;
};
// AI m_Volume Register
// AI Volume Register
union AIVR
{
AIVR() { hex = 0;}
struct
{
u32 leftVolume : 8;
u32 rightVolume : 8;
u32 : 16;
u32 left : 8;
u32 right : 8;
u32 :16;
};
u32 hex;
};
// AudioInterface-Registers
struct SAudioRegister
{
AICR m_Control;
AIVR m_Volume;
u32 m_SampleCounter;
u32 m_InterruptTiming;
};
// STATE_TO_SAVE
static SAudioRegister g_AudioRegister;
// Registers
static AICR m_Control;
static AIVR m_Volume;
static u32 m_SampleCounter = 0;
static u32 m_InterruptTiming = 0;
static u64 g_LastCPUTime = 0;
static unsigned int g_AISampleRate = 32000;
static unsigned int g_DACSampleRate = 32000;
static u64 g_CPUCyclesPerSample = 0xFFFFFFFFFFFULL;
static unsigned int g_AISSampleRate = 48000;
static unsigned int g_AIDSampleRate = 32000;
void DoState(PointerWrap &p)
{
p.Do(g_AudioRegister);
p.Do(m_Control);
p.Do(m_Volume);
p.Do(m_SampleCounter);
p.Do(m_InterruptTiming);
p.Do(g_LastCPUTime);
p.Do(g_AISampleRate);
p.Do(g_DACSampleRate);
p.Do(g_AISSampleRate);
p.Do(g_AIDSampleRate);
p.Do(g_CPUCyclesPerSample);
}
void GenerateAudioInterrupt();
void UpdateInterrupts();
void IncreaseSampleCount(const u32 _uAmount);
void ReadStreamBlock(short* _pPCM);
void ReadStreamBlock(s16* _pPCM);
void Init()
{
g_AudioRegister.m_SampleCounter = 0;
g_AudioRegister.m_Control.AIFR = 1;
m_Control.hex = 0;
m_Control.AISFR = AIS_48KHz;
m_Volume.hex = 0;
m_SampleCounter = 0;
m_InterruptTiming = 0;
}
void Shutdown()
@ -119,42 +160,36 @@ void Shutdown()
}
void Read32(u32& _rReturnValue, const u32 _Address)
{
//__AI_SRC_INIT compares CC006C08 to zero, loops if 2
{
switch (_Address & 0xFFFF)
{
case AI_CONTROL_REGISTER: //0x6C00
DEBUG_LOG(AUDIO_INTERFACE, "AudioInterface(R) 0x%08x", _Address);
_rReturnValue = g_AudioRegister.m_Control.hex;
case AI_CONTROL_REGISTER:
_rReturnValue = m_Control.hex;
break;
return;
case AI_VOLUME_REGISTER:
_rReturnValue = m_Volume.hex;
break;
// Sample Rate (AIGetDSPSampleRate)
// 32bit state (highest bit PlayState) // AIGetStreamPlayState
case AI_VOLUME_REGISTER: //0x6C04
DEBUG_LOG(AUDIO_INTERFACE, "AudioInterface(R) 0x%08x", _Address);
_rReturnValue = g_AudioRegister.m_Volume.hex;
return;
case AI_SAMPLE_COUNTER: //0x6C08
_rReturnValue = g_AudioRegister.m_SampleCounter;
if (g_AudioRegister.m_Control.PSTAT)
g_AudioRegister.m_SampleCounter++; // FAKE: but this is a must
return;
case AI_SAMPLE_COUNTER:
_rReturnValue = m_SampleCounter;
// HACK - AI SRC init will do while (oldval == sample_counter) {}
// in order to pass this, we need to increment the counter whenever read
if (m_Control.PSTAT)
m_SampleCounter++;
break;
case AI_INTERRUPT_TIMING:
// When sample counter reaches the value of this register, the interrupt AIINT should
// fire.
DEBUG_LOG(AUDIO_INTERFACE, "AudioInterface(R) 0x%08x", _Address);
_rReturnValue = g_AudioRegister.m_InterruptTiming;
return;
_rReturnValue = m_InterruptTiming;
break;
default:
INFO_LOG(AUDIO_INTERFACE, "AudioInterface(R) 0x%08x", _Address);
_dbg_assert_msg_(AUDIO_INTERFACE, 0, "AudioInterface - Read from ???");
ERROR_LOG(AUDIO_INTERFACE, "unknown read 0x%08x", _Address);
_dbg_assert_msg_(AUDIO_INTERFACE, 0, "AudioInterface - Read from 0x%08x", _Address);
_rReturnValue = 0;
return;
}
DEBUG_LOG(AUDIO_INTERFACE, "r32 %08x %08x", _Address, _rReturnValue);
}
void Write32(const u32 _Value, const u32 _Address)
@ -165,123 +200,111 @@ void Write32(const u32 _Value, const u32 _Address)
{
AICR tmpAICtrl(_Value);
g_AudioRegister.m_Control.AIINTMSK = tmpAICtrl.AIINTMSK;
g_AudioRegister.m_Control.AIINTVLD = tmpAICtrl.AIINTVLD;
m_Control.AIINTMSK = tmpAICtrl.AIINTMSK;
m_Control.AIINTVLD = tmpAICtrl.AIINTVLD;
// Set frequency
if (tmpAICtrl.AIFR != g_AudioRegister.m_Control.AIFR)
// Set frequency of streaming audio
if (tmpAICtrl.AISFR != m_Control.AISFR)
{
INFO_LOG(AUDIO_INTERFACE, "Change Freq to %s", tmpAICtrl.AIFR ? "48khz":"32khz");
g_AudioRegister.m_Control.AIFR = tmpAICtrl.AIFR;
DEBUG_LOG(AUDIO_INTERFACE, "Change AISFR to %s", tmpAICtrl.AISFR ? "48khz":"32khz");
m_Control.AISFR = tmpAICtrl.AISFR;
}
// Set DSP frequency
if (tmpAICtrl.DACFR != g_AudioRegister.m_Control.DACFR)
// Set frequency of DMA
if (tmpAICtrl.AIDFR != m_Control.AIDFR)
{
INFO_LOG(AUDIO_INTERFACE, "AI_CONTROL_REGISTER: Change DSPFR Freq to %s", tmpAICtrl.DACFR ? "48khz":"32khz");
g_AudioRegister.m_Control.DACFR = tmpAICtrl.DACFR;
DEBUG_LOG(AUDIO_INTERFACE, "Change AIDFR to %s", tmpAICtrl.AIDFR ? "32khz":"48khz");
m_Control.AIDFR = tmpAICtrl.AIDFR;
}
g_AISampleRate = tmpAICtrl.AIFR ? 48000 : 32000;
g_DACSampleRate = tmpAICtrl.DACFR ? 32000 : 48000;
g_AISSampleRate = tmpAICtrl.AISFR ? 48000 : 32000;
g_AIDSampleRate = tmpAICtrl.AIDFR ? 32000 : 48000;
g_CPUCyclesPerSample = SystemTimers::GetTicksPerSecond() / g_AISampleRate;
g_CPUCyclesPerSample = SystemTimers::GetTicksPerSecond() / g_AISSampleRate;
// Streaming counter
if (tmpAICtrl.PSTAT != g_AudioRegister.m_Control.PSTAT)
// Streaming counter
if (tmpAICtrl.PSTAT != m_Control.PSTAT)
{
INFO_LOG(AUDIO_INTERFACE, "Change StreamingCounter to %s", tmpAICtrl.PSTAT ? "startet":"stopped");
g_AudioRegister.m_Control.PSTAT = tmpAICtrl.PSTAT;
DEBUG_LOG(AUDIO_INTERFACE, "%s streaming audio", tmpAICtrl.PSTAT ? "start":"stop");
m_Control.PSTAT = tmpAICtrl.PSTAT;
g_LastCPUTime = CoreTiming::GetTicks();
// This is the only new code in this ~3,326 revision, it seems to avoid hanging Crazy Taxi,
// while the 1080 and Wave Race music still works
// Tell Drive Interface to stop streaming
if (!tmpAICtrl.PSTAT) DVDInterface::g_bStream = false;
}
// AI Interrupt
if (tmpAICtrl.AIINT)
{
INFO_LOG(AUDIO_INTERFACE, "Clear AI Interrupt");
g_AudioRegister.m_Control.AIINT = 0;
DEBUG_LOG(AUDIO_INTERFACE, "Clear AIS Interrupt");
m_Control.AIINT = 0;
}
// Sample Count Reset
if (tmpAICtrl.SCRESET)
{
INFO_LOG(AUDIO_INTERFACE, "Reset SampleCounter");
g_AudioRegister.m_SampleCounter = 0;
g_AudioRegister.m_Control.SCRESET = 0;
// set PSTAT = 0 too ? at least the reversed look like this
{
DEBUG_LOG(AUDIO_INTERFACE, "Reset AIS sample counter");
m_SampleCounter = 0;
g_LastCPUTime = CoreTiming::GetTicks();
}
// I don't think we need this
//g_AudioRegister.m_Control = tmpAICtrl;
UpdateInterrupts();
}
break;
case AI_VOLUME_REGISTER:
g_AudioRegister.m_Volume.hex = _Value;
INFO_LOG(AUDIO_INTERFACE, "Set m_Volume: left(%i) right(%i)", g_AudioRegister.m_Volume.leftVolume, g_AudioRegister.m_Volume.rightVolume);
m_Volume.hex = _Value;
DEBUG_LOG(AUDIO_INTERFACE, "Set volume: left(%02x) right(%02x)", m_Volume.left, m_Volume.right);
break;
case AI_SAMPLE_COUNTER:
// _dbg_assert_msg_(AUDIO_INTERFACE, 0, "AudioInterface - m_SampleCounter is Read only");
g_AudioRegister.m_SampleCounter = _Value;
// Why was this commented out? Does something do this?
_dbg_assert_msg_(AUDIO_INTERFACE, 0, "AIS - sample counter is read only");
m_SampleCounter = _Value;
break;
case AI_INTERRUPT_TIMING:
g_AudioRegister.m_InterruptTiming = _Value;
INFO_LOG(AUDIO_INTERFACE, "Set AudioInterrupt: 0x%08x Samples", g_AudioRegister.m_InterruptTiming);
m_InterruptTiming = _Value;
DEBUG_LOG(AUDIO_INTERFACE, "Set interrupt: %08x samples", m_InterruptTiming);
break;
default:
PanicAlert("AudioInterface unknown write");
_dbg_assert_msg_(AUDIO_INTERFACE,0,"AudioInterface - Write to ??? %08x", _Address);
ERROR_LOG(AUDIO_INTERFACE, "unknown write %08x @ %08x", _Value, _Address);
_dbg_assert_msg_(AUDIO_INTERFACE,0,"AIS - Write %08x to %08x", _Value, _Address);
break;
}
}
void UpdateInterrupts()
static void UpdateInterrupts()
{
if (g_AudioRegister.m_Control.AIINT & g_AudioRegister.m_Control.AIINTMSK)
{
ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_AI, true);
}
else
{
ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_AI, false);
}
ProcessorInterface::SetInterrupt(
ProcessorInterface::INT_CAUSE_AI, m_Control.AIINT & m_Control.AIINTMSK);
}
void GenerateAudioInterrupt()
static void GenerateAudioInterrupt()
{
g_AudioRegister.m_Control.AIINT = 1;
m_Control.AIINT = 1;
UpdateInterrupts();
}
void Callback_GetSampleRate(unsigned int &_AISampleRate, unsigned int &_DACSampleRate)
{
_AISampleRate = g_AISampleRate;
_DACSampleRate = g_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 (g_AudioRegister.m_Control.PSTAT && !CCPU::IsStepping())
if (m_Control.PSTAT && !CCPU::IsStepping())
{
static int pos = 0;
static short pcm[28*2];
const int lvolume = g_AudioRegister.m_Volume.leftVolume;
const int rvolume = g_AudioRegister.m_Volume.rightVolume;
static short pcm[NGCADPCM::SAMPLES_PER_BLOCK*2];
const int lvolume = m_Volume.left;
const int rvolume = m_Volume.right;
if (g_AISampleRate == 48000 && _sampleRate == 32000)
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;
@ -293,7 +316,7 @@ unsigned int Callback_GetStreaming(short* _pDestBuffer, unsigned int _numSamples
if (pos == 0)
ReadStreamBlock(pcm);
if (g_AISampleRate == 48000 && _sampleRate == 32000) //downsample 48>32
if (g_AISSampleRate == 48000 && _sampleRate == 32000) //downsample 48>32
{
if (i % 3)
{
@ -312,7 +335,7 @@ unsigned int Callback_GetStreaming(short* _pDestBuffer, unsigned int _numSamples
pos++;
}
else if (g_AISampleRate == 32000 && _sampleRate == 48000) //upsample 32>48
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 );
@ -368,7 +391,7 @@ unsigned int Callback_GetStreaming(short* _pDestBuffer, unsigned int _numSamples
pos++;
}
if (pos == 28)
if (pos == NGCADPCM::SAMPLES_PER_BLOCK)
pos = 0;
}
}
@ -387,52 +410,42 @@ unsigned int Callback_GetStreaming(short* _pDestBuffer, unsigned int _numSamples
}
// WARNING - called from audio thread
void ReadStreamBlock(short *_pPCM)
void ReadStreamBlock(s16 *_pPCM)
{
char tempADPCM[32];
if (DVDInterface::DVDReadADPCM((u8*)tempADPCM, 32))
u8 tempADPCM[NGCADPCM::ONE_BLOCK_SIZE];
if (DVDInterface::DVDReadADPCM(tempADPCM, NGCADPCM::ONE_BLOCK_SIZE))
{
NGCADPCM::DecodeBlock(_pPCM, (u8*)tempADPCM);
NGCADPCM::DecodeBlock(_pPCM, tempADPCM);
}
else
{
for (int j=0; j<28; j++)
{
*_pPCM++ = 0;
*_pPCM++ = 0;
}
memset(_pPCM, 0, NGCADPCM::SAMPLES_PER_BLOCK*2);
}
// COMMENT:
// 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.
// This call must not be done wihout going through CoreTiming's threadsafe option.
// IncreaseSampleCount(28);
// 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.
}
void IncreaseSampleCount(const u32 _iAmount)
static void IncreaseSampleCount(const u32 _iAmount)
{
if (g_AudioRegister.m_Control.PSTAT)
if (m_Control.PSTAT)
{
g_AudioRegister.m_SampleCounter += _iAmount;
if (g_AudioRegister.m_Control.AIINTVLD &&
(g_AudioRegister.m_SampleCounter >= g_AudioRegister.m_InterruptTiming))
m_SampleCounter += _iAmount;
if (m_Control.AIINTVLD && (m_SampleCounter >= m_InterruptTiming))
{
GenerateAudioInterrupt();
}
}
}
unsigned int GetDSPSampleRate()
unsigned int GetAIDSampleRate()
{
return g_DACSampleRate;
return g_AIDSampleRate;
}
void Update()
{
// update timer
if (g_AudioRegister.m_Control.PSTAT)
if (m_Control.PSTAT)
{
const u64 Diff = CoreTiming::GetTicks() - g_LastCPUTime;
if (Diff > g_CPUCyclesPerSample)
@ -445,4 +458,3 @@ void Update()
}
} // end of namespace AudioInterface

View File

@ -41,7 +41,7 @@ void Read32(u32& _uReturnValue, const u32 _iAddress);
void Write32(const u32 _iValue, const u32 _iAddress);
// Get the audio rates (48000 or 32000 only)
unsigned int GetDSPSampleRate();
unsigned int GetAIDSampleRate();
} // namespace

View File

@ -2,21 +2,15 @@
#include "StreamADPCM.h"
#define ONE_BLOCK_SIZE 32
#define SAMPLES_PER_BLOCK 28
// STATE_TO_SAVE (not saved yet!)
static int histl1;
static int histl2;
static int histr1;
static int histr2;
static s32 histl1;
static s32 histl2;
static s32 histr1;
static s32 histr2;
short ADPDecodeSample(int bits, int q, int *hist1p, int *hist2p)
s16 ADPDecodeSample(s32 bits, s32 q, s32& hist1, s32& hist2)
{
const int hist1 = *hist1p;
const int hist2 = *hist2p;
int hist = 0;
s32 hist = 0;
switch (q >> 4)
{
case 0:
@ -36,17 +30,17 @@ short ADPDecodeSample(int bits, int q, int *hist1p, int *hist2p)
if (hist > 0x1fffff) hist = 0x1fffff;
if (hist < -0x200000) hist = -0x200000;
int cur = (((short)(bits << 12) >> (q & 0xf)) << 6) + hist;
s32 cur = (((s16)(bits << 12) >> (q & 0xf)) << 6) + hist;
*hist2p = *hist1p;
*hist1p = cur;
hist2 = hist1;
hist1 = cur;
cur >>= 6;
if (cur < -0x8000) return -0x8000;
if (cur > 0x7fff) return 0x7fff;
return (short)cur;
return (s16)cur;
}
void NGCADPCM::InitFilter()
@ -57,11 +51,11 @@ void NGCADPCM::InitFilter()
histr2 = 0;
}
void NGCADPCM::DecodeBlock(short *pcm, const u8 *adpcm)
void NGCADPCM::DecodeBlock(s16 *pcm, const u8 *adpcm)
{
for (int i = 0; i < SAMPLES_PER_BLOCK; i++)
{
pcm[i * 2] = ADPDecodeSample(adpcm[i + (ONE_BLOCK_SIZE - SAMPLES_PER_BLOCK)] & 0xf, adpcm[0], &histl1, &histl2);
pcm[i * 2 + 1] = ADPDecodeSample(adpcm[i + (ONE_BLOCK_SIZE - SAMPLES_PER_BLOCK)] >> 4, adpcm[1], &histr1, &histr2);
pcm[i * 2] = ADPDecodeSample(adpcm[i + (ONE_BLOCK_SIZE - SAMPLES_PER_BLOCK)] & 0xf, adpcm[0], histl1, histl2);
pcm[i * 2 + 1] = ADPDecodeSample(adpcm[i + (ONE_BLOCK_SIZE - SAMPLES_PER_BLOCK)] >> 4, adpcm[1], histr1, histr2);
}
}

View File

@ -8,8 +8,14 @@
class NGCADPCM
{
public:
enum
{
ONE_BLOCK_SIZE = 32,
SAMPLES_PER_BLOCK = 28
};
static void InitFilter();
static void DecodeBlock(short *pcm, const u8 *adpcm);
static void DecodeBlock(s16 *pcm, const u8 *adpcm);
};
#endif

View File

@ -151,8 +151,6 @@ u32 ConvertMillisecondsToTicks(u32 _Milliseconds)
void AICallback(u64 userdata, int cyclesLate)
{
// Update disk streaming. All that code really needs a revamp, including replacing the codec with the one
// from in_cube.
AudioInterface::Update();
CoreTiming::ScheduleEvent(AI_PERIOD - cyclesLate, et_AI);
}
@ -166,7 +164,7 @@ void DSPCallback(u64 userdata, int cyclesLate)
void AudioDMACallback(u64 userdata, int cyclesLate)
{
int period = CPU_CORE_CLOCK / (AudioInterface::GetDSPSampleRate() * 4 / 32);
int period = CPU_CORE_CLOCK / (AudioInterface::GetAIDSampleRate() * 4 / 32);
DSP::UpdateAudioDMA(); // Push audio to speakers.
CoreTiming::ScheduleEvent(period - cyclesLate, et_AudioDMA);
}
@ -280,7 +278,7 @@ void Init()
AI_PERIOD = GetTicksPerSecond() / 80;
// System internal sample rate is fixed at 32KHz * 4 (16bit Stereo) / 32 bytes DMA
AUDIO_DMA_PERIOD = CPU_CORE_CLOCK / (AudioInterface::GetDSPSampleRate() * 4 / 32);
AUDIO_DMA_PERIOD = CPU_CORE_CLOCK / (AudioInterface::GetAIDSampleRate() * 4 / 32);
Common::Timer::IncreaseResolution();
// store and convert localtime at boot to timebase ticks