mirror of https://github.com/mgba-emu/mgba.git
GB Audio: Add channel 4 batching back (fixes #1313)
This commit is contained in:
parent
dd850b8d33
commit
c5b78f9354
1
CHANGES
1
CHANGES
|
@ -113,6 +113,7 @@ Misc:
|
|||
- GB: Allow pausing event loop while CPU is blocked
|
||||
- GB: Add support for sleep and shutdown callbacks
|
||||
- GB: Redo double speed emulation (closes mgba.io/i/1515)
|
||||
- GB Audio: Add channel 4 batching back (fixes mgba.io/i/1313)
|
||||
- GB Core: Return the current number of banks for ROM/SRAM, not theoretical max
|
||||
- GB I/O: Implement preliminary support for PCM12/PCM34 (closes mgba.io/i/1468)
|
||||
- GBA: Allow pausing event loop while CPU is blocked
|
||||
|
|
|
@ -240,6 +240,7 @@ void GBAudioWriteNR51(struct GBAudio* audio, uint8_t);
|
|||
void GBAudioWriteNR52(struct GBAudio* audio, uint8_t);
|
||||
|
||||
void GBAudioUpdateFrame(struct GBAudio* audio);
|
||||
void GBAudioUpdateChannel4(struct GBAudio* audio);
|
||||
|
||||
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right);
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesL
|
|||
static void _updateChannel2(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _updateChannel4(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
|
||||
void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAudioStyle style) {
|
||||
|
@ -90,7 +89,7 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu
|
|||
audio->ch3Fade.priority = 0x14;
|
||||
audio->ch4Event.context = audio;
|
||||
audio->ch4Event.name = "GB Audio Channel 4";
|
||||
audio->ch4Event.callback = _updateChannel4;
|
||||
audio->ch4Event.callback = NULL; // This is pending removal, so calling it will crash
|
||||
audio->ch4Event.priority = 0x15;
|
||||
audio->sampleEvent.context = audio;
|
||||
audio->sampleEvent.name = "GB Audio Sample";
|
||||
|
@ -346,32 +345,33 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) {
|
|||
}
|
||||
|
||||
void GBAudioWriteNR41(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
_writeDuty(&audio->ch4.envelope, value);
|
||||
audio->ch4.length = 64 - audio->ch4.envelope.length;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
if (!_writeEnvelope(&audio->ch4.envelope, value, audio->style)) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch4Event);
|
||||
audio->playingCh4 = false;
|
||||
*audio->nr52 &= ~0x0008;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR43(struct GBAudio* audio, uint8_t value) {
|
||||
// TODO: Reschedule event
|
||||
GBAudioUpdateChannel4(audio);
|
||||
audio->ch4.ratio = GBAudioRegisterNoiseFeedbackGetRatio(value);
|
||||
audio->ch4.frequency = GBAudioRegisterNoiseFeedbackGetFrequency(value);
|
||||
audio->ch4.power = GBAudioRegisterNoiseFeedbackGetPower(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
bool wasStop = audio->ch4.stop;
|
||||
audio->ch4.stop = GBAudioRegisterNoiseControlGetStop(value);
|
||||
if (!wasStop && audio->ch4.stop && audio->ch4.length && !(audio->frame & 1)) {
|
||||
--audio->ch4.length;
|
||||
if (audio->ch4.length == 0) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch4Event);
|
||||
audio->playingCh4 = false;
|
||||
}
|
||||
}
|
||||
|
@ -391,8 +391,6 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
|
|||
}
|
||||
if (audio->playingCh4 && audio->ch4.envelope.dead != 2) {
|
||||
audio->ch4.lastEvent = mTimingCurrentTime(audio->timing);
|
||||
mTimingDeschedule(audio->timing, &audio->ch4Event);
|
||||
mTimingSchedule(audio->timing, &audio->ch4Event, 0);
|
||||
}
|
||||
}
|
||||
*audio->nr52 &= ~0x0008;
|
||||
|
@ -550,7 +548,7 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
if (audio->ch4.length && audio->ch4.stop) {
|
||||
--audio->ch4.length;
|
||||
if (audio->ch4.length == 0) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch4Event);
|
||||
GBAudioUpdateChannel4(audio);
|
||||
audio->playingCh4 = 0;
|
||||
*audio->nr52 &= ~0x0008;
|
||||
}
|
||||
|
@ -582,11 +580,9 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
if (audio->playingCh4 && !audio->ch4.envelope.dead) {
|
||||
--audio->ch4.envelope.nextStep;
|
||||
if (audio->ch4.envelope.nextStep == 0) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
int8_t sample = audio->ch4.sample;
|
||||
_updateEnvelope(&audio->ch4.envelope);
|
||||
if (audio->ch4.envelope.dead == 2) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch4Event);
|
||||
}
|
||||
audio->ch4.sample = (sample > 0) * audio->ch4.envelope.currentVolume;
|
||||
if (audio->ch4.nSamples) {
|
||||
audio->ch4.samples -= sample;
|
||||
|
@ -598,6 +594,31 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
}
|
||||
}
|
||||
|
||||
void GBAudioUpdateChannel4(struct GBAudio* audio) {
|
||||
struct GBAudioNoiseChannel* ch = &audio->ch4;
|
||||
if (ch->envelope.dead == 2 || !audio->playingCh4) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t cycles = ch->ratio ? 2 * ch->ratio : 1;
|
||||
cycles <<= ch->frequency;
|
||||
cycles *= 8 * audio->timingFactor;
|
||||
|
||||
uint32_t last = 0;
|
||||
uint32_t now = mTimingCurrentTime(audio->timing) - ch->lastEvent;
|
||||
|
||||
for (; last + cycles <= now; last += cycles) {
|
||||
int lsb = ch->lfsr & 1;
|
||||
ch->sample = lsb * ch->envelope.currentVolume;
|
||||
++ch->nSamples;
|
||||
ch->samples += ch->sample;
|
||||
ch->lfsr >>= 1;
|
||||
ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8);
|
||||
}
|
||||
|
||||
ch->lastEvent += last;
|
||||
}
|
||||
|
||||
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
|
||||
int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : -0x8;
|
||||
int sampleLeft = dcOffset;
|
||||
|
@ -637,6 +658,7 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
|
|||
sampleRight <<= 3;
|
||||
|
||||
if (!audio->forceDisableCh[3]) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
int16_t sample = audio->style == GB_AUDIO_GBA ? (audio->ch4.sample << 3) : _coalesceNoiseChannel(&audio->ch4);
|
||||
if (audio->ch4Left) {
|
||||
sampleLeft += sample;
|
||||
|
@ -770,7 +792,7 @@ static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) {
|
|||
}
|
||||
|
||||
static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) {
|
||||
if (!ch->nSamples) {
|
||||
if (ch->nSamples <= 1) {
|
||||
return ch->sample << 3;
|
||||
}
|
||||
// TODO keep track of timing
|
||||
|
@ -926,42 +948,6 @@ static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLat
|
|||
audio->ch3.readable = false;
|
||||
}
|
||||
|
||||
static void _updateChannel4(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBAudio* audio = user;
|
||||
struct GBAudioNoiseChannel* ch = &audio->ch4;
|
||||
|
||||
int32_t cycles = ch->ratio ? 2 * ch->ratio : 1;
|
||||
cycles <<= ch->frequency;
|
||||
cycles *= 8 * audio->timingFactor;
|
||||
|
||||
uint32_t last = 0;
|
||||
uint32_t now = cycles;
|
||||
int32_t next = cycles - cyclesLate;
|
||||
|
||||
if (audio->style == GB_AUDIO_GBA) {
|
||||
last = ch->lastEvent;
|
||||
now = mTimingCurrentTime(timing) - cyclesLate;
|
||||
ch->lastEvent = now;
|
||||
now -= last;
|
||||
last = 0;
|
||||
if (audio->sampleInterval > next) {
|
||||
// TODO: Make batching work when descheduled
|
||||
next = audio->sampleInterval;
|
||||
}
|
||||
}
|
||||
|
||||
for (; last < now; last += cycles) {
|
||||
int lsb = ch->lfsr & 1;
|
||||
ch->sample = lsb * ch->envelope.currentVolume;
|
||||
++ch->nSamples;
|
||||
ch->samples += ch->sample;
|
||||
ch->lfsr >>= 1;
|
||||
ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8);
|
||||
}
|
||||
|
||||
mTimingSchedule(timing, &audio->ch4Event, next);
|
||||
}
|
||||
|
||||
void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut) {
|
||||
uint32_t flags = 0;
|
||||
uint32_t sweep = 0;
|
||||
|
@ -1007,7 +993,11 @@ void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGStat
|
|||
ch4Flags = GBSerializedAudioEnvelopeSetNextStep(ch4Flags, audio->ch4.envelope.nextStep);
|
||||
STORE_32LE(ch4Flags, 0, &state->ch4.envelope);
|
||||
STORE_32LE(audio->ch4.lastEvent, 0, &state->ch4.lastEvent);
|
||||
STORE_32LE(audio->ch4Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch4.nextEvent);
|
||||
|
||||
int32_t cycles = audio->ch4.ratio ? 2 * audio->ch4.ratio : 1;
|
||||
cycles <<= audio->ch4.frequency;
|
||||
cycles *= 8 * audio->timingFactor;
|
||||
STORE_32LE(audio->ch4.lastEvent + cycles, 0, &state->ch4.nextEvent);
|
||||
|
||||
STORE_32LE(flags, 0, flagsOut);
|
||||
}
|
||||
|
@ -1095,7 +1085,6 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
|
|||
cycles *= 8 * audio->timingFactor;
|
||||
audio->ch4.lastEvent = currentTime + (when & (cycles - 1)) - cycles;
|
||||
}
|
||||
mTimingSchedule(audio->timing, &audio->ch4Event, when);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -166,6 +166,10 @@ void GBIOReset(struct GB* gb) {
|
|||
GBIOWrite(gb, GB_REG_TMA, 0);
|
||||
GBIOWrite(gb, GB_REG_TAC, 0);
|
||||
GBIOWrite(gb, GB_REG_IF, 1);
|
||||
gb->audio.playingCh1 = false;
|
||||
gb->audio.playingCh2 = false;
|
||||
gb->audio.playingCh3 = false;
|
||||
gb->audio.playingCh4 = false;
|
||||
GBIOWrite(gb, GB_REG_NR52, 0xF1);
|
||||
GBIOWrite(gb, GB_REG_NR14, 0x3F);
|
||||
GBIOWrite(gb, GB_REG_NR10, 0x80);
|
||||
|
@ -633,6 +637,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
|
|||
if (gb->model < GB_MODEL_CGB) {
|
||||
mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address);
|
||||
} else if (gb->audio.enable) {
|
||||
GBAudioUpdateChannel4(&gb->audio);
|
||||
return (gb->audio.ch3.sample) | (gb->audio.ch4.sample << 4);
|
||||
}
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue