all repos — mgba @ fd2869603a8ead37a697ac3340e5f75c39843a55

mGBA Game Boy Advance Emulator

DS Audio: First pass at ADPCM
Vicki Pfau vi@endrift.com
Wed, 08 Mar 2017 12:01:14 -0800
commit

fd2869603a8ead37a697ac3340e5f75c39843a55

parent

414152d14801d05b9ebfa0bb885c35fc20cc002e

2 files changed, 75 insertions(+), 5 deletions(-)

jump to
M include/mgba/internal/ds/audio.hinclude/mgba/internal/ds/audio.h

@@ -49,10 +49,10 @@

int adpcmOffset; int16_t adpcmStartSample; - unsigned adpcmStartIndex; + int adpcmStartIndex; int16_t adpcmSample; - unsigned adpcmIndex; + int adpcmIndex; bool enable; };
M src/ds/audio.csrc/ds/audio.c

@@ -19,6 +19,21 @@ static void _updateChannel(struct mTiming* timing, void* user, uint32_t cyclesLate);

static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate); static void _updateMixer(struct DSAudio*); +static const int _adpcmIndexTable[8] = { + -1, -1, -1, -1, 2, 4, 6, 8 +}; + +static const uint16_t _adpcmTable[89] = { + 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x0010, 0x0011, 0x0013, 0x0015, + 0x0017, 0x0019, 0x001C, 0x001F, 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042, + 0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F, 0x009D, 0x00AD, 0x00BE, 0x00D1, + 0x00E6, 0x00FD, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292, + 0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, 0x0610, 0x06AB, 0x0756, 0x0812, + 0x08E0, 0x09C3, 0x0ABD, 0x0BD0, 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954, + 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, 0x3BB9, 0x41B2, 0x4844, 0x4F7E, + 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF +}; + void DSAudioInit(struct DSAudio* audio, size_t samples) { audio->samples = samples; audio->left = blip_new(BLIP_BUFFER_SIZE);

@@ -79,6 +94,11 @@ audio->ch[ch].loopPoint = 0;

audio->ch[ch].length = 0; audio->ch[ch].offset = 0; audio->ch[ch].sample = 0; + audio->ch[ch].adpcmOffset = 0; + audio->ch[ch].adpcmStartSample = 0; + audio->ch[ch].adpcmStartIndex = 0; + audio->ch[ch].adpcmSample = 0; + audio->ch[ch].adpcmIndex = 0; } blip_clear(audio->left);

@@ -113,7 +133,7 @@ ch->panning = DSRegisterSOUNDxCNTGetPanning(reg);

ch->repeat = DSRegisterSOUNDxCNTGetRepeat(reg); ch->format = DSRegisterSOUNDxCNTGetFormat(reg); - if (ch->format >= 2) { + if (ch->format > 2) { mLOG(DS_AUDIO, STUB, "Unimplemented audio format %i", ch->format); }

@@ -123,6 +143,12 @@ } else if (!ch->enable && DSRegisterSOUNDxCNTIsBusy(reg)) {

ch->offset = 0; mTimingDeschedule(&audio->p->ds7.timing, &ch->updateEvent); mTimingSchedule(&audio->p->ds7.timing, &ch->updateEvent, 0); + if (ch->format == 2) { + uint32_t header = audio->p->ds7.cpu->memory.load32(audio->p->ds7.cpu, ch->source, NULL); + ch->offset += 4; + ch->adpcmStartSample = header &= 0xFFFF; + ch->adpcmStartIndex = header >> 16; + } } ch->enable = DSRegisterSOUNDxCNTIsBusy(reg); }

@@ -165,6 +191,41 @@ audio->sampleLeft = sampleLeft >> 6;

audio->sampleRight = sampleRight >> 6; } +static void _updateAdpcm(struct DSAudioChannel* ch, int sample) { + if (ch->adpcmIndex < 0) { + ch->adpcmIndex = 0; + } else if (ch->adpcmIndex > 88) { + ch->adpcmIndex = 88; + } + int16_t diff = _adpcmTable[ch->adpcmIndex] >> 3; + if (sample & 1) { + diff += _adpcmTable[ch->adpcmIndex] >> 2; + } + if (sample & 2) { + diff += _adpcmTable[ch->adpcmIndex] >> 1; + } + if (sample & 4) { + diff += _adpcmTable[ch->adpcmIndex]; + } + if (sample & 8) { + int32_t newSample = ch->adpcmSample - diff; + if (newSample < -0x7FFF) { + ch->adpcmSample = -0x7FFF; + } else { + ch->adpcmSample = newSample; + } + } else { + int32_t newSample = ch->adpcmSample + diff; + if (newSample > 0x7FFF) { + ch->adpcmSample = 0x7FFF; + } else { + ch->adpcmSample = newSample; + } + } + ch->sample = ch->adpcmSample; + ch->adpcmIndex += _adpcmIndexTable[sample & 0x7]; +} + static void _updateChannel(struct mTiming* timing, void* user, uint32_t cyclesLate) { struct DSAudioChannel* ch = user; struct ARMCore* cpu = ch->p->p->ds7.cpu;

@@ -178,8 +239,13 @@ ch->sample = cpu->memory.load16(cpu, ch->offset + ch->source, NULL);

ch->offset += 2; break; case 2: - // TODO - ch->enable = false; + _updateAdpcm(ch, (cpu->memory.load8(cpu, ch->offset + ch->source, NULL) >> ch->adpcmOffset) & 0xF); + ch->offset += ch->adpcmOffset >> 2; + ch->adpcmOffset ^= 4; + if (ch->offset == ch->loopPoint) { + ch->adpcmStartSample = ch->adpcmSample; + ch->adpcmStartIndex = ch->adpcmIndex; + } break; } _updateMixer(ch->p);

@@ -193,6 +259,10 @@ case 2:

if (ch->offset >= ch->length) { ch->enable = false; ch->p->p->memory.io7[(DS7_REG_SOUND0CNT_HI + (ch->index << 4)) >> 1] &= 0x7FFF; + if (ch->format == 2) { + ch->adpcmSample = ch->adpcmStartSample; + ch->adpcmIndex = ch->adpcmStartIndex; + } } break; }