diff --git a/CHANGES b/CHANGES index e8a40834c..a559f2349 100644 --- a/CHANGES +++ b/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 diff --git a/include/mgba/internal/gb/audio.h b/include/mgba/internal/gb/audio.h index 494817a3e..4f4fdef32 100644 --- a/include/mgba/internal/gb/audio.h +++ b/include/mgba/internal/gb/audio.h @@ -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); diff --git a/src/gb/audio.c b/src/gb/audio.c index 5cc2b491e..6824072bf 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -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); } } diff --git a/src/gb/io.c b/src/gb/io.c index 6cf77e4c8..9ba248882 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -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;