mirror of https://github.com/mgba-emu/mgba.git
GBA Audio: Experimental HLE audio
This commit is contained in:
parent
7e476dfb76
commit
4e2052f934
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -10,10 +10,14 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/core/cpu.h>
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/internal/gb/audio.h>
|
||||
#include <mgba-util/circle-buffer.h>
|
||||
|
||||
#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);
|
||||
|
|
|
@ -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 <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/gba/audio.h>
|
||||
|
||||
void GBAAudioMixerCreate(struct GBAAudioMixer* mixer);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -14,6 +14,8 @@
|
|||
#include <mgba/internal/gba/serialize.h>
|
||||
#include <mgba/internal/gba/video.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <mgba/internal/gba/cheats.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/io.h>
|
||||
#include <mgba/internal/gba/extra/audio-mixer.h>
|
||||
#include <mgba/internal/gba/extra/cli.h>
|
||||
#include <mgba/internal/gba/overrides.h>
|
||||
#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
|
||||
|
|
|
@ -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 <mgba/internal/gba/extra/audio-mixer.h>
|
||||
|
||||
#include <mgba/core/blip_buf.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/video.h>
|
||||
|
||||
#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);
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue