GBA Audio: Revamp FIFO emulation (fixes #356, fixes #875, fixes #1847)

This commit is contained in:
Vicki Pfau 2020-08-21 01:14:48 -07:00
parent 0a52f44fa8
commit 5c58186f03
6 changed files with 142 additions and 62 deletions

View File

@ -22,6 +22,7 @@ Emulation fixes:
- GBA Audio: Fix deserializing SOUNDCNT_L - GBA Audio: Fix deserializing SOUNDCNT_L
- GBA Audio: Fix stereo in XQ audio - GBA Audio: Fix stereo in XQ audio
- GBA Audio: Fix volume/mute in XQ audio (fixes mgba.io/i/1864) - GBA Audio: Fix volume/mute in XQ audio (fixes mgba.io/i/1864)
- GBA Audio: Revamp FIFO emulation (fixes mgba.io/i/356, mgba.io/i/875, mgba.io/i/1847)
- GBA BIOS: Implement dummy sound driver calls - GBA BIOS: Implement dummy sound driver calls
- GBA BIOS: Improve HLE BIOS timing - GBA BIOS: Improve HLE BIOS timing
- GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808) - GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808)

View File

@ -15,6 +15,8 @@ CXX_GUARD_START
#include <mgba/internal/gb/audio.h> #include <mgba/internal/gb/audio.h>
#include <mgba-util/circle-buffer.h> #include <mgba-util/circle-buffer.h>
#define GBA_AUDIO_FIFO_SIZE 8
#define MP2K_MAGIC 0x68736D53 #define MP2K_MAGIC 0x68736D53
#define MP2K_MAX_SOUND_CHANNELS 12 #define MP2K_MAX_SOUND_CHANNELS 12
@ -26,7 +28,11 @@ extern const unsigned GBA_AUDIO_SAMPLES;
extern const int GBA_AUDIO_VOLUME_MAX; extern const int GBA_AUDIO_VOLUME_MAX;
struct GBAAudioFIFO { struct GBAAudioFIFO {
struct CircleBuffer fifo; uint32_t fifo[GBA_AUDIO_FIFO_SIZE];
int fifoWrite;
int fifoRead;
uint32_t internalSample;
int internalRemaining;
int dmaSource; int dmaSource;
int8_t sample; int8_t sample;
}; };
@ -298,7 +304,7 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value);
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value); void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value);
void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value); void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value);
void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value); uint32_t GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value);
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles); void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles);
struct GBASerializedState; struct GBASerializedState;

View File

@ -20,7 +20,7 @@ extern const uint32_t GBA_SAVESTATE_VERSION;
mLOG_DECLARE_CATEGORY(GBA_STATE); mLOG_DECLARE_CATEGORY(GBA_STATE);
/* Savestate format: /* Savestate format:
* 0x00000 - 0x00003: Version Magic (0x01000001) * 0x00000 - 0x00003: Version Magic (0x01000004)
* 0x00004 - 0x00007: BIOS checksum (e.g. 0xBAAE187F for official BIOS) * 0x00004 - 0x00007: BIOS checksum (e.g. 0xBAAE187F for official BIOS)
* 0x00008 - 0x0000B: ROM CRC32 * 0x00008 - 0x0000B: ROM CRC32
* 0x0000C - 0x0000F: Master cycles * 0x0000C - 0x0000F: Master cycles
@ -69,10 +69,16 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* 0x0018C - 0x001AB: Audio FIFO 1 * 0x0018C - 0x001AB: Audio FIFO 1
* 0x001AC - 0x001CB: Audio FIFO 2 * 0x001AC - 0x001CB: Audio FIFO 2
* 0x001CC - 0x001DF: Audio miscellaneous state * 0x001CC - 0x001DF: Audio miscellaneous state
* | 0x001CC - 0x001CF: FIFO 1 size * | 0x001CC - 0x001CF: Channel A internal audio samples
* | 0x001D0 - 0x001D3: Reserved * | 0x001D0 - 0x001D3: Channel B internal audio samples
* | 0x001D4 - 0x001D7: Next sample * | 0x001D4 - 0x001D7: Next sample
* | 0x001D8 - 0x001DB: FIFO 2 size * | 0x001D8: Channel A current sample
* | 0x001D9: Channel B current sample
* | 0x001DA - 0x001DB: Flags
* | bits 0 - 1: Channel B internal samples remaining
* | bits 2 - 4: Channel B readable words
* | bits 5 - 6: Channel A internal samples remaining
* | bits 7 - 9: Channel A readable words
* | TODO: Fix this, they're in big-endian order, but field is little-endian * | TODO: Fix this, they're in big-endian order, but field is little-endian
* | 0x001DC - 0x001DC: Channel 1 envelope state * | 0x001DC - 0x001DC: Channel 1 envelope state
* | bits 0 - 3: Current volume * | bits 0 - 3: Current volume
@ -217,6 +223,12 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* Total size: 0x61000 (397,312) bytes * Total size: 0x61000 (397,312) bytes
*/ */
DECL_BITFIELD(GBASerializedAudioFlags, uint16_t);
DECL_BITS(GBASerializedAudioFlags, FIFOInternalSamplesB, 0, 2);
DECL_BITS(GBASerializedAudioFlags, FIFOSamplesB, 2, 3); // Yay legacy?
DECL_BITS(GBASerializedAudioFlags, FIFOInternalSamplesA, 5, 2);
DECL_BITS(GBASerializedAudioFlags, FIFOSamplesA, 7, 3);
DECL_BITFIELD(GBASerializedVideoFlags, uint32_t); DECL_BITFIELD(GBASerializedVideoFlags, uint32_t);
DECL_BITS(GBASerializedVideoFlags, Mode, 0, 2); DECL_BITS(GBASerializedVideoFlags, Mode, 0, 2);
@ -267,12 +279,14 @@ struct GBASerializedState {
struct { struct {
struct GBSerializedPSGState psg; struct GBSerializedPSGState psg;
uint8_t fifoA[32]; uint32_t fifoA[8];
uint8_t fifoB[32]; uint32_t fifoB[8];
uint32_t fifoSizeA; uint32_t internalA;
int32_t reserved; uint32_t internalB;
int32_t nextSample; int32_t nextSample;
uint32_t fifoSizeB; int8_t sampleA;
int8_t sampleB;
GBASerializedAudioFlags gbaFlags;
GBSerializedAudioFlags flags; GBSerializedAudioFlags flags;
} audio; } audio;

View File

@ -23,7 +23,6 @@
mLOG_DEFINE_CATEGORY(GBA_AUDIO, "GBA Audio", "gba.audio"); mLOG_DEFINE_CATEGORY(GBA_AUDIO, "GBA Audio", "gba.audio");
const unsigned GBA_AUDIO_SAMPLES = 2048; const unsigned GBA_AUDIO_SAMPLES = 2048;
const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t);
const int GBA_AUDIO_VOLUME_MAX = 0x100; const int GBA_AUDIO_VOLUME_MAX = 0x100;
static const int CLOCKS_PER_FRAME = 0x800; static const int CLOCKS_PER_FRAME = 0x800;
@ -48,8 +47,6 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) {
// Guess too large; we hang producing extra samples if we guess too low // Guess too large; we hang producing extra samples if we guess too low
blip_set_rates(audio->psg.left, GBA_ARM7TDMI_FREQUENCY, 96000); blip_set_rates(audio->psg.left, GBA_ARM7TDMI_FREQUENCY, 96000);
blip_set_rates(audio->psg.right, GBA_ARM7TDMI_FREQUENCY, 96000); blip_set_rates(audio->psg.right, GBA_ARM7TDMI_FREQUENCY, 96000);
CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE);
CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE);
audio->externalMixing = false; audio->externalMixing = false;
audio->forceDisableChA = false; audio->forceDisableChA = false;
@ -64,7 +61,17 @@ void GBAAudioReset(struct GBAAudio* audio) {
mTimingSchedule(&audio->p->timing, &audio->sampleEvent, 0); mTimingSchedule(&audio->p->timing, &audio->sampleEvent, 0);
audio->chA.dmaSource = 1; audio->chA.dmaSource = 1;
audio->chB.dmaSource = 2; audio->chB.dmaSource = 2;
audio->chA.fifoWrite = 0;
audio->chA.fifoRead = 0;
audio->chA.internalSample = 0;
audio->chA.internalRemaining = 0;
memset(audio->chA.fifo, 0, sizeof(audio->chA.fifo));
audio->chA.sample = 0; audio->chA.sample = 0;
audio->chB.fifoWrite = 0;
audio->chB.fifoRead = 0;
audio->chB.internalSample = 0;
audio->chB.internalRemaining = 0;
memset(audio->chB.fifo, 0, sizeof(audio->chB.fifo));
audio->chB.sample = 0; audio->chB.sample = 0;
audio->sampleRate = 0x8000; audio->sampleRate = 0x8000;
audio->soundbias = 0x200; audio->soundbias = 0x200;
@ -84,14 +91,10 @@ void GBAAudioReset(struct GBAAudio* audio) {
blip_clear(audio->psg.left); blip_clear(audio->psg.left);
blip_clear(audio->psg.right); blip_clear(audio->psg.right);
audio->clock = 0; audio->clock = 0;
CircleBufferClear(&audio->chA.fifo);
CircleBufferClear(&audio->chB.fifo);
} }
void GBAAudioDeinit(struct GBAAudio* audio) { void GBAAudioDeinit(struct GBAAudio* audio) {
GBAudioDeinit(&audio->psg); GBAudioDeinit(&audio->psg);
CircleBufferDeinit(&audio->chA.fifo);
CircleBufferDeinit(&audio->chB.fifo);
} }
void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) { void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
@ -199,10 +202,12 @@ void GBAAudioWriteSOUNDCNT_HI(struct GBAAudio* audio, uint16_t value) {
audio->chBLeft = GBARegisterSOUNDCNT_HIGetChBLeft(value); audio->chBLeft = GBARegisterSOUNDCNT_HIGetChBLeft(value);
audio->chBTimer = GBARegisterSOUNDCNT_HIGetChBTimer(value); audio->chBTimer = GBARegisterSOUNDCNT_HIGetChBTimer(value);
if (GBARegisterSOUNDCNT_HIIsChAReset(value)) { if (GBARegisterSOUNDCNT_HIIsChAReset(value)) {
CircleBufferClear(&audio->chA.fifo); audio->chA.fifoWrite = 0;
audio->chA.fifoRead = 0;
} }
if (GBARegisterSOUNDCNT_HIIsChBReset(value)) { if (GBARegisterSOUNDCNT_HIIsChBReset(value)) {
CircleBufferClear(&audio->chB.fifo); audio->chB.fifoWrite = 0;
audio->chB.fifoRead = 0;
} }
} }
@ -219,26 +224,25 @@ void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) {
audio->psg.ch3.wavedata32[address | (!audio->psg.ch3.bank * 4)] = value; audio->psg.ch3.wavedata32[address | (!audio->psg.ch3.bank * 4)] = value;
} }
void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value) { uint32_t GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value) {
struct CircleBuffer* fifo; struct GBAAudioFIFO* channel;
switch (address) { switch (address) {
case REG_FIFO_A_LO: case REG_FIFO_A_LO:
fifo = &audio->chA.fifo; channel = &audio->chA;
break; break;
case REG_FIFO_B_LO: case REG_FIFO_B_LO:
fifo = &audio->chB.fifo; channel = &audio->chB;
break; break;
default: default:
mLOG(GBA_AUDIO, ERROR, "Bad FIFO write to address 0x%03x", address); mLOG(GBA_AUDIO, ERROR, "Bad FIFO write to address 0x%03x", address);
return; return value;
}
int i;
for (i = 0; i < 4; ++i) {
while (!CircleBufferWrite8(fifo, value >> (8 * i))) {
int8_t dummy;
CircleBufferRead8(fifo, &dummy);
} }
channel->fifo[channel->fifoWrite] = value;
++channel->fifoWrite;
if (channel->fifoWrite == GBA_AUDIO_FIFO_SIZE) {
channel->fifoWrite = 0;
} }
return channel->fifo[channel->fifoWrite];
} }
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
@ -251,17 +255,33 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
mLOG(GBA_AUDIO, ERROR, "Bad FIFO write to address 0x%03x", fifoId); mLOG(GBA_AUDIO, ERROR, "Bad FIFO write to address 0x%03x", fifoId);
return; return;
} }
if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) { int fifoSize;
if (channel->fifoWrite >= channel->fifoRead) {
fifoSize = channel->fifoWrite - channel->fifoRead;
} else {
fifoSize = GBA_AUDIO_FIFO_SIZE - channel->fifoRead + channel->fifoWrite;
}
if (GBA_AUDIO_FIFO_SIZE - fifoSize > 4 && channel->dmaSource > 0) {
struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource]; struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource];
if (GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM) { if (GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM) {
dma->when = mTimingCurrentTime(&audio->p->timing) - cycles; dma->when = mTimingCurrentTime(&audio->p->timing) - cycles;
dma->nextCount = 4; dma->nextCount = 4;
GBADMASchedule(audio->p, channel->dmaSource, dma); GBADMASchedule(audio->p, channel->dmaSource, dma);
} else {
channel->dmaSource = 0;
} }
} }
CircleBufferRead8(&channel->fifo, (int8_t*) &channel->sample); if (!channel->internalRemaining && fifoSize) {
channel->internalSample = channel->fifo[channel->fifoRead];
channel->internalRemaining = 4;
++channel->fifoRead;
if (channel->fifoRead == GBA_AUDIO_FIFO_SIZE) {
channel->fifoRead = 0;
}
}
channel->sample = channel->internalSample;
if (channel->internalRemaining) {
channel->internalSample >>= 8;
--channel->internalRemaining;
}
} }
static int _applyBias(struct GBAAudio* audio, int sample) { static int _applyBias(struct GBAAudio* audio, int sample) {
@ -345,37 +365,76 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) { void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
GBAudioPSGSerialize(&audio->psg, &state->audio.psg, &state->audio.flags); GBAudioPSGSerialize(&audio->psg, &state->audio.psg, &state->audio.flags);
CircleBufferDump(&audio->chA.fifo, state->audio.fifoA, sizeof(state->audio.fifoA)); STORE_32(audio->chA.internalSample, 0, &state->audio.internalA);
CircleBufferDump(&audio->chB.fifo, state->audio.fifoB, sizeof(state->audio.fifoB)); STORE_32(audio->chB.internalSample, 0, &state->audio.internalB);
uint32_t fifoSize = CircleBufferSize(&audio->chA.fifo); state->audio.sampleA = audio->chA.sample;
STORE_32(fifoSize, 0, &state->audio.fifoSizeA); state->audio.sampleB = audio->chB.sample;
fifoSize = CircleBufferSize(&audio->chB.fifo);
STORE_32(fifoSize, 0, &state->audio.fifoSizeB); int readA = audio->chA.fifoRead;
int readB = audio->chB.fifoRead;
size_t i;
for (i = 0; i < GBA_AUDIO_FIFO_SIZE; ++i) {
STORE_32(audio->chA.fifo[readA], i << 2, state->audio.fifoA);
STORE_32(audio->chB.fifo[readB], i << 2, state->audio.fifoB);
++readA;
if (readA == GBA_AUDIO_FIFO_SIZE) {
readA = 0;
}
++readB;
if (readB == GBA_AUDIO_FIFO_SIZE) {
readB = 0;
}
}
int fifoSizeA;
if (audio->chA.fifoWrite >= audio->chA.fifoRead) {
fifoSizeA = audio->chA.fifoWrite - audio->chA.fifoRead;
} else {
fifoSizeA = GBA_AUDIO_FIFO_SIZE - audio->chA.fifoRead + audio->chA.fifoWrite;
}
int fifoSizeB;
if (audio->chB.fifoWrite >= audio->chB.fifoRead) {
fifoSizeB = audio->chB.fifoWrite - audio->chB.fifoRead;
} else {
fifoSizeB = GBA_AUDIO_FIFO_SIZE - audio->chB.fifoRead + audio->chB.fifoWrite;
}
GBASerializedAudioFlags flags = 0;
flags = GBASerializedAudioFlagsSetFIFOSamplesA(flags, fifoSizeA);
flags = GBASerializedAudioFlagsSetFIFOSamplesB(flags, fifoSizeB);
flags = GBASerializedAudioFlagsSetFIFOInternalSamplesA(flags, audio->chA.internalRemaining);
flags = GBASerializedAudioFlagsSetFIFOInternalSamplesB(flags, audio->chB.internalRemaining);
STORE_32(flags, 0, &state->audio.gbaFlags);
STORE_32(audio->sampleEvent.when - mTimingCurrentTime(&audio->p->timing), 0, &state->audio.nextSample); STORE_32(audio->sampleEvent.when - mTimingCurrentTime(&audio->p->timing), 0, &state->audio.nextSample);
} }
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) { void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) {
GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags); GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags);
CircleBufferClear(&audio->chA.fifo); LOAD_32(audio->chA.internalSample, 0, &state->audio.internalA);
CircleBufferClear(&audio->chB.fifo); LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB);
uint32_t fifoSize; audio->chA.sample = state->audio.sampleA;
LOAD_32(fifoSize, 0, &state->audio.fifoSizeA); audio->chB.sample = state->audio.sampleB;
if (fifoSize > CircleBufferCapacity(&audio->chA.fifo)) {
fifoSize = CircleBufferCapacity(&audio->chA.fifo);
}
size_t i;
for (i = 0; i < fifoSize; ++i) {
CircleBufferWrite8(&audio->chA.fifo, state->audio.fifoA[i]);
}
LOAD_32(fifoSize, 0, &state->audio.fifoSizeB); int readA = 0;
if (fifoSize > CircleBufferCapacity(&audio->chB.fifo)) { int readB = 0;
fifoSize = CircleBufferCapacity(&audio->chB.fifo); size_t i;
} for (i = 0; i < GBA_AUDIO_FIFO_SIZE; ++i) {
for (i = 0; i < fifoSize; ++i) { LOAD_32(audio->chA.fifo[readA], i << 2, state->audio.fifoA);
CircleBufferWrite8(&audio->chB.fifo, state->audio.fifoB[i]); LOAD_32(audio->chB.fifo[readB], i << 2, state->audio.fifoB);
++readA;
++readB;
} }
audio->chA.fifoRead = 0;
audio->chB.fifoRead = 0;
GBASerializedAudioFlags flags;
LOAD_32(flags, 0, &state->audio.gbaFlags);
audio->chA.fifoWrite = GBASerializedAudioFlagsGetFIFOSamplesA(flags);
audio->chB.fifoWrite = GBASerializedAudioFlagsGetFIFOSamplesB(flags);
audio->chA.internalRemaining = GBASerializedAudioFlagsGetFIFOInternalSamplesA(flags);
audio->chB.internalRemaining = GBASerializedAudioFlagsGetFIFOInternalSamplesB(flags);
uint32_t when; uint32_t when;
LOAD_32(when, 0, &state->audio.nextSample); LOAD_32(when, 0, &state->audio.nextSample);

View File

@ -429,12 +429,12 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
case REG_FIFO_A_LO: case REG_FIFO_A_LO:
case REG_FIFO_B_LO: case REG_FIFO_B_LO:
GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value); GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value);
break; return;
case REG_FIFO_A_HI: case REG_FIFO_A_HI:
case REG_FIFO_B_HI: case REG_FIFO_B_HI:
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
break; return;
// DMA // DMA
case REG_DMA0SAD_LO: case REG_DMA0SAD_LO:
@ -630,7 +630,7 @@ void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) {
break; break;
case REG_FIFO_A_LO: case REG_FIFO_A_LO:
case REG_FIFO_B_LO: case REG_FIFO_B_LO:
GBAAudioWriteFIFO(&gba->audio, address, value); value = GBAAudioWriteFIFO(&gba->audio, address, value);
break; break;
case REG_DMA0SAD_LO: case REG_DMA0SAD_LO:
value = GBADMAWriteSAD(gba, 0, value); value = GBADMAWriteSAD(gba, 0, value);

View File

@ -15,7 +15,7 @@
#include <fcntl.h> #include <fcntl.h>
const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000; const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
const uint32_t GBA_SAVESTATE_VERSION = 0x00000003; const uint32_t GBA_SAVESTATE_VERSION = 0x00000004;
mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize"); mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize");