mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 02:50:14 +00:00
Audio: Implement decoding of ADPCM
This commit is contained in:
parent
16a5a8792a
commit
09200c85cf
@ -7,11 +7,12 @@
|
||||
|
||||
#include "core/audio/stream.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <queue>
|
||||
|
||||
namespace Audio {
|
||||
std::vector<u16> DecodeADPCM(u8* data, size_t sample_count, u16 adpcm_ps, s16 adpcm_yn[2], std::array<u8, 16> adpcm_coeff);
|
||||
std::vector<s16> DecodeADPCM(u8* data, size_t sample_count, bool has_adpcm, u16 adpcm_ps, s16* adpcm_yn, const std::array<s16, 16>& adpcm_coeff);
|
||||
|
||||
static const int BASE_SAMPLE_RATE = 22050;
|
||||
|
||||
@ -27,13 +28,69 @@ namespace Audio {
|
||||
}
|
||||
};
|
||||
|
||||
struct AdpcmState {
|
||||
u16 ps;
|
||||
s16 yn0;
|
||||
s16 yn1;
|
||||
};
|
||||
|
||||
std::vector<s16> DecodeADPCM(u8* data, size_t sample_count, bool has_adpcm, u16 adpcm_ps, s16 adpcm_yn[2], const std::array<s16, 16>& adpcm_coeff, AdpcmState& state) {
|
||||
std::vector<s16> ret(sample_count);
|
||||
|
||||
int yn0 = state.yn0, yn1 = state.yn1;
|
||||
|
||||
if (sample_count % 14 != 0) {
|
||||
LOG_ERROR(Audio, "Audio stream has incomplete frames");
|
||||
}
|
||||
|
||||
const static int signed_nybbles[16] = { 0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1 };
|
||||
|
||||
const int num_frames = sample_count / 14;
|
||||
for (int frameno = 0; frameno < num_frames; frameno++) {
|
||||
int frame_header = data[frameno * 8];
|
||||
|
||||
int scale = 1 << (frame_header & 0xF);
|
||||
int idx = (frame_header >> 4) & 0x7;
|
||||
|
||||
int coef0 = (s16)adpcm_coeff[idx * 2 + 0];
|
||||
int coef1 = (s16)adpcm_coeff[idx * 2 + 1];
|
||||
|
||||
auto next_nybble = [&](int nybble) -> s16 {
|
||||
int val = (((nybble * scale) << 11) + 0x400 + coef0 * yn0 + coef1 * yn1) >> 11;
|
||||
if (val >= 32767) val = 32767;
|
||||
if (val <= -32768) val = -32768;
|
||||
yn1 = yn0;
|
||||
yn0 = val;
|
||||
return (s16)val;
|
||||
};
|
||||
|
||||
for (int i = frameno * 14, datai = frameno * 8 + 1, samplecount = 0; samplecount < 14; i += 2, datai++, samplecount += 2) {
|
||||
ret[i + 0] = next_nybble(signed_nybbles[data[datai] & 0xF]);
|
||||
ret[i + 1] = next_nybble(signed_nybbles[data[datai] >> 4]);
|
||||
}
|
||||
}
|
||||
|
||||
state.yn0 = yn0;
|
||||
state.yn1 = yn1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct OutputChannel {
|
||||
ALuint source;
|
||||
|
||||
int mono_or_stereo;
|
||||
Format format;
|
||||
int format_rest;
|
||||
|
||||
std::priority_queue<Buffer> queue;
|
||||
std::queue<Buffer> playing;
|
||||
u16 last_bufid;
|
||||
|
||||
bool enabled;
|
||||
|
||||
std::array<s16, 16> adpcm_coeffs;
|
||||
AdpcmState adpcm_state;
|
||||
};
|
||||
|
||||
OutputChannel chans[24];
|
||||
@ -83,21 +140,28 @@ namespace Audio {
|
||||
alGenSources(1, &chans[i].source);
|
||||
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to setup sound source");
|
||||
}
|
||||
|
||||
silence.fill(0);
|
||||
}
|
||||
|
||||
void Shutdown() {}
|
||||
|
||||
void UpdateFormat(int chanid, int mono_or_stereo, Format format) {
|
||||
void UpdateFormat(int chanid, int mono_or_stereo, Format format, int rest) {
|
||||
chans[chanid].mono_or_stereo = mono_or_stereo;
|
||||
chans[chanid].format = format;
|
||||
chans[chanid].format_rest = rest;
|
||||
}
|
||||
|
||||
LOG_WARNING(Audio, "(STUB)");
|
||||
void UpdateAdpcm(int chanid, s16 coeffs[16]) {
|
||||
LOG_INFO(Audio, "ADPCM Coeffs updated for channel %i", chanid);
|
||||
std::copy(coeffs, coeffs+16, std::begin(chans[chanid].adpcm_coeffs));
|
||||
}
|
||||
|
||||
void EnqueueBuffer(int chanid, u16 buffer_id,
|
||||
void* data, int sample_count,
|
||||
bool has_adpcm, u16 adpcm_ps, s16 adpcm_yn[2],
|
||||
bool is_looping) {
|
||||
LOG_INFO(Audio, "enqueu for %i", chanid);
|
||||
|
||||
if (is_looping) {
|
||||
LOG_WARNING(Audio, "Looped buffers are unimplemented");
|
||||
@ -128,14 +192,14 @@ namespace Audio {
|
||||
break;
|
||||
}
|
||||
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to init buffer");
|
||||
} /*else if (chans[chanid].format == FORMAT_ADPCM) {
|
||||
} else if (chans[chanid].format == FORMAT_ADPCM) {
|
||||
if (chans[chanid].mono_or_stereo != 1) {
|
||||
LOG_ERROR(Audio, "Being fed non-mono ADPCM");
|
||||
}
|
||||
std::vector<u16> decoded = DecodeADPCM(data, sample_count, adpcm_ps, adpcm_yn, chans[chanid].adpcm_coeff);
|
||||
alBufferData(b, AL_FORMAT_MONO16, decoded.data(), decoded.size() * 2, BASE_SAMPLE_RATE);
|
||||
std::vector<s16> decoded = DecodeADPCM((u8*)data, sample_count, has_adpcm, adpcm_ps, adpcm_yn, chans[chanid].adpcm_coeffs, chans[chanid].adpcm_state);
|
||||
alBufferData(b, AL_FORMAT_STEREO16, decoded.data(), decoded.size()*2, BASE_SAMPLE_RATE);
|
||||
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to init buffer");
|
||||
}*/ else {
|
||||
} else {
|
||||
LOG_ERROR(Audio, "Unrecognised audio format in buffer 0x%04x (size: %i samples)", buffer_id, sample_count);
|
||||
alBufferData(b, AL_FORMAT_MONO8, silence.data(), silence.size(), BASE_SAMPLE_RATE);
|
||||
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to init buffer");
|
||||
@ -144,24 +208,34 @@ namespace Audio {
|
||||
chans[chanid].queue.emplace( Buffer { buffer_id, b, is_looping });
|
||||
}
|
||||
|
||||
void Play(int chanid, bool play) {
|
||||
LOG_INFO(Audio, "Play(%i,%i)", chanid, play);
|
||||
chans[chanid].enabled = play;
|
||||
}
|
||||
|
||||
void Tick(int chanid) {
|
||||
auto& c = chans[chanid];
|
||||
|
||||
if (!c.queue.empty()) {
|
||||
while (!c.queue.empty()) {
|
||||
alSourceQueueBuffers(c.source, 1, &c.queue.top().buffer);
|
||||
if (alGetError() != AL_NO_ERROR) LOG_CRITICAL(Audio, "Failed to enqueue buffer");
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio, "Failed to enqueue buffer");
|
||||
c.queue.pop();
|
||||
continue;
|
||||
}
|
||||
c.playing.emplace(c.queue.top());
|
||||
LOG_INFO(Audio, "Enqueued buffer id 0x%04x", c.queue.top().id);
|
||||
LOG_DEBUG(Audio, "Enqueued buffer id 0x%04x", c.queue.top().id);
|
||||
c.queue.pop();
|
||||
}
|
||||
|
||||
if (c.enabled) {
|
||||
ALint state;
|
||||
alGetSourcei(c.source, AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) {
|
||||
alSourcePlay(c.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!c.playing.empty()) {
|
||||
c.last_bufid = c.playing.front().id;
|
||||
@ -174,18 +248,15 @@ namespace Audio {
|
||||
alSourceUnqueueBuffers(c.source, 1, &buf);
|
||||
processed--;
|
||||
|
||||
LOG_INFO(Audio, "Finished buffer id 0x%04x", c.playing.front().id);
|
||||
|
||||
while (!c.playing.empty() && c.playing.front().buffer != buf) {
|
||||
c.playing.pop();
|
||||
LOG_ERROR(Audio, "Audio is extremely funky. Should abort. (Desynced queue.)");
|
||||
}
|
||||
LOG_DEBUG(Audio, "Finished buffer id 0x%04x", c.playing.front().id);
|
||||
|
||||
if (!c.playing.empty()) {
|
||||
if (c.playing.front().buffer != buf) LOG_CRITICAL(Audio, "Audio is extremely funky. Should abort. (Desynced queue.)");
|
||||
|
||||
c.last_bufid = c.playing.front().id;
|
||||
c.playing.pop();
|
||||
} else {
|
||||
LOG_ERROR(Audio, "Audio is extremely funky. Should abort. (Empty queue.)");
|
||||
LOG_CRITICAL(Audio, "Audio is extremely funky. Should abort. (Empty queue.)");
|
||||
}
|
||||
|
||||
alDeleteBuffers(1, &buf);
|
||||
@ -199,7 +270,7 @@ namespace Audio {
|
||||
std::tuple<bool, u16, u32> GetStatus(int chanid) {
|
||||
auto& c = chans[chanid];
|
||||
|
||||
bool isplaying = false;
|
||||
bool isplaying = c.enabled;
|
||||
u16 bufid = 0;
|
||||
u32 pos = 0;
|
||||
|
||||
@ -207,8 +278,6 @@ namespace Audio {
|
||||
alGetSourcei(c.source, AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(c.source, AL_SAMPLE_OFFSET, &samples);
|
||||
|
||||
if (state == AL_PLAYING) isplaying = true;
|
||||
|
||||
bufid = c.last_bufid;
|
||||
|
||||
pos = samples;
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
namespace Audio {
|
||||
void Init();
|
||||
void Play(void* buf, size_t size);
|
||||
void Shutdown();
|
||||
|
||||
enum Format : u16 {
|
||||
@ -19,7 +18,10 @@ namespace Audio {
|
||||
FORMAT_ADPCM = 2
|
||||
};
|
||||
|
||||
void UpdateFormat(int chanid, int mono_or_stereo, Format format);
|
||||
void UpdateFormat(int chanid, int mono_or_stereo, Format format, int rest);
|
||||
void UpdateAdpcm(int chanid, s16 coeffs[16]);
|
||||
|
||||
void Play(int chanid, bool play);
|
||||
|
||||
void EnqueueBuffer(int chanid, u16 buffer_id,
|
||||
void* data, int sample_count,
|
||||
|
@ -110,7 +110,12 @@ struct ChannelContext {
|
||||
u32 dirty;
|
||||
|
||||
// Effects
|
||||
INSERT_PADDING_DSPWORDS(35);
|
||||
float mix[12];
|
||||
float rate;
|
||||
u8 rim[2];
|
||||
u16 iirFilterType;
|
||||
u16 iirFilter_mono[2];
|
||||
u16 iirFilter_biquad[5];
|
||||
|
||||
// Buffer Queue
|
||||
u16 buffers_dirty; //< Which of those queued buffers is dirty (bit i == buffers[i])
|
||||
@ -125,14 +130,18 @@ struct ChannelContext {
|
||||
dsp_u32 physical_address;
|
||||
dsp_u32 sample_count;
|
||||
union {
|
||||
u16 flags1_raw;
|
||||
BitField<0, 2, u16> mono_or_stereo;
|
||||
BitField<2, 2, Audio::Format> format;
|
||||
BitField<4, 12, u16> rest;
|
||||
};
|
||||
u16 adpcm_ps;
|
||||
s16 adpcm_yn[2];
|
||||
union {
|
||||
u16 flags2_raw;
|
||||
BitField<0, 1, u16> has_adpcm;
|
||||
BitField<1, 1, u16> is_looping;
|
||||
BitField<2, 14, u16> rest2;
|
||||
};
|
||||
u16 buffer_id;
|
||||
};
|
||||
@ -149,7 +158,7 @@ struct ChannelStatus {
|
||||
ASSERT_STRUCT(ChannelStatus, 12);
|
||||
|
||||
struct AdpcmCoefficients {
|
||||
u16 coeff[16];
|
||||
s16 coeff[16];
|
||||
};
|
||||
ASSERT_STRUCT(AdpcmCoefficients, 32);
|
||||
|
||||
@ -183,6 +192,8 @@ static void AudioTick(u64, int cycles_late) {
|
||||
}
|
||||
|
||||
auto channel_contexes = (ChannelContext*) Memory::GetPointer(DspAddrToVAddr(current_base, DSPADDR1));
|
||||
auto channel_contex0 = (ChannelContext*)Memory::GetPointer(DspAddrToVAddr(BASE_ADDR_0, DSPADDR1));
|
||||
auto channel_contex1 = (ChannelContext*)Memory::GetPointer(DspAddrToVAddr(BASE_ADDR_1, DSPADDR1));
|
||||
auto channel_status0 = (ChannelStatus*)Memory::GetPointer(DspAddrToVAddr(BASE_ADDR_0, DSPADDR2));
|
||||
auto channel_status1 = (ChannelStatus*)Memory::GetPointer(DspAddrToVAddr(BASE_ADDR_1, DSPADDR2));
|
||||
auto channel_adpcm_coeffs = (AdpcmCoefficients*) Memory::GetPointer(DspAddrToVAddr(current_base, DSPADDR3));
|
||||
@ -198,15 +209,9 @@ static void AudioTick(u64, int cycles_late) {
|
||||
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 29");
|
||||
}
|
||||
|
||||
if (TestAndUnsetBit(ctx.dirty, 16)) {
|
||||
// Is Active?
|
||||
//LOG_WARNING(Service_DSP, "Unimplemented dirty bit 16");
|
||||
}
|
||||
|
||||
if (TestAndUnsetBit(ctx.dirty, 2)) {
|
||||
// Update ADPCM coefficients
|
||||
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 2");
|
||||
AdpcmCoefficients& coeff = channel_adpcm_coeffs[chanid];
|
||||
Audio::UpdateAdpcm(chanid, channel_adpcm_coeffs[chanid].coeff);
|
||||
}
|
||||
|
||||
if (TestAndUnsetBit(ctx.dirty, 17)) {
|
||||
@ -217,16 +222,18 @@ static void AudioTick(u64, int cycles_late) {
|
||||
if (TestAndUnsetBit(ctx.dirty, 18)) {
|
||||
// Rate
|
||||
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 18");
|
||||
LOG_INFO(Service_DSP, "Rate %f", ctx.rate);
|
||||
}
|
||||
|
||||
if (TestAndUnsetBit(ctx.dirty, 22)) {
|
||||
// IIR
|
||||
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 22");
|
||||
LOG_INFO(Service_DSP, "IIR %x", ctx.iirFilterType);
|
||||
}
|
||||
|
||||
if (TestAndUnsetBit(ctx.dirty, 28)) {
|
||||
// Sync count
|
||||
LOG_WARNING(Service_DSP, "(STUB) Update Sync Count");
|
||||
LOG_DEBUG(Service_DSP, "Update Sync Count");
|
||||
|
||||
status0.sync = ctx.sync;
|
||||
status1.sync = ctx.sync;
|
||||
@ -235,12 +242,20 @@ static void AudioTick(u64, int cycles_late) {
|
||||
if (TestAndUnsetBit(ctx.dirty, 25) | TestAndUnsetBit(ctx.dirty, 26) | TestAndUnsetBit(ctx.dirty, 27)) {
|
||||
// Mix
|
||||
LOG_WARNING(Service_DSP, "Unimplemented dirty bit 25/26/27");
|
||||
for (int i = 0; i < 12; i++)
|
||||
LOG_INFO(Service_DSP, "mix[%i] %f", i, ctx.mix[i]);
|
||||
}
|
||||
|
||||
if (TestAndUnsetBit(ctx.dirty, 4) | TestAndUnsetBit(ctx.dirty, 21) | TestAndUnsetBit(ctx.dirty, 30)) {
|
||||
// TODO(merry): One of these bits might merely signify an update to the format. Verify this.
|
||||
// Embedded Buffer Changed
|
||||
Audio::UpdateFormat(chanid, ctx.mono_or_stereo, ctx.format);
|
||||
Audio::UpdateFormat(chanid, ctx.mono_or_stereo, ctx.format, ctx.rest);
|
||||
channel_contex0[chanid].flags1_raw = channel_contex1[chanid].flags1_raw = ctx.flags1_raw;
|
||||
channel_contex0[chanid].flags2_raw = channel_contex1[chanid].flags2_raw = ctx.flags2_raw;
|
||||
if (ctx.rest || ctx.rest2) {
|
||||
LOG_ERROR(Service_DSP, "chan %i rest %04x rest2 %04x", chanid, ctx.rest, ctx.rest2);
|
||||
}
|
||||
Audio::UpdateAdpcm(chanid, channel_adpcm_coeffs[chanid].coeff);
|
||||
Audio::EnqueueBuffer(chanid, ctx.buffer_id,
|
||||
Memory::GetPhysicalPointer(ctx.physical_address), ctx.sample_count,
|
||||
ctx.has_adpcm, ctx.adpcm_ps, ctx.adpcm_yn,
|
||||
@ -270,8 +285,17 @@ static void AudioTick(u64, int cycles_late) {
|
||||
status0.is_playing |= 0x100; // TODO: This is supposed to flicker on then turn off.
|
||||
}
|
||||
|
||||
if (TestAndUnsetBit(ctx.dirty, 16)) {
|
||||
// Is Active?
|
||||
Audio::Play(chanid, (ctx.is_active & 0xFF) != 0);
|
||||
}
|
||||
|
||||
if (ctx.dirty) {
|
||||
LOG_ERROR(Service_DSP, "Unknown channel dirty bits: 0x%08x", ctx.dirty);
|
||||
LOG_ERROR(Service_DSP, "%i Rim %i %i", chanid, ctx.rim[0], ctx.rim[1]);
|
||||
LOG_ERROR(Service_DSP, "%i IIR-type %i", chanid, ctx.iirFilterType);
|
||||
LOG_ERROR(Service_DSP, "%i Mono %f %f", chanid, ctx.iirFilter_mono[0], ctx.iirFilter_mono[1]);
|
||||
LOG_ERROR(Service_DSP, "%i Biquad %f %f %f %f %f", chanid, ctx.iirFilter_biquad[0], ctx.iirFilter_biquad[1], ctx.iirFilter_biquad[2], ctx.iirFilter_biquad[3], ctx.iirFilter_biquad[4]);
|
||||
}
|
||||
|
||||
ctx.dirty = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user