mirror of https://github.com/mgba-emu/mgba.git
parent
0a52f44fa8
commit
5c58186f03
1
CHANGES
1
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)
|
||||
|
|
|
@ -15,6 +15,8 @@ CXX_GUARD_START
|
|||
#include <mgba/internal/gb/audio.h>
|
||||
#include <mgba-util/circle-buffer.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
153
src/gba/audio.c
153
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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <fcntl.h>
|
||||
|
||||
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");
|
||||
|
||||
|
|
Loading…
Reference in New Issue