Zelda HLE: Implement HQ AFC samples decoding.

This commit is contained in:
Pierre Bourdon 2014-12-13 16:56:50 +01:00
parent 33c29d8aab
commit 580fe12c5b
2 changed files with 232 additions and 27 deletions

View File

@ -227,8 +227,14 @@ void ZeldaUCode::RunPendingCommands()
sine_table[i] = Common::swap16(data_ptr[0x200 + i]); sine_table[i] = Common::swap16(data_ptr[0x200 + i]);
m_renderer.SetSineTable(std::move(sine_table)); m_renderer.SetSineTable(std::move(sine_table));
u16* afc_coeffs_ptr = (u16*)HLEMemory_Get_Pointer(Read32());
std::array<s16, 0x20> afc_coeffs;
for (size_t i = 0; i < 0x20; ++i)
afc_coeffs[i] = Common::swap16(afc_coeffs_ptr[i]);
m_renderer.SetAfcCoeffs(std::move(afc_coeffs));
SendCommandAck(CommandAck::STANDARD, sync); SendCommandAck(CommandAck::STANDARD, sync);
Read32(); Read32(); Read32();
break; break;
} }
@ -278,13 +284,6 @@ void ZeldaUCode::SendCommandAck(CommandAck ack_type, u16 sync_value)
void ZeldaUCode::RenderAudio() void ZeldaUCode::RenderAudio()
{ {
#if 0
WARN_LOG(DSPHLE, "RenderAudio() frame %d/%d voice %d/%d (sync to %d)",
m_rendering_curr_frame, m_rendering_requested_frames,
m_rendering_curr_voice, m_rendering_voices_per_frame,
m_sync_max_voice_id);
#endif
if (!RenderingInProgress()) if (!RenderingInProgress())
{ {
WARN_LOG(DSPHLE, "Trying to render audio while no rendering should be happening."); WARN_LOG(DSPHLE, "Trying to render audio while no rendering should be happening.");
@ -421,7 +420,10 @@ struct ZeldaAudioRenderer::VPB
u16 current_pos_frac; u16 current_pos_frac;
u16 unk_31; u16 unk_31;
u16 unk_32;
// Number of remaining decoded AFC samples in the VPB internal buffer (see
// VPB[0x58]) that haven't been output yet.
u16 afc_remaining_decoded_samples;
// Value used as the constant sample value if VPB[6] (use_constant_sample) // Value used as the constant sample value if VPB[6] (use_constant_sample)
// is set. Reset to the last sample value after each round of resampling. // is set. Reset to the last sample value after each round of resampling.
@ -441,8 +443,11 @@ struct ZeldaAudioRenderer::VPB
u16 samples_before_loop; u16 samples_before_loop;
u16 unk_37; u16 unk_37;
u16 unk_38;
u16 unk_39; // Current address used to stream samples for the ARAM sample source types.
u16 current_aram_addr_h;
u16 current_aram_addr_l;
DEFINE_32BIT_ACCESSOR(current_aram_addr, CurrentARAMAddr)
// Remaining number of samples to load before considering the voice // Remaining number of samples to load before considering the voice
// rendering complete and setting the done flag. Note that this is an // rendering complete and setting the done flag. Note that this is an
@ -454,21 +459,40 @@ struct ZeldaAudioRenderer::VPB
// Stores the last 4 resampled input samples after each frame, so that they // Stores the last 4 resampled input samples after each frame, so that they
// can be used for future linear interpolation. // can be used for future linear interpolation.
u16 resample_buffer[4]; s16 resample_buffer[4];
u16 unk[0x80 - 0x40]; // TODO: document and implement.
s16 prev_input_samples[0x18];
// Values from the last decoded AFC block. The last two values are
// especially important since AFC decoding - as a variant of ADPCM -
// requires the two latest sample values to be able to decode future
// samples.
s16 afc_remaining_samples[0x10];
s16* AFCYN2() { return &afc_remaining_samples[0xE]; }
s16* AFCYN1() { return &afc_remaining_samples[0xF]; }
u16 unk_68_80[0x80 - 0x68];
enum SamplesSourceType enum SamplesSourceType
{ {
// Samples stored in ARAM at a rate of 16 samples/9 bytes, AFC encoded,
// at an arbitrary sample rate (resampling is applied).
SRC_AFC_HQ_FROM_ARAM = 9,
// Samples stored in MRAM at an arbitrary sample rate (resampling is // Samples stored in MRAM at an arbitrary sample rate (resampling is
// applied, unlike PCM16_FROM_MRAM_RAW). // applied, unlike PCM16_FROM_MRAM_RAW).
SRC_PCM16_FROM_MRAM = 33, SRC_PCM16_FROM_MRAM = 33,
}; };
u16 samples_source_type; u16 samples_source_type;
u16 unk_81; // If non zero, indicates that the sound should loop.
u16 unk_82; u16 is_looping;
u16 unk_83;
// For AFC looping voices, the values of the last 2 samples before the
// start of the loop, in order to be able to decode samples after looping.
s16 loop_yn1;
s16 loop_yn2;
u16 unk_84; u16 unk_84;
// If true, ramp down quickly to a volume of zero, and end the voice (by // If true, ramp down quickly to a volume of zero, and end the voice (by
@ -480,21 +504,23 @@ struct ZeldaAudioRenderer::VPB
// Base address used to download samples data after the loop point of the // Base address used to download samples data after the loop point of the
// voice has been reached. // voice has been reached.
u16 dma_loop_address_h; u16 loop_address_h;
u16 dma_loop_address_l; u16 loop_address_l;
DEFINE_32BIT_ACCESSOR(dma_loop_address, DMALoopAddress) DEFINE_32BIT_ACCESSOR(loop_address, LoopAddress)
// Offset (in number of raw samples) of the start of the loop area in the // Offset (in number of raw samples) of the start of the loop area in the
// voice. Note: some sample sources only use the _h part of this. // voice. Note: some sample sources only use the _h part of this.
//
// TODO: rename to length? confusion with remaining_length...
u16 loop_start_position_h; u16 loop_start_position_h;
u16 loop_start_position_l; u16 loop_start_position_l;
DEFINE_32BIT_ACCESSOR(loop_start_position, LoopStartPosition) DEFINE_32BIT_ACCESSOR(loop_start_position, LoopStartPosition)
// Base address used to download samples data before the loop point of the // Base address used to download samples data before the loop point of the
// voice has been reached. // voice has been reached.
u16 dma_base_address_h; u16 base_address_h;
u16 dma_base_address_l; u16 base_address_l;
DEFINE_32BIT_ACCESSOR(dma_base_address, DMABaseAddress) DEFINE_32BIT_ACCESSOR(base_address, BaseAddress)
u16 padding[SIZE_IN_WORDS]; u16 padding[SIZE_IN_WORDS];
}; };
@ -653,6 +679,11 @@ void ZeldaAudioRenderer::AddVoice(u16 voice_id)
} }
} }
// By then the VPB has been reset, unless we're in the "constant sample" /
// silence mode.
if (!vpb.use_constant_sample)
vpb.reset_vpb = false;
StoreVPB(voice_id, vpb); StoreVPB(voice_id, vpb);
} }
@ -720,13 +751,17 @@ void ZeldaAudioRenderer::LoadInputSamples(MixingBuffer* buffer, VPB* vpb)
switch (vpb->samples_source_type) switch (vpb->samples_source_type)
{ {
case VPB::SRC_AFC_HQ_FROM_ARAM:
DownloadAFCSamplesFromARAM(raw_input_samples.data() + 4, vpb,
NeededRawSamplesCount(*vpb));
Resample(vpb, raw_input_samples.data(), buffer);
break;
case VPB::SRC_PCM16_FROM_MRAM: case VPB::SRC_PCM16_FROM_MRAM:
{
DownloadRawSamplesFromMRAM(raw_input_samples.data() + 4, vpb, DownloadRawSamplesFromMRAM(raw_input_samples.data() + 4, vpb,
NeededRawSamplesCount(*vpb)); NeededRawSamplesCount(*vpb));
Resample(vpb, raw_input_samples.data(), buffer); Resample(vpb, raw_input_samples.data(), buffer);
break; break;
}
default: default:
ERROR_LOG(DSPHLE, "Using an unknown/unimplemented sample source: %04x", vpb->samples_source_type); ERROR_LOG(DSPHLE, "Using an unknown/unimplemented sample source: %04x", vpb->samples_source_type);
@ -789,7 +824,7 @@ void ZeldaAudioRenderer::Resample(VPB* vpb, const s16* src, MixingBuffer* dst)
void ZeldaAudioRenderer::DownloadRawSamplesFromMRAM( void ZeldaAudioRenderer::DownloadRawSamplesFromMRAM(
s16* dst, VPB* vpb, u16 requested_samples_count) s16* dst, VPB* vpb, u16 requested_samples_count)
{ {
u32 addr = vpb->GetDMABaseAddress() + vpb->current_position_h * sizeof (u16); u32 addr = vpb->GetBaseAddress() + vpb->current_position_h * sizeof (u16);
s16* src_ptr = (s16*)HLEMemory_Get_Pointer(addr); s16* src_ptr = (s16*)HLEMemory_Get_Pointer(addr);
if (requested_samples_count > vpb->GetRemainingLength()) if (requested_samples_count > vpb->GetRemainingLength())
@ -818,8 +853,8 @@ void ZeldaAudioRenderer::DownloadRawSamplesFromMRAM(
{ {
for (u16 i = 0; i < vpb->samples_before_loop; ++i) for (u16 i = 0; i < vpb->samples_before_loop; ++i)
*dst++ = Common::swap16(*src_ptr++); *dst++ = Common::swap16(*src_ptr++);
vpb->SetDMABaseAddress(vpb->GetDMALoopAddress()); vpb->SetBaseAddress(vpb->GetLoopAddress());
src_ptr = (s16*)HLEMemory_Get_Pointer(vpb->GetDMALoopAddress()); src_ptr = (s16*)HLEMemory_Get_Pointer(vpb->GetLoopAddress());
for (u16 i = vpb->samples_before_loop; i < requested_samples_count; ++i) for (u16 i = vpb->samples_before_loop; i < requested_samples_count; ++i)
*dst++ = Common::swap16(*src_ptr++); *dst++ = Common::swap16(*src_ptr++);
vpb->current_position_h = requested_samples_count - vpb->samples_before_loop; vpb->current_position_h = requested_samples_count - vpb->samples_before_loop;
@ -827,6 +862,169 @@ void ZeldaAudioRenderer::DownloadRawSamplesFromMRAM(
} }
} }
void ZeldaAudioRenderer::DownloadAFCSamplesFromARAM(
s16* dst, VPB* vpb, u16 requested_samples_count)
{
if (vpb->reset_vpb)
{
*vpb->AFCYN1() = 0;
*vpb->AFCYN2() = 0;
vpb->afc_remaining_decoded_samples = 0;
vpb->SetRemainingLength(vpb->GetLoopStartPosition());
vpb->SetCurrentARAMAddr(vpb->GetBaseAddress());
}
if (vpb->done)
{
for (u16 i = 0; i < requested_samples_count; ++i)
dst[i] = 0;
return;
}
// Try several things until we have output enough samples.
while (true)
{
// Try to push currently cached/already decoded samples.
u16 remaining_to_output = std::min(vpb->afc_remaining_decoded_samples,
requested_samples_count);
for (size_t i = 0x10 - remaining_to_output; i < 0x10; ++i)
*dst++ = vpb->afc_remaining_samples[i];
vpb->afc_remaining_decoded_samples -= remaining_to_output;
requested_samples_count -= remaining_to_output;
if (requested_samples_count == 0)
{
return; // We have output everything we needed.
}
else if (requested_samples_count <= vpb->GetRemainingLength())
{
// Each AFC block is 16 samples.
u16 requested_blocks_count = (requested_samples_count + 0xF) >> 4;
u16 decoded_samples_count = requested_blocks_count << 4;
if (decoded_samples_count < vpb->GetRemainingLength())
{
vpb->afc_remaining_decoded_samples =
decoded_samples_count - requested_samples_count;
vpb->SetRemainingLength(vpb->GetRemainingLength() - decoded_samples_count);
}
else
{
vpb->afc_remaining_decoded_samples =
vpb->GetRemainingLength() - requested_samples_count;
vpb->SetRemainingLength(0);
}
DecodeAFC(vpb, dst, requested_blocks_count);
for (size_t i = 0; i < 0x10; ++i)
vpb->afc_remaining_samples[i] = dst[decoded_samples_count - 0x10 + i];
return;
}
else
{
// More samples asked than available. Either complete the sound, or
// start looping.
if (vpb->GetRemainingLength()) // Skip if we cannot load anything.
{
requested_samples_count -= vpb->GetRemainingLength();
u16 requested_blocks_count = (vpb->GetRemainingLength() + 0xF) >> 4;
DecodeAFC(vpb, dst, requested_blocks_count);
dst += vpb->GetRemainingLength();
}
if (!vpb->is_looping)
{
vpb->done = true;
for (size_t i = 0; i < requested_samples_count; ++i)
*dst++ = 0;
return;
}
else
{
// We need to loop. Compute the new position, decode a block,
// and loop back to the beginning of the download logic.
// Use the fact that the sample source number also represents
// the number of bytes per 16 samples.
u32 loop_off_in_bytes =
(vpb->GetLoopAddress() >> 4) * vpb->samples_source_type;
u32 loop_start_addr = vpb->GetBaseAddress() + loop_off_in_bytes;
vpb->SetCurrentARAMAddr(loop_start_addr);
*vpb->AFCYN2() = vpb->loop_yn2;
*vpb->AFCYN1() = vpb->loop_yn1;
DecodeAFC(vpb, vpb->afc_remaining_samples, 1);
// Realign and recompute the number of internally cached
// samples and the current position.
vpb->afc_remaining_decoded_samples =
0x10 - (vpb->GetLoopAddress() & 0xF);
u32 remaining_length = vpb->GetLoopStartPosition();
remaining_length -= vpb->afc_remaining_decoded_samples;
remaining_length -= vpb->GetLoopAddress();
vpb->SetRemainingLength(remaining_length);
continue;
}
}
}
}
void ZeldaAudioRenderer::DecodeAFC(VPB* vpb, s16* dst, size_t block_count)
{
u32 addr = vpb->GetCurrentARAMAddr();
u8* src = (u8*)DSP::GetARAMPtr() + addr;
vpb->SetCurrentARAMAddr(addr + block_count * vpb->samples_source_type);
for (size_t b = 0; b < block_count; ++b)
{
s16 nibbles[16];
s16 delta = 1 << ((*src >> 4) & 0xF);
s16 idx = (*src & 0xF);
src++;
if (vpb->samples_source_type == VPB::SRC_AFC_HQ_FROM_ARAM)
{
for (size_t i = 0; i < 16; i += 2)
{
nibbles[i + 0] = *src >> 4;
nibbles[i + 1] = *src & 0xF;
src++;
}
for (auto& nibble : nibbles)
{
if (nibble >= 8)
nibble = nibble - 16;
nibble <<= 11;
}
}
else
{
// TODO: LQ samples.
}
s32 yn1 = *vpb->AFCYN1(), yn2 = *vpb->AFCYN2();
for (size_t i = 0; i < 16; ++i)
{
s32 sample = delta * nibbles[i] +
yn1 * m_afc_coeffs[idx * 2] +
yn2 * m_afc_coeffs[idx * 2 + 1];
sample >>= 11;
MathUtil::Clamp(&sample, -0x8000, 0x7fff);
*dst++ = (s16)sample;
yn2 = yn1;
yn1 = sample;
}
*vpb->AFCYN2() = yn2;
*vpb->AFCYN1() = yn1;
}
}
void ZeldaAudioRenderer::DoState(PointerWrap& p) void ZeldaAudioRenderer::DoState(PointerWrap& p)
{ {
p.Do(m_output_lbuf_addr); p.Do(m_output_lbuf_addr);

View File

@ -17,6 +17,7 @@ public:
void SetSineTable(std::array<s16, 0x80>&& sine_table) { m_sine_table = sine_table; } void SetSineTable(std::array<s16, 0x80>&& sine_table) { m_sine_table = sine_table; }
void SetResamplingCoeffs(std::array<s16, 0x100>&& coeffs) { m_resampling_coeffs = coeffs; } void SetResamplingCoeffs(std::array<s16, 0x100>&& coeffs) { m_resampling_coeffs = coeffs; }
void SetAfcCoeffs(std::array<s16, 0x20>&& coeffs) { m_afc_coeffs = coeffs; }
void SetVPBBaseAddress(u32 addr) { m_vpb_base_addr = addr; } void SetVPBBaseAddress(u32 addr) { m_vpb_base_addr = addr; }
void SetOutputVolume(u16 volume) { m_output_volume = volume; } void SetOutputVolume(u16 volume) { m_output_volume = volume; }
void SetOutputLeftBufferAddr(u32 addr) { m_output_lbuf_addr = addr; } void SetOutputLeftBufferAddr(u32 addr) { m_output_lbuf_addr = addr; }
@ -139,6 +140,12 @@ private:
// Downloads samples from MRAM while handling appropriate length / looping // Downloads samples from MRAM while handling appropriate length / looping
// behavior. // behavior.
void DownloadRawSamplesFromMRAM(s16* dst, VPB* vpb, u16 requested_samples_count); void DownloadRawSamplesFromMRAM(s16* dst, VPB* vpb, u16 requested_samples_count);
// Download AFC encoded samples from ARAM and decode them. Handles looping
// and other parameters appropriately.
void DownloadAFCSamplesFromARAM(s16* dst, VPB* vpb, u16 requested_samples_count);
void DecodeAFC(VPB* vpb, s16* dst, size_t block_count);
std::array<s16, 0x20> m_afc_coeffs;
}; };
class ZeldaUCode : public UCodeInterface class ZeldaUCode : public UCodeInterface