From 4e2052f9341e05a47a20e4060d5dd56d741e0088 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 18 May 2019 18:42:45 -0700 Subject: [PATCH] GBA Audio: Experimental HLE audio --- CMakeLists.txt | 1 + include/mgba/core/cpu.h | 4 + include/mgba/internal/gba/audio.h | 189 +++++++++++ include/mgba/internal/gba/extra/audio-mixer.h | 19 ++ src/gba/audio.c | 50 ++- src/gba/core.c | 18 ++ src/gba/extra/audio-mixer.c | 304 ++++++++++++++++++ src/gba/gba.c | 4 + 8 files changed, 575 insertions(+), 14 deletions(-) create mode 100644 include/mgba/internal/gba/extra/audio-mixer.h create mode 100644 src/gba/extra/audio-mixer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d25191081..ed522e70b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,7 @@ file(GLOB GBA_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/test/*.c) file(GLOB GB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/*.c) file(GLOB GB_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/test/*.c) file(GLOB GBA_CHEATS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/cheats/*.c) +file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/audio-mixer.c) file(GLOB GBA_RR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB CORE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/core/*.c) file(GLOB CORE_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/core/test/*.c) diff --git a/include/mgba/core/cpu.h b/include/mgba/core/cpu.h index fe6caedd3..71f3398c2 100644 --- a/include/mgba/core/cpu.h +++ b/include/mgba/core/cpu.h @@ -13,6 +13,10 @@ CXX_GUARD_START enum mCPUComponentType { CPU_COMPONENT_DEBUGGER, CPU_COMPONENT_CHEAT_DEVICE, + CPU_COMPONENT_MISC_1, + CPU_COMPONENT_MISC_2, + CPU_COMPONENT_MISC_3, + CPU_COMPONENT_MISC_4, CPU_COMPONENT_MAX }; diff --git a/include/mgba/internal/gba/audio.h b/include/mgba/internal/gba/audio.h index 10f5a7e65..f08f845b6 100644 --- a/include/mgba/internal/gba/audio.h +++ b/include/mgba/internal/gba/audio.h @@ -10,10 +10,14 @@ CXX_GUARD_START +#include #include #include #include +#define MP2K_MAGIC 0x68736D53 +#define MP2K_MAX_SOUND_CHANNELS 12 + mLOG_DECLARE_CATEGORY(GBA_AUDIO); struct GBADMA; @@ -44,6 +48,7 @@ DECL_BITFIELD(GBARegisterSOUNDBIAS, uint16_t); DECL_BITS(GBARegisterSOUNDBIAS, Bias, 0, 10); DECL_BITS(GBARegisterSOUNDBIAS, Resolution, 14, 2); +struct GBAAudioMixer; struct GBAAudio { struct GBA* p; @@ -71,6 +76,8 @@ struct GBAAudio { GBARegisterSOUNDBIAS soundbias; + struct GBAAudioMixer* mixer; + bool externalMixing; int32_t sampleInterval; bool forceDisableChA; @@ -85,6 +92,188 @@ struct GBAStereoSample { int16_t right; }; +struct GBAMP2kADSR { + uint8_t attack; + uint8_t decay; + uint8_t sustain; + uint8_t release; +}; + +struct GBAMP2kSoundChannel { + uint8_t status; + uint8_t type; + uint8_t rightVolume; + uint8_t leftVolume; + struct GBAMP2kADSR adsr; + uint8_t ky; + uint8_t envelopeV; + uint8_t envelopeRight; + uint8_t envelopeLeft; + uint8_t echoVolume; + uint8_t echoLength; + uint8_t d1; + uint8_t d2; + uint8_t gt; + uint8_t midiKey; + uint8_t ve; + uint8_t pr; + uint8_t rp; + uint8_t d3[3]; + uint32_t ct; + uint32_t fw; + uint32_t freq; + uint32_t waveData; + uint32_t cp; + uint32_t track; + uint32_t pp; + uint32_t np; + uint32_t d4; + uint16_t xpi; + uint16_t xpc; +}; + +struct GBAMP2kContext { + uint32_t magic; + uint8_t pcmDmaCounter; + uint8_t reverb; + uint8_t maxChans; + uint8_t masterVolume; + uint8_t freq; + uint8_t mode; + uint8_t c15; + uint8_t pcmDmaPeriod; + uint8_t maxLines; + uint8_t gap[3]; + int32_t pcmSamplesPerVBlank; + int32_t pcmFreq; + int32_t divFreq; + uint32_t cgbChans; + uint32_t func; + uint32_t intp; + uint32_t cgbSound; + uint32_t cgbOscOff; + uint32_t midiKeyToCgbFreq; + uint32_t mPlayJumpTable; + uint32_t plynote; + uint32_t extVolPit; + uint8_t gap2[16]; + struct GBAMP2kSoundChannel chans[MP2K_MAX_SOUND_CHANNELS]; +}; + +struct GBAMP2kMusicPlayerInfo { + uint32_t songHeader; + uint32_t status; + uint8_t trackCount; + uint8_t priority; + uint8_t cmd; + uint8_t unk_B; + uint32_t clock; + uint8_t gap[8]; + uint32_t memAccArea; + uint16_t tempoD; + uint16_t tempoU; + uint16_t tempoI; + uint16_t tempoC; + uint16_t fadeOI; + uint16_t fadeOC; + uint16_t fadeOV; + uint32_t tracks; + uint32_t tone; + uint32_t magic; + uint32_t func; + uint32_t intp; +}; + +struct GBAMP2kInstrument { + uint8_t type; + uint8_t key; + uint8_t length; + union { + uint8_t pan; + uint8_t sweep; + } ps; + union { + uint32_t waveData; + uint32_t subTable; + } data; + union { + struct GBAMP2kADSR adsr; + uint32_t map; + } extInfo; +}; + +struct GBAMP2kMusicPlayerTrack { + uint8_t flags; + uint8_t wait; + uint8_t patternLevel; + uint8_t repN; + uint8_t gateTime; + uint8_t key; + uint8_t velocity; + uint8_t runningStatus; + uint8_t keyM; + uint8_t pitM; + int8_t keyShift; + int8_t keyShiftX; + int8_t tune; + uint8_t pitX; + int8_t bend; + uint8_t bendRange; + uint8_t volMR; + uint8_t volML; + uint8_t vol; + uint8_t volX; + int8_t pan; + int8_t panX; + int8_t modM; + uint8_t mod; + uint8_t modT; + uint8_t lfoSpeed; + uint8_t lfoSpeedC; + uint8_t lfoDelay; + uint8_t lfoDelayC; + uint8_t priority; + uint8_t echoVolume; + uint8_t echoLength; + uint32_t chan; + struct GBAMP2kInstrument instrument; + uint8_t gap[10]; + uint16_t unk_3A; + uint32_t unk_3C; + uint32_t cmdPtr; + uint32_t patternStack[3]; +}; + +struct GBAMP2kTrack { + struct GBAMP2kMusicPlayerTrack track; + struct GBAMP2kSoundChannel* channel; + uint8_t lastCommand; + struct CircleBuffer buffer; + uint32_t samplePlaying; + float currentOffset; + bool waiting; +}; + +struct GBAAudioMixer { + struct mCPUComponent d; + struct GBAAudio* p; + + uint32_t contextAddress; + + bool (*engage)(struct GBAAudioMixer* mixer, uint32_t address); + void (*vblank)(struct GBAAudioMixer* mixer); + void (*step)(struct GBAAudioMixer* mixer); + + struct GBAMP2kContext context; + struct GBAMP2kMusicPlayerInfo player; + struct GBAMP2kTrack activeTracks[MP2K_MAX_SOUND_CHANNELS]; + + double tempo; + double frame; + + struct GBAStereoSample last; +}; + void GBAAudioInit(struct GBAAudio* audio, size_t samples); void GBAAudioReset(struct GBAAudio* audio); void GBAAudioDeinit(struct GBAAudio* audio); diff --git a/include/mgba/internal/gba/extra/audio-mixer.h b/include/mgba/internal/gba/extra/audio-mixer.h new file mode 100644 index 000000000..369a4aaa3 --- /dev/null +++ b/include/mgba/internal/gba/extra/audio-mixer.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_AUDIO_MIXER_H +#define GBA_AUDIO_MIXER_H + +#include + +CXX_GUARD_START + +#include + +void GBAAudioMixerCreate(struct GBAAudioMixer* mixer); + +CXX_GUARD_END + +#endif diff --git a/src/gba/audio.c b/src/gba/audio.c index c0ed7cf4c..b337043a3 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -14,6 +14,8 @@ #include #include +#define MP2K_LOCK_MAX 8 + #ifdef _3DS #define blip_add_delta blip_add_delta_fast #endif @@ -24,7 +26,7 @@ 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 = 0x400; +static const int CLOCKS_PER_FRAME = 0x800; static int _applyBias(struct GBAAudio* audio, int sample); static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate); @@ -49,6 +51,7 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) { CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE); CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE); + audio->externalMixing = false; audio->forceDisableChA = false; audio->forceDisableChB = false; audio->masterVolume = GBA_AUDIO_VOLUME_MAX; @@ -111,6 +114,20 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA* mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest); return; } + uint32_t source = info->source; + uint32_t magic[2] = { + audio->p->cpu->memory.load32(audio->p->cpu, source - 0x350, NULL), + audio->p->cpu->memory.load32(audio->p->cpu, source - 0x980, NULL) + }; + if (audio->mixer) { + if (magic[0] - MP2K_MAGIC <= MP2K_LOCK_MAX) { + audio->mixer->engage(audio->mixer, source - 0x350); + } else if (magic[1] - MP2K_MAGIC <= MP2K_LOCK_MAX) { + audio->mixer->engage(audio->mixer, source - 0x980); + } else { + audio->externalMixing = false; + } + } info->reg = GBADMARegisterSetDestControl(info->reg, GBA_DMA_FIXED); info->reg = GBADMARegisterSetWidth(info->reg, 1); } @@ -265,23 +282,28 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { sampleLeft >>= psgShift; sampleRight >>= psgShift; - if (!audio->forceDisableChA) { - if (audio->chALeft) { - sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; - } - - if (audio->chARight) { - sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; - } + if (audio->mixer) { + audio->mixer->step(audio->mixer); } + if (!audio->externalMixing) { + if (!audio->forceDisableChA) { + if (audio->chALeft) { + sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA; + } - if (!audio->forceDisableChB) { - if (audio->chBLeft) { - sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + if (audio->chARight) { + sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA; + } } - if (audio->chBRight) { - sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + if (!audio->forceDisableChB) { + if (audio->chBLeft) { + sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB; + } + + if (audio->chBRight) { + sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; + } } } diff --git a/src/gba/core.c b/src/gba/core.c index 44d125f73..6b9061f8f 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #ifndef DISABLE_THREADING @@ -123,6 +124,9 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { }; struct mVideoLogContext; + +#define CPU_COMPONENT_AUDIO_MIXER CPU_COMPONENT_MISC_1 + struct GBACore { struct mCore d; struct GBAVideoSoftwareRenderer renderer; @@ -140,6 +144,7 @@ struct GBACore { const struct Configuration* overrides; struct mDebuggerPlatform* debuggerPlatform; struct mCheatDevice* cheatDevice; + struct GBAAudioMixer* audioMixer; }; static bool _GBACoreInit(struct mCore* core) { @@ -162,6 +167,7 @@ static bool _GBACoreInit(struct mCore* core) { gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; gbacore->logContext = NULL; + gbacore->audioMixer = NULL; GBACreate(gba); // TODO: Restore cheats @@ -209,6 +215,7 @@ static void _GBACoreDeinit(struct mCore* core) { mCheatDeviceDestroy(gbacore->cheatDevice); } free(gbacore->cheatDevice); + free(gbacore->audioMixer); mCoreConfigFreeOpts(&core->opts); free(core); } @@ -272,6 +279,7 @@ static void _GBACoreLoadConfig(struct mCore* core, const struct mCoreConfig* con mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections"); mCoreConfigCopyValue(&core->config, config, "gba.bios"); + mCoreConfigCopyValue(&core->config, config, "gba.audioHle"); #ifndef DISABLE_THREADING mCoreConfigCopyValue(&core->config, config, "threadedVideo"); @@ -464,6 +472,16 @@ static void _GBACoreReset(struct mCore* core) { GBAVideoAssociateRenderer(&gba->video, renderer); } +#ifndef MINIMAL_CORE + int useAudioMixer; + if (!gbacore->audioMixer && mCoreConfigGetIntValue(&core->config, "gba.audioHle", &useAudioMixer) && useAudioMixer) { + gbacore->audioMixer = malloc(sizeof(*gbacore->audioMixer)); + GBAAudioMixerCreate(gbacore->audioMixer); + ((struct ARMCore*) core->cpu)->components[CPU_COMPONENT_AUDIO_MIXER] = &gbacore->audioMixer->d; + ARMHotplugAttach(core->cpu, CPU_COMPONENT_AUDIO_MIXER); + } +#endif + GBAOverrideApplyDefaults(gba, gbacore->overrides); #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 diff --git a/src/gba/extra/audio-mixer.c b/src/gba/extra/audio-mixer.c new file mode 100644 index 000000000..b7eb8c315 --- /dev/null +++ b/src/gba/extra/audio-mixer.c @@ -0,0 +1,304 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include +#include +#include + +#define OVERSAMPLE 2 + +static void _mp2kInit(void* cpu, struct mCPUComponent* component); +static void _mp2kDeinit(struct mCPUComponent* component); + +static bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address); +static void _mp2kVblank(struct GBAAudioMixer* mixer); +static void _mp2kStep(struct GBAAudioMixer* mixer); + +void GBAAudioMixerCreate(struct GBAAudioMixer* mixer) { + mixer->d.init = _mp2kInit; + mixer->d.deinit = _mp2kDeinit; + mixer->engage = _mp2kEngage; + mixer->vblank = _mp2kVblank; + mixer->step = _mp2kStep; +} + +void _mp2kInit(void* cpu, struct mCPUComponent* component) { + struct ARMCore* arm = cpu; + struct GBA* gba = (struct GBA*) arm->master; + struct GBAAudioMixer* mixer = (struct GBAAudioMixer*) component; + gba->audio.mixer = mixer; + mixer->p = &gba->audio; + mixer->contextAddress = 0; + mixer->tempo = 120.0 / 75.0; + mixer->frame = 0; + mixer->last.left = 0; + mixer->last.right = 0; + memset(&mixer->context, 0, sizeof(mixer->context)); + memset(&mixer->activeTracks, 0, sizeof(mixer->activeTracks)); + + size_t i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + mixer->activeTracks[i].channel = &mixer->context.chans[i]; + CircleBufferInit(&mixer->activeTracks[i].buffer, 0x10000); + } +} + +void _mp2kDeinit(struct mCPUComponent* component) { + struct GBAAudioMixer* mixer = (struct GBAAudioMixer*) component; + size_t i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + CircleBufferDeinit(&mixer->activeTracks[i].buffer); + } +} + +static void _loadInstrument(struct ARMCore* cpu, struct GBAMP2kInstrument* instrument, uint32_t base) { + struct ARMMemory* memory = &cpu->memory; + instrument->type = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, type), 0); + instrument->key = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, key), 0); + instrument->length = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, length), 0); + instrument->ps.pan = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, ps.pan), 0); + if (instrument->type == 0x40 || instrument->type == 0x80) { + instrument->data.subTable = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, data.subTable), 0); + instrument->extInfo.map = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.map), 0); + } else { + instrument->data.waveData = memory->load32(cpu, base + offsetof(struct GBAMP2kInstrument, data.waveData), 0); + instrument->extInfo.adsr.attack = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.attack), 0); + instrument->extInfo.adsr.decay = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.decay), 0); + instrument->extInfo.adsr.sustain = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.sustain), 0); + instrument->extInfo.adsr.release = memory->load8(cpu, base + offsetof(struct GBAMP2kInstrument, extInfo.adsr.release), 0); + } +} + +static void _lookupInstrument(struct ARMCore* cpu, struct GBAMP2kInstrument* instrument, uint8_t key) { + struct ARMMemory* memory = &cpu->memory; + if (instrument->type == 0x40) { + uint32_t subInstrumentBase = instrument->data.subTable; + uint32_t keyTable = instrument->extInfo.map; + uint8_t id = memory->load8(cpu, keyTable + key, 0); + subInstrumentBase += 12 * id; + _loadInstrument(cpu, instrument, subInstrumentBase); + } + if (instrument->type == 0x80) { + uint32_t subInstrumentBase = instrument->data.subTable; + subInstrumentBase += 12 * key; + _loadInstrument(cpu, instrument, subInstrumentBase); + } +} + +static void _stepSample(struct GBAAudioMixer* mixer, struct GBAMP2kTrack* track) { + struct ARMCore* cpu = mixer->p->p->cpu; + struct ARMMemory* memory = &cpu->memory; + uint32_t headerAddress; + struct GBAMP2kInstrument instrument = track->track.instrument; + + uint8_t note = track->track.key; + _lookupInstrument(cpu, &instrument, note); + double freq; + + switch (instrument.type) { + case 0x00: + case 0x08: + case 0x40: + case 0x80: + freq = GBA_ARM7TDMI_FREQUENCY / (double) track->channel->freq; + break; + default: + // We don't care about PSG channels + return; + } + headerAddress = instrument.data.waveData; + if (headerAddress < 0x20) { + mLOG(GBA_AUDIO, ERROR, "Audio track has invalid instrument"); + return; + } + uint32_t loopOffset = memory->load32(cpu, headerAddress + 0x8, 0); + uint32_t endOffset = memory->load32(cpu, headerAddress + 0xC, 0); + uint32_t sampleBase = headerAddress + 0x10; + uint32_t sampleI = track->samplePlaying; + double sampleOffset = track->currentOffset; + double updates = VIDEO_TOTAL_LENGTH / (mixer->tempo * mixer->p->sampleInterval / OVERSAMPLE); + int nSample; + for (nSample = 0; nSample < updates; ++nSample) { + int8_t sample = memory->load8(cpu, sampleBase + sampleI, 0); + + struct GBAStereoSample stereo = { + (sample * track->channel->leftVolume * track->channel->envelopeV) >> 9, + (sample * track->channel->rightVolume * track->channel->envelopeV) >> 9 + }; + + CircleBufferWrite16(&track->buffer, stereo.left); + CircleBufferWrite16(&track->buffer, stereo.right); + + sampleOffset += mixer->p->sampleInterval / OVERSAMPLE; + while (sampleOffset > freq) { + sampleOffset -= freq; + ++sampleI; + if (sampleI >= endOffset) { + sampleI = loopOffset; + } + } + } + + track->samplePlaying = sampleI; + track->currentOffset = sampleOffset; +} + +static void _mp2kReload(struct GBAAudioMixer* mixer) { + struct ARMCore* cpu = mixer->p->p->cpu; + struct ARMMemory* memory = &cpu->memory; + mixer->context.magic = memory->load32(cpu, mixer->contextAddress + offsetof(struct GBAMP2kContext, magic), 0); + int i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + struct GBAMP2kSoundChannel* ch = &mixer->context.chans[i]; + struct GBAMP2kTrack* track = &mixer->activeTracks[i]; + track->waiting = false; + uint32_t base = mixer->contextAddress + offsetof(struct GBAMP2kContext, chans[i]); + + ch->status = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, status), 0); + ch->type = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, type), 0); + ch->rightVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, rightVolume), 0); + ch->leftVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, leftVolume), 0); + ch->adsr.attack = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.attack), 0); + ch->adsr.decay = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.decay), 0); + ch->adsr.sustain = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.sustain), 0); + ch->adsr.release = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, adsr.release), 0); + ch->ky = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, ky), 0); + ch->envelopeV = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeV), 0); + ch->envelopeRight = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeRight), 0); + ch->envelopeLeft = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, envelopeLeft), 0); + ch->echoVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, echoVolume), 0); + ch->echoLength = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, echoLength), 0); + ch->d1 = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d1), 0); + ch->d2 = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d2), 0); + ch->gt = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, gt), 0); + ch->midiKey = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, midiKey), 0); + ch->ve = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, ve), 0); + ch->pr = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, pr), 0); + ch->rp = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, rp), 0); + ch->d3[0] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[0]), 0); + ch->d3[1] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[1]), 0); + ch->d3[2] = memory->load8(cpu, base + offsetof(struct GBAMP2kSoundChannel, d3[2]), 0); + ch->ct = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, ct), 0); + ch->fw = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, fw), 0); + ch->freq = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, freq), 0); + ch->waveData = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, waveData), 0); + ch->cp = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, cp), 0); + ch->track = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, track), 0); + ch->pp = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, pp), 0); + ch->np = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, np), 0); + ch->d4 = memory->load32(cpu, base + offsetof(struct GBAMP2kSoundChannel, d4), 0); + ch->xpi = memory->load16(cpu, base + offsetof(struct GBAMP2kSoundChannel, xpi), 0); + ch->xpc = memory->load16(cpu, base + offsetof(struct GBAMP2kSoundChannel, xpc), 0); + + base = ch->track; + if (base) { + track->track.flags = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, flags), 0); + track->track.wait = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, wait), 0); + track->track.patternLevel = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternLevel), 0); + track->track.repN = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, repN), 0); + track->track.gateTime = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, gateTime), 0); + track->track.key = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, key), 0); + track->track.velocity = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, velocity), 0); + track->track.runningStatus = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, runningStatus), 0); + track->track.keyM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyM), 0); + track->track.pitM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pitM), 0); + track->track.keyShift = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyShift), 0); + track->track.keyShiftX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, keyShiftX), 0); + track->track.tune = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, tune), 0); + track->track.pitX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pitX), 0); + track->track.bend = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, bend), 0); + track->track.bendRange = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, bendRange), 0); + track->track.volMR = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volMR), 0); + track->track.volML = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volML), 0); + track->track.vol = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, vol), 0); + track->track.volX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, volX), 0); + track->track.pan = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, pan), 0); + track->track.panX = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, panX), 0); + track->track.modM = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, modM), 0); + track->track.mod = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, mod), 0); + track->track.modT = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, modT), 0); + track->track.lfoSpeed = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoSpeed), 0); + track->track.lfoSpeedC = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoSpeedC), 0); + track->track.lfoDelay = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoDelay), 0); + track->track.lfoDelayC = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, lfoDelayC), 0); + track->track.priority = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, priority), 0); + track->track.echoVolume = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, echoVolume), 0); + track->track.echoLength = memory->load8(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, echoLength), 0); + track->track.chan = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, chan), 0); + _loadInstrument(cpu, &track->track.instrument, base + offsetof(struct GBAMP2kMusicPlayerTrack, instrument)); + track->track.cmdPtr = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, cmdPtr), 0); + track->track.patternStack[0] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[0]), 0); + track->track.patternStack[1] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[1]), 0); + track->track.patternStack[2] = memory->load32(cpu, base + offsetof(struct GBAMP2kMusicPlayerTrack, patternStack[2]), 0); + } else { + memset(&track->track, 0, sizeof(track->track)); + } + } +} + +bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address) { + if (address < BASE_WORKING_RAM) { + return false; + } + if (address != mixer->contextAddress) { + mixer->contextAddress = address; + mixer->p->externalMixing = true; + _mp2kReload(mixer); + } + return true; +} + +void _mp2kStep(struct GBAAudioMixer* mixer) { + if (!mixer->p->externalMixing) { + return; + } + mixer->frame += mixer->p->sampleInterval; + + while (mixer->frame >= VIDEO_TOTAL_LENGTH / mixer->tempo) { + int i; + for (i = 0; i < MP2K_MAX_SOUND_CHANNELS; ++i) { + struct GBAMP2kTrack* track = &mixer->activeTracks[i]; + if (track->channel->status > 0) { + _stepSample(mixer, track); + } else { + track->currentOffset = 0; + track->samplePlaying = 0; + CircleBufferClear(&track->buffer); + } + } + mixer->frame -= VIDEO_TOTAL_LENGTH / mixer->tempo; + } + + uint32_t interval = mixer->p->sampleInterval / OVERSAMPLE; + int i; + for (i = 0; i < OVERSAMPLE; ++i) { + struct GBAStereoSample sample = {0}; + size_t track; + for (track = 0; track < MP2K_MAX_SOUND_CHANNELS; ++track) { + if (!mixer->activeTracks[track].channel->status) { + continue; + } + int16_t value; + CircleBufferRead16(&mixer->activeTracks[track].buffer, &value); + sample.left += value; + CircleBufferRead16(&mixer->activeTracks[track].buffer, &value); + sample.right += value; + } + blip_add_delta(mixer->p->psg.left, mixer->p->clock + i * interval, sample.left - mixer->last.left); + blip_add_delta(mixer->p->psg.right, mixer->p->clock + i * interval, sample.left - mixer->last.left); + mixer->last = sample; + } +} + +void _mp2kVblank(struct GBAAudioMixer* mixer) { + if (!mixer->contextAddress) { + return; + } + mLOG(GBA_AUDIO, DEBUG, "Frame"); + mixer->p->externalMixing = true; + _mp2kReload(mixer); +} diff --git a/src/gba/gba.c b/src/gba/gba.c index 4434fc526..39a3f72c3 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -789,6 +789,10 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { void GBAFrameStarted(struct GBA* gba) { GBATestKeypadIRQ(gba); + if (gba->audio.mixer) { + gba->audio.mixer->vblank(gba->audio.mixer); + } + size_t c; for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) { struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gba->coreCallbacks, c);