From 5c58186f03fb053038ee804b44051f21623ec62c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 21 Aug 2020 01:14:48 -0700 Subject: [PATCH] GBA Audio: Revamp FIFO emulation (fixes #356, fixes #875, fixes #1847) --- CHANGES | 1 + include/mgba/internal/gba/audio.h | 10 +- include/mgba/internal/gba/serialize.h | 32 ++++-- src/gba/audio.c | 153 ++++++++++++++++++-------- src/gba/io.c | 6 +- src/gba/serialize.c | 2 +- 6 files changed, 142 insertions(+), 62 deletions(-) diff --git a/CHANGES b/CHANGES index bf28a6b04..8b414a59d 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,7 @@ Emulation fixes: - GBA Audio: Fix deserializing SOUNDCNT_L - GBA Audio: Fix stereo in XQ audio - 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: Improve HLE BIOS timing - GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808) diff --git a/include/mgba/internal/gba/audio.h b/include/mgba/internal/gba/audio.h index f08f845b6..212e1b860 100644 --- a/include/mgba/internal/gba/audio.h +++ b/include/mgba/internal/gba/audio.h @@ -15,6 +15,8 @@ CXX_GUARD_START #include #include +#define GBA_AUDIO_FIFO_SIZE 8 + #define MP2K_MAGIC 0x68736D53 #define MP2K_MAX_SOUND_CHANNELS 12 @@ -26,7 +28,11 @@ extern const unsigned GBA_AUDIO_SAMPLES; extern const int GBA_AUDIO_VOLUME_MAX; struct GBAAudioFIFO { - struct CircleBuffer fifo; + uint32_t fifo[GBA_AUDIO_FIFO_SIZE]; + int fifoWrite; + int fifoRead; + uint32_t internalSample; + int internalRemaining; int dmaSource; 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 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); struct GBASerializedState; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 24cc1b85c..2b372e51d 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -20,7 +20,7 @@ extern const uint32_t GBA_SAVESTATE_VERSION; mLOG_DECLARE_CATEGORY(GBA_STATE); /* Savestate format: - * 0x00000 - 0x00003: Version Magic (0x01000001) + * 0x00000 - 0x00003: Version Magic (0x01000004) * 0x00004 - 0x00007: BIOS checksum (e.g. 0xBAAE187F for official BIOS) * 0x00008 - 0x0000B: ROM CRC32 * 0x0000C - 0x0000F: Master cycles @@ -69,10 +69,16 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * 0x0018C - 0x001AB: Audio FIFO 1 * 0x001AC - 0x001CB: Audio FIFO 2 * 0x001CC - 0x001DF: Audio miscellaneous state - * | 0x001CC - 0x001CF: FIFO 1 size - * | 0x001D0 - 0x001D3: Reserved + * | 0x001CC - 0x001CF: Channel A internal audio samples + * | 0x001D0 - 0x001D3: Channel B internal audio samples * | 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 * | 0x001DC - 0x001DC: Channel 1 envelope state * | bits 0 - 3: Current volume @@ -217,6 +223,12 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * 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_BITS(GBASerializedVideoFlags, Mode, 0, 2); @@ -267,12 +279,14 @@ struct GBASerializedState { struct { struct GBSerializedPSGState psg; - uint8_t fifoA[32]; - uint8_t fifoB[32]; - uint32_t fifoSizeA; - int32_t reserved; + uint32_t fifoA[8]; + uint32_t fifoB[8]; + uint32_t internalA; + uint32_t internalB; int32_t nextSample; - uint32_t fifoSizeB; + int8_t sampleA; + int8_t sampleB; + GBASerializedAudioFlags gbaFlags; GBSerializedAudioFlags flags; } audio; diff --git a/src/gba/audio.c b/src/gba/audio.c index c0a275834..0a09868bd 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -23,7 +23,6 @@ mLOG_DEFINE_CATEGORY(GBA_AUDIO, "GBA Audio", "gba.audio"); const unsigned GBA_AUDIO_SAMPLES = 2048; -const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t); const int GBA_AUDIO_VOLUME_MAX = 0x100; 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 blip_set_rates(audio->psg.left, 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->forceDisableChA = false; @@ -64,7 +61,17 @@ void GBAAudioReset(struct GBAAudio* audio) { mTimingSchedule(&audio->p->timing, &audio->sampleEvent, 0); audio->chA.dmaSource = 1; 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->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->sampleRate = 0x8000; audio->soundbias = 0x200; @@ -84,14 +91,10 @@ void GBAAudioReset(struct GBAAudio* audio) { blip_clear(audio->psg.left); blip_clear(audio->psg.right); audio->clock = 0; - CircleBufferClear(&audio->chA.fifo); - CircleBufferClear(&audio->chB.fifo); } void GBAAudioDeinit(struct GBAAudio* audio) { GBAudioDeinit(&audio->psg); - CircleBufferDeinit(&audio->chA.fifo); - CircleBufferDeinit(&audio->chB.fifo); } 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->chBTimer = GBARegisterSOUNDCNT_HIGetChBTimer(value); if (GBARegisterSOUNDCNT_HIIsChAReset(value)) { - CircleBufferClear(&audio->chA.fifo); + audio->chA.fifoWrite = 0; + audio->chA.fifoRead = 0; } 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; } -void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value) { - struct CircleBuffer* fifo; +uint32_t GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value) { + struct GBAAudioFIFO* channel; switch (address) { case REG_FIFO_A_LO: - fifo = &audio->chA.fifo; + channel = &audio->chA; break; case REG_FIFO_B_LO: - fifo = &audio->chB.fifo; + channel = &audio->chB; break; default: 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) { @@ -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); 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]; if (GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM) { dma->when = mTimingCurrentTime(&audio->p->timing) - cycles; dma->nextCount = 4; 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) { @@ -345,37 +365,76 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) { GBAudioPSGSerialize(&audio->psg, &state->audio.psg, &state->audio.flags); - CircleBufferDump(&audio->chA.fifo, state->audio.fifoA, sizeof(state->audio.fifoA)); - CircleBufferDump(&audio->chB.fifo, state->audio.fifoB, sizeof(state->audio.fifoB)); - uint32_t fifoSize = CircleBufferSize(&audio->chA.fifo); - STORE_32(fifoSize, 0, &state->audio.fifoSizeA); - fifoSize = CircleBufferSize(&audio->chB.fifo); - STORE_32(fifoSize, 0, &state->audio.fifoSizeB); + STORE_32(audio->chA.internalSample, 0, &state->audio.internalA); + STORE_32(audio->chB.internalSample, 0, &state->audio.internalB); + state->audio.sampleA = audio->chA.sample; + state->audio.sampleB = audio->chB.sample; + + 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); } void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) { GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags); - CircleBufferClear(&audio->chA.fifo); - CircleBufferClear(&audio->chB.fifo); - uint32_t fifoSize; - LOAD_32(fifoSize, 0, &state->audio.fifoSizeA); - 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(audio->chA.internalSample, 0, &state->audio.internalA); + LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB); + audio->chA.sample = state->audio.sampleA; + audio->chB.sample = state->audio.sampleB; - LOAD_32(fifoSize, 0, &state->audio.fifoSizeB); - if (fifoSize > CircleBufferCapacity(&audio->chB.fifo)) { - fifoSize = CircleBufferCapacity(&audio->chB.fifo); - } - for (i = 0; i < fifoSize; ++i) { - CircleBufferWrite8(&audio->chB.fifo, state->audio.fifoB[i]); + int readA = 0; + int readB = 0; + size_t i; + for (i = 0; i < GBA_AUDIO_FIFO_SIZE; ++i) { + LOAD_32(audio->chA.fifo[readA], i << 2, state->audio.fifoA); + 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; LOAD_32(when, 0, &state->audio.nextSample); diff --git a/src/gba/io.c b/src/gba/io.c index 0c3aea64d..2e0fb576e 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -429,12 +429,12 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { case REG_FIFO_A_LO: case REG_FIFO_B_LO: GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value); - break; + return; case REG_FIFO_A_HI: case REG_FIFO_B_HI: GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); - break; + return; // DMA case REG_DMA0SAD_LO: @@ -630,7 +630,7 @@ void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) { break; case REG_FIFO_A_LO: case REG_FIFO_B_LO: - GBAAudioWriteFIFO(&gba->audio, address, value); + value = GBAAudioWriteFIFO(&gba->audio, address, value); break; case REG_DMA0SAD_LO: value = GBADMAWriteSAD(gba, 0, value); diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 8732d2ec1..fe4e15bef 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -15,7 +15,7 @@ #include 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");