/* FCE Ultra - NES/Famicom Emulator * * Copyright notice for this file: * Copyright (C) 2002 Xodnizel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "fceu-types.h" #include "x6502.h" #include "fceu.h" #include "sound.h" #include "filter.h" #include "state.h" static uint32_t wlookup1[32]; static uint32_t wlookup2[203]; /* Helper: clamp wlookup2's combined index to the table size. * * The LQ Tri/Noise/PCM mix sums lq_tcout, noiseout, and RawDALatch (with * per-channel volume scalars applied) and uses the result as the index * into wlookup2[203]. Each input is bounded under normal emulation: * lq_tcout is (tristep & 0xF) * 3 (max 45), noiseout is the noise * envelope decvolume << 1 (max 30 with 0..15 envelope range), and * RawDALatch is the $4011 DAC latch (writes mask off the top bit, so * 0..127 in the running emulator). Sum max ~202 fits the table. * * After loading a savestate, however, every one of those state fields * is whatever the file said, so EnvUnits[2].decvolume (loaded as raw * uint8_t) can be 0..255, RawDALatch can be 0..255, lq_tcout (loaded * as uint32_t) can be anything, and the sum can overflow the table by * a wide margin - a heap-buffer-overflow read that AddressSanitizer * surfaces in retro_run after retro_unserialize on a malformed state. * * Clamp at the access site so the protection holds regardless of which * piece of state was tampered with. Out-of-range values play wrong * audio for one frame until DoEnv / channel writes restore sane state, * but the emulator stays alive. */ static INLINE uint32_t wl2(uint32_t idx) { if (idx >= sizeof(wlookup2) / sizeof(wlookup2[0])) idx = sizeof(wlookup2) / sizeof(wlookup2[0]) - 1; return wlookup2[idx]; } int32_t Wave[2048 + 512]; int32_t WaveHi[40000]; int32_t WaveFinal[2048 + 512]; EXPSOUND GameExpSound = { 0, 0, 0, 0, 0, 0 }; static uint8_t TriCount = 0; static uint8_t TriMode = 0; static int32_t tristep = 0; static int32_t wlcount[4] = { 0, 0, 0, 0 }; /* Wave length counters. */ static uint8_t IRQFrameMode = 0; /* $4017 / xx000000 */ static uint8_t PSG[0x10]; static uint8_t RawDALatch = 0; /* $4011 0xxxxxxx */ uint8_t EnabledChannels = 0; /* Byte written to $4015 */ typedef struct { uint8_t Speed; uint8_t Mode; /* Fixed volume(1), and loop(2) */ uint8_t DecCountTo1; uint8_t decvolume; int reloaddec; } ENVUNIT; unsigned DMC_7bit = 0; /* used to skip overclocking */ static ENVUNIT EnvUnits[3]; static const int RectDuties[4] = { 1, 2, 4, 6 }; static int32_t RectDutyCount[2]; static uint8_t sweepon[2]; static int32_t curfreq[2]; static uint8_t SweepCount[2]; static uint8_t sweepReload[2]; static uint16_t nreg = 0; static uint8_t fcnt = 0; static int32_t fhcnt = 0; static int32_t fhinc = 0; uint32_t soundtsoffs = 0; /* Variables exclusively for low-quality sound. */ int32_t nesincsize = 0; uint32_t soundtsinc = 0; uint32_t soundtsi = 0; static int32_t sqacc[2]; static uint32_t lq_tcout; static int32_t lq_triacc; static int32_t lq_noiseacc; /* LQ variables segment ends. */ static int32_t lengthcount[4]; static const uint8_t lengthtable[0x20] = { 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e, 0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1E }; static const uint32_t NTSCNoiseFreqTable[0x10] = { 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 }; static const uint32_t PALNoiseFreqTable[0x10] = { 0x004, 0x008, 0x00E, 0x01E, 0x03C, 0x058, 0x076, 0x094, 0x0BC, 0x0EC, 0x162, 0x1D8, 0x2C4, 0x3B0, 0x762, 0xEC2 }; static const uint32_t NTSCDMCTable[0x10] = { 0x1AC, 0x17C, 0x154, 0x140, 0x11E, 0x0FE, 0x0E2, 0x0D6, 0x0BE, 0x0A0, 0x08E, 0x080, 0x06A, 0x054, 0x048, 0x036 }; static const uint32_t PALDMCTable[0x10] = { 0x18E, 0x162, 0x13C, 0x12A, 0x114, 0x0EC, 0x0D2, 0x0C6, 0x0B0, 0x094, 0x084, 0x076, 0x062, 0x04E, 0x042, 0x032 }; /* $4010 - Frequency * $4011 - Actual data outputted * $4012 - Address register: $c000 + V*64 * $4013 - Size register: Size in bytes = (V+1)*64 */ static int32_t DMCacc = 1; static int32_t DMCPeriod = 0; static uint8_t DMCBitCount = 0; static uint8_t DMCAddressLatch = 0, DMCSizeLatch = 0; /* writes to 4012 and 4013 */ static uint8_t DMCFormat = 0; /* Write to $4010 */ static uint32_t DMCAddress = 0; static int32_t DMCSize = 0; static uint8_t DMCShift = 0; static uint8_t SIRQStat = 0; static char DMCHaveDMA = 0; static uint8_t DMCDMABuf = 0; static char DMCHaveSample = 0; static void Dummyfunc(void) { } static void (*DoNoise)(void) = Dummyfunc; static void (*DoTriangle)(void) = Dummyfunc; static void (*DoPCM)(void) = Dummyfunc; static void (*DoSQ1)(void) = Dummyfunc; static void (*DoSQ2)(void) = Dummyfunc; static uint32_t ChannelBC[5]; static void LoadDMCPeriod(uint8_t V) { if (PAL) DMCPeriod = PALDMCTable[V]; else DMCPeriod = NTSCDMCTable[V]; } static void PrepDPCM() { DMCAddress = 0x4000 + (DMCAddressLatch << 6); DMCSize = (DMCSizeLatch << 4) + 1; } /* Instantaneous? Maybe the new freq value is being calculated all of the time... */ static int FASTAPASS(2) CheckFreq(uint32_t cf, uint8_t sr) { uint32_t mod; if (!(sr & 0x8)) { mod = cf >> (sr & 7); if ((mod + cf) & 0x800) return(0); } return(1); } static void SQReload(int x, uint8_t V) { if (EnabledChannels & (1 << x)) lengthcount[x] = lengthtable[(V >> 3) & 0x1f]; curfreq[x] = (curfreq[x] & 0xff) | ((V & 7) << 8); RectDutyCount[x] = 7; EnvUnits[x].reloaddec = 1; } static DECLFW(Write_PSG) { A &= 0x1F; switch (A) { case 0x0: DoSQ1(); EnvUnits[0].Mode = (V & 0x30) >> 4; EnvUnits[0].Speed = (V & 0xF); if (swapDuty) V = (V & 0x3F) | ((V & 0x80) >> 1) | ((V & 0x40) << 1); break; case 0x1: DoSQ1(); sweepReload[0] = 1; sweepon[0] = (V & 0x80); break; case 0x2: DoSQ1(); curfreq[0] &= 0xFF00; curfreq[0] |= V; break; case 0x3: DoSQ1(); SQReload(0, V); break; case 0x4: DoSQ2(); EnvUnits[1].Mode = (V & 0x30) >> 4; EnvUnits[1].Speed = (V & 0xF); if (swapDuty) V = (V & 0x3F) | ((V & 0x80) >> 1) | ((V & 0x40) << 1); break; case 0x5: DoSQ2(); sweepReload[1] = 1; sweepon[1] = (V & 0x80); break; case 0x6: DoSQ2(); curfreq[1] &= 0xFF00; curfreq[1] |= V; break; case 0x7: DoSQ2(); SQReload(1, V); break; case 0xa: DoTriangle(); break; case 0xb: DoTriangle(); if (EnabledChannels & 0x4) lengthcount[2] = lengthtable[(V >> 3) & 0x1f]; TriMode = 1; /* Load mode */ break; case 0xC: DoNoise(); EnvUnits[2].Mode = (V & 0x30) >> 4; EnvUnits[2].Speed = (V & 0xF); break; case 0xE: DoNoise(); break; case 0xF: DoNoise(); if (EnabledChannels & 0x8) lengthcount[3] = lengthtable[(V >> 3) & 0x1f]; EnvUnits[2].reloaddec = 1; break; case 0x10: DoPCM(); LoadDMCPeriod(V & 0xF); if (SIRQStat & 0x80) { if (!(V & 0x80)) { X6502_IRQEnd(FCEU_IQDPCM); SIRQStat &= ~0x80; } else X6502_IRQBegin(FCEU_IQDPCM); } break; } PSG[A] = V; } static DECLFW(Write_DMCRegs) { A &= 0xF; switch (A) { case 0x00: DoPCM(); LoadDMCPeriod(V & 0xF); if (SIRQStat & 0x80) { if (!(V & 0x80)) { X6502_IRQEnd(FCEU_IQDPCM); SIRQStat &= ~0x80; } else X6502_IRQBegin(FCEU_IQDPCM); } DMCFormat = V; break; case 0x01: DoPCM(); { /* $4011 is the 7-bit DAC latch. Games like Castlevania II * pulse this register directly to produce sample-style audio * out-of-band from the DPCM bit-stream; the abrupt steps * between successive writes are the audible "pop". When * ReduceDMCPopping is on, take only the midpoint of the * old-vs-new transition - i.e. step the DAC halfway toward * the requested value rather than jumping straight to it. * This is the same algorithm libretro-fceumm_next ships * (backport reference: negativeExponent's fceumm_next). * The DPCM playback path (the +/-2 per bit update further * down) is left untouched. */ uint8_t newval = V & 0x7F; uint8_t lastval = RawDALatch; RawDALatch = newval; if (FSettings.ReduceDMCPopping) { RawDALatch = (uint8_t)(newval - ((int)newval - (int)lastval) / 2); } } if (RawDALatch) DMC_7bit = 1; break; case 0x02: DMCAddressLatch = V; if (V) DMC_7bit = 0; break; case 0x03: DMCSizeLatch = V; if (V) DMC_7bit = 0; break; } } static DECLFW(StatusWrite) { int x; DoSQ1(); DoSQ2(); DoTriangle(); DoNoise(); DoPCM(); for (x = 0; x < 4; x++) if (!(V & (1 << x))) lengthcount[x] = 0; /* Force length counters to 0. */ if (V & 0x10) { if (!DMCSize) PrepDPCM(); } else { DMCSize = 0; } SIRQStat &= ~0x80; X6502_IRQEnd(FCEU_IQDPCM); EnabledChannels = V & 0x1F; } static DECLFR(StatusRead) { int x; uint8_t ret; ret = SIRQStat; for (x = 0; x < 4; x++) ret |= lengthcount[x] ? (1 << x) : 0; if (DMCSize) ret |= 0x10; { SIRQStat &= ~0x40; X6502_IRQEnd(FCEU_IQFCOUNT); } return ret; } static void FASTAPASS(1) FrameSoundStuff(int V) { int P; DoSQ1(); DoSQ2(); DoNoise(); DoTriangle(); if (!(V & 1)) { /* Envelope decay, linear counter, length counter, freq sweep */ if (!(PSG[8] & 0x80)) if (lengthcount[2] > 0) lengthcount[2]--; if (!(PSG[0xC] & 0x20)) /* Make sure loop flag is not set. */ if (lengthcount[3] > 0) lengthcount[3]--; for (P = 0; P < 2; P++) { if (!(PSG[P << 2] & 0x20)) /* Make sure loop flag is not set. */ if (lengthcount[P] > 0) lengthcount[P]--; /* Frequency Sweep Code Here */ /* xxxx 0000 */ /* xxxx = hz. 120/(x+1)*/ /* http://wiki.nesdev.com/w/index.php/APU_Sweep */ if (SweepCount[P] > 0) SweepCount[P]--; if (SweepCount[P] <= 0) { uint32_t sweepShift = (PSG[(P << 2) + 0x1] & 7); if (sweepon[P] && sweepShift && curfreq[P] >= 8) { int32_t mod = (curfreq[P] >> sweepShift); if (PSG[(P << 2) + 0x1] & 0x8) { curfreq[P] -= (mod + (P ^ 1)); } else if ((mod + curfreq[P]) < 0x800) { curfreq[P] += mod; } } SweepCount[P] = (((PSG[(P << 2) + 0x1] >> 4) & 7) + 1); } if (sweepReload[P]) { SweepCount[P] = (((PSG[(P << 2) + 0x1] >> 4) & 7) + 1); sweepReload[P] = 0; } } } /* Now do envelope decay + linear counter. */ if (TriMode)/* In load mode? */ TriCount = PSG[0x8] & 0x7F; else if (TriCount) TriCount--; if (!(PSG[0x8] & 0x80)) TriMode = 0; for (P = 0; P < 3; P++) { if (EnvUnits[P].reloaddec) { EnvUnits[P].decvolume = 0xF; EnvUnits[P].DecCountTo1 = EnvUnits[P].Speed + 1; EnvUnits[P].reloaddec = 0; continue; } if (EnvUnits[P].DecCountTo1 > 0) EnvUnits[P].DecCountTo1--; if (EnvUnits[P].DecCountTo1 == 0) { EnvUnits[P].DecCountTo1 = EnvUnits[P].Speed + 1; if (EnvUnits[P].decvolume || (EnvUnits[P].Mode & 0x2)) { EnvUnits[P].decvolume--; EnvUnits[P].decvolume &= 0xF; } } } } void FrameSoundUpdate(void) { /* Linear counter: Bit 0-6 of $4008 * Length counter: Bit 4-7 of $4003, $4007, $400b, $400f */ if (fcnt == 3) { if (IRQFrameMode & 0x2) fhcnt += fhinc; } FrameSoundStuff(fcnt); fcnt = (fcnt + 1) & 3; /* has to be moved here to fix Dragon Warrior 4 * after irq inhibit fix for $4017 */ if (!fcnt && !(IRQFrameMode & 0x3)) { SIRQStat |= 0x40; X6502_IRQBegin(FCEU_IQFCOUNT); } } static INLINE void tester(void) { if (DMCBitCount == 0) { if (!DMCHaveDMA) DMCHaveSample = 0; else { DMCHaveSample = 1; DMCShift = DMCDMABuf; DMCHaveDMA = 0; } } } static INLINE void DMCDMA(void) { if (DMCSize && !DMCHaveDMA) { X6502_DMR(0x8000 + DMCAddress); X6502_DMR(0x8000 + DMCAddress); X6502_DMR(0x8000 + DMCAddress); DMCDMABuf = X6502_DMR(0x8000 + DMCAddress); DMCHaveDMA = 1; DMCAddress = (DMCAddress + 1) & 0x7fff; DMCSize--; if (!DMCSize) { if (DMCFormat & 0x40) PrepDPCM(); else { if (DMCFormat & 0x80) { SIRQStat |= 0x80; X6502_IRQBegin(FCEU_IQDPCM); } } } } } void FASTAPASS(1) FCEU_SoundCPUHook(int cycles) { fhcnt -= cycles * 48; if (fhcnt <= 0) { FrameSoundUpdate(); fhcnt += fhinc; } DMCDMA(); DMCacc -= cycles; while (DMCacc <= 0) { if (DMCHaveSample) { uint8_t bah = RawDALatch; int t = ((DMCShift & 1) << 2) - 2; /* Unbelievably ugly hack */ if (FSettings.SndRate) { soundtsoffs += DMCacc; DoPCM(); soundtsoffs -= DMCacc; } RawDALatch += t; if (RawDALatch & 0x80) RawDALatch = bah; } DMCacc += DMCPeriod; DMCBitCount = (DMCBitCount + 1) & 7; DMCShift >>= 1; tester(); } } static void RDoPCM(void) { uint32_t V; for (V = ChannelBC[4]; V < SOUNDTS; V++) /* TODO: get rid of floating calculations to binary. set log volume scaling. */ WaveHi[V] += (((RawDALatch << 16) / 256) * FSettings.PCMVolume ) & (~0xFFFF); ChannelBC[4] = SOUNDTS; } /* This has the correct phase. Don't mess with it. */ static INLINE void RDoSQ(int x) { int32_t V; int32_t amp; int32_t rthresh; int32_t *D; int32_t currdc; int32_t cf; int32_t rc; V = SOUNDTS - ChannelBC[x]; cf = (curfreq[x] + 1) * 2; rc = wlcount[x]; /* added 2018/12/08 */ /* when pulse channel is silenced, resets length counters but not * duty cycle, instead of resetting both */ if ((curfreq[x] < 8 || curfreq[x] > 0x7ff) || !CheckFreq(curfreq[x], PSG[(x << 2) | 0x1]) || !lengthcount[x]) { rc -= V; if (rc <= 0) { rc = cf - (-rc % cf); } } else { int dutyCycle; if (EnvUnits[x].Mode & 0x1) amp = EnvUnits[x].Speed; else amp = EnvUnits[x].decvolume; /* Modify Square wave volume based on channel volume modifiers * Note: the formulat x = x * y /100 does not yield exact results, * but is "close enough" and avoids the need for using double values * or implicit cohersion which are slower (we need speed here) */ /* TODO: Optimize this. */ if (FSettings.SquareVolume[x] != 256) amp = (amp * FSettings.SquareVolume[x]) / 256; amp <<= 24; dutyCycle = (PSG[(x << 2)] & 0xC0) >> 6; rthresh = RectDuties[dutyCycle]; currdc = RectDutyCount[x]; D = &WaveHi[ChannelBC[x]]; while (V > 0) { if (currdc < rthresh) *D += amp; rc--; if (!rc) { rc = cf; currdc = (currdc + 1) & 7; } V--; D++; } RectDutyCount[x] = currdc; } wlcount[x] = rc; ChannelBC[x] = SOUNDTS; } static void RDoSQ1(void) { RDoSQ(0); } static void RDoSQ2(void) { RDoSQ(1); } static void RDoSQLQ(void) { int32_t start, end; int32_t V; int32_t amp[2]; int32_t rthresh[2]; int32_t freq[2]; int x; int32_t inie[2]; int32_t ttable[2][8]; int32_t totalout; start = ChannelBC[0]; end = (SOUNDTS << 16) / soundtsinc; if (end <= start) return; ChannelBC[0] = end; for (x = 0; x < 2; x++) { int y; int dutyCycle; inie[x] = nesincsize; if (curfreq[x] < 8 || curfreq[x] > 0x7ff) inie[x] = 0; if (!CheckFreq(curfreq[x], PSG[(x << 2) | 0x1])) inie[x] = 0; if (!lengthcount[x]) inie[x] = 0; if (EnvUnits[x].Mode & 0x1) amp[x] = EnvUnits[x].Speed; else amp[x] = EnvUnits[x].decvolume; /* Modify Square wave volume based on channel volume modifiers * Note: the formulat x = x * y /100 does not yield exact results, * but is "close enough" and avoids the need for using double vales * or implicit cohersion which are slower (we need speed here) * fixed - setting up maximum volume for square2 caused complete mute square2 channel. * TODO: Optimize this. */ if (FSettings.SquareVolume[x] != 256) amp[x] = (amp[x] * FSettings.SquareVolume[x]) / 256; if (!inie[x]) amp[x] = 0; /* Correct? Buzzing in MM2, others otherwise... */ dutyCycle = (PSG[(x << 2)] & 0xC0) >> 6; rthresh[x] = RectDuties[dutyCycle]; for (y = 0; y < 8; y++) { if (y < rthresh[x]) ttable[x][y] = amp[x]; else ttable[x][y] = 0; } freq[x] = (curfreq[x] + 1) << 1; freq[x] <<= 17; } /* RectDutyCount[] is reloaded from savestate as raw int32_t and may * hold any value at this point - the inner-loop increments later mask * it back to 0..7, but this first lookup hits the table before any * such mask. Without bounds protection, ttable[0..7] (int[8]) reads * garbage memory and feeds it into wlookup1[32], which then OOB-reads * - reachable via a malformed savestate. Mask both indices defensively * here and at every other ttable/wlookup1 call site below. The mask * is one instruction and runs only at the chunk boundaries, not per * inner-loop sample. */ totalout = wlookup1[(ttable[0][RectDutyCount[0] & 7] + ttable[1][RectDutyCount[1] & 7]) & 31]; if (!inie[0] && !inie[1]) { /* Both squares silent: amp[x] was forced to 0 above (line * "if (!inie[x]) amp[x] = 0"), which propagates through * ttable[x] to make totalout = wlookup1[0] = 0. The * previous code looped (end - start) iterations adding 0 * to Wave[V>>4] - genuinely no-op for ~30000 NES cycles * per frame. Skip the loop entirely. */ } else { for (V = start; V < end; V++) { /* int tmpamp=0; if(RectDutyCount[0]> 4] += totalout; /* tmpamp; */ sqacc[0] -= inie[0]; sqacc[1] -= inie[1]; if (sqacc[0] <= 0) { rea: sqacc[0] += freq[0]; RectDutyCount[0] = (RectDutyCount[0] + 1) & 7; if (sqacc[0] <= 0) goto rea; totalout = wlookup1[(ttable[0][RectDutyCount[0]] + ttable[1][RectDutyCount[1] & 7]) & 31]; } if (sqacc[1] <= 0) { rea2: sqacc[1] += freq[1]; RectDutyCount[1] = (RectDutyCount[1] + 1) & 7; if (sqacc[1] <= 0) goto rea2; totalout = wlookup1[(ttable[0][RectDutyCount[0] & 7] + ttable[1][RectDutyCount[1]]) & 31]; } } } } static void RDoTriangle(void) { uint32_t V; int32_t tcout = (tristep & 0xF); uint32_t triangle_raw_period = (PSG[0xa] | ((PSG[0xb] & 7) << 8)); if (!(tristep & 0x10)) tcout ^= 0xF; tcout = (tcout * 3) << 16; /* (tcout<<1); */ /* The LQ tri/noise/PCM mixer (RDoTriangleNoisePCMLQ below) already * forces the triangle channel quiet when its period is low enough * to produce only ultrasonic output - games like Castlevania II * and Jackal repeatedly drop the triangle into that range when * they want silence, and without the gate the DAC reconstruction * filter folds the high-frequency content back as audible * popping. Mirror the gate in the HQ path, conditional on the * RemoveTriangleNoise option so the default HQ output stays * bit-exact with the original code unless the user opts in. */ if (!lengthcount[2] || !TriCount || (FSettings.RemoveTriangleNoise && triangle_raw_period <= 3)) { /* Counter is halted, but we still need to output. */ int32_t *start = &WaveHi[ChannelBC[2]]; int32_t count = SOUNDTS - ChannelBC[2]; while (count--) { *start += (tcout / 256 * FSettings.TriangleVolume) & (~0xFFFF); /* TODO OPTIMIZE ME */ start++; } /* cout = (tcout / 256 * FSettings.TriangleVolume) & (~0xFFFF); for(V = ChannelBC[2]; V < SOUNDTS; V++) WaveHi[V] += cout; */ } else { for (V = ChannelBC[2]; V < SOUNDTS; V++) { WaveHi[V] += (tcout / 256 * FSettings.TriangleVolume) & (~0xFFFF); /* TODO OPTIMIZE ME! */ wlcount[2]--; if (!wlcount[2]) { wlcount[2] = (PSG[0xa] | ((PSG[0xb] & 7) << 8)) + 1; tristep++; tcout = (tristep & 0xF); if (!(tristep & 0x10)) tcout ^= 0xF; tcout = (tcout * 3) << 16; } } } ChannelBC[2] = SOUNDTS; } static void RDoTriangleNoisePCMLQ(void) { int32_t V; int32_t start, end; int32_t freq[2]; int32_t inie[2]; uint32_t amptab[2]; uint32_t noiseout; int nshift; uint32_t scaled_tcout; uint32_t scaled_dmc; const uint32_t tri_vol = FSettings.TriangleVolume; int32_t totalout; start = ChannelBC[2]; end = (SOUNDTS << 16) / soundtsinc; if (end <= start) return; ChannelBC[2] = end; inie[0] = inie[1] = nesincsize; freq[0] = (((PSG[0xa] | ((PSG[0xb] & 7) << 8)) + 1)); if (!lengthcount[2] || !TriCount || freq[0] <= 4) inie[0] = 0; freq[0] <<= 17; if (EnvUnits[2].Mode & 0x1) amptab[0] = EnvUnits[2].Speed; else amptab[0] = EnvUnits[2].decvolume; /* Apply per-channel volume modifiers (set via fceumm_apu_N options). * * EnvUnits[2] is the Noise envelope (not Triangle - Triangle has no * envelope; EnvUnits[0]=SQ1, [1]=SQ2, [2]=Noise). The previous code * scaled amptab[0] by FSettings.TriangleVolume, which crossed the * Triangle and Noise mute toggles in LQ mode and left Triangle * itself never muted. Triangle's contribution enters wlookup2 via * lq_tcout below; PCM enters via RawDALatch. Scale each input * channel by its own volume before the non-linear DAC mix - 0 in * still produces 0 out, and 256 leaves the value unchanged. */ if (FSettings.NoiseVolume != 256) amptab[0] = (amptab[0] * FSettings.NoiseVolume) / 256; amptab[1] = 0; amptab[0] <<= 1; if (!lengthcount[3]) amptab[0] = inie[1] = 0; /* Quick hack speedup, set inie[1] to 0 */ noiseout = amptab[(nreg >> 0xe) & 1]; if (PSG[0xE] & 0x80) nshift = 8; else nshift = 13; scaled_tcout = (tri_vol != 256) ? ((lq_tcout * tri_vol) / 256) : lq_tcout; scaled_dmc = (FSettings.PCMVolume != 256) ? ((RawDALatch * FSettings.PCMVolume) / 256) : RawDALatch; totalout = wl2(scaled_tcout + noiseout + scaled_dmc); if (inie[0] && inie[1]) { for (V = start; V < end; V++) { Wave[V >> 4] += totalout; lq_triacc -= inie[0]; lq_noiseacc -= inie[1]; if (lq_triacc <= 0) { rea: lq_triacc += freq[0]; /* t; */ tristep = (tristep + 1) & 0x1F; if (lq_triacc <= 0) goto rea; lq_tcout = (tristep & 0xF); if (!(tristep & 0x10)) lq_tcout ^= 0xF; lq_tcout = lq_tcout * 3; scaled_tcout = (tri_vol != 256) ? ((lq_tcout * tri_vol) / 256) : lq_tcout; totalout = wl2(scaled_tcout + noiseout + scaled_dmc); } if (lq_noiseacc <= 0) { rea2: /* used to added <<(16+2) when the noise table * values were half. */ if (PAL) lq_noiseacc += PALNoiseFreqTable[PSG[0xE] & 0xF] << (16 + 1); else lq_noiseacc += NTSCNoiseFreqTable[PSG[0xE] & 0xF] << (16 + 1); nreg = (nreg << 1) + (((nreg >> nshift) ^ (nreg >> 14)) & 1); nreg &= 0x7fff; noiseout = amptab[(nreg >> 0xe) & 1]; if (lq_noiseacc <= 0) goto rea2; totalout = wl2(scaled_tcout + noiseout + scaled_dmc); } /* noiseacc<=0 */ } /* for(V=... */ } else if (inie[0]) { for (V = start; V < end; V++) { Wave[V >> 4] += totalout; lq_triacc -= inie[0]; if (lq_triacc <= 0) { area: lq_triacc += freq[0]; /* t; */ tristep = (tristep + 1) & 0x1F; if (lq_triacc <= 0) goto area; lq_tcout = (tristep & 0xF); if (!(tristep & 0x10)) lq_tcout ^= 0xF; lq_tcout = lq_tcout * 3; scaled_tcout = (tri_vol != 256) ? ((lq_tcout * tri_vol) / 256) : lq_tcout; totalout = wl2(scaled_tcout + noiseout + scaled_dmc); } } } else if (inie[1]) { for (V = start; V < end; V++) { Wave[V >> 4] += totalout; lq_noiseacc -= inie[1]; if (lq_noiseacc <= 0) { area2: /* used to be added <<(16+2) when the noise table * values were half. */ if (PAL) lq_noiseacc += PALNoiseFreqTable[PSG[0xE] & 0xF] << (16 + 1); else lq_noiseacc += NTSCNoiseFreqTable[PSG[0xE] & 0xF] << (16 + 1); nreg = (nreg << 1) + (((nreg >> nshift) ^ (nreg >> 14)) & 1); nreg &= 0x7fff; noiseout = amptab[(nreg >> 0xe) & 1]; if (lq_noiseacc <= 0) goto area2; totalout = wl2(scaled_tcout + noiseout + scaled_dmc); } /* noiseacc<=0 */ } } else { for (V = start; V < end; V++) Wave[V >> 4] += totalout; } } static void RDoNoise(void) { uint32_t V; int32_t outo; uint32_t amptab[2]; if (EnvUnits[2].Mode & 0x1) amptab[0] = EnvUnits[2].Speed; else amptab[0] = EnvUnits[2].decvolume; /* Modify Noise wave volume based on channel volume modifiers * Note: the formulat x = x * y /100 does not yield exact results, * but is "close enough" and avoids the need for using double vales * or implicit cohersion which are slower (we need speed here) * TODO: Optimize this. */ if (FSettings.NoiseVolume != 256) amptab[0] = (amptab[0] * FSettings.NoiseVolume) / 256; amptab[0] <<= 16; amptab[1] = 0; amptab[0] <<= 1; outo = amptab[(nreg >> 0xe) & 1]; if (!lengthcount[3]) { outo = amptab[0] = 0; } if (PSG[0xE] & 0x80) {/* "short" noise */ for (V = ChannelBC[3]; V < SOUNDTS; V++) { WaveHi[V] += outo; wlcount[3]--; if (!wlcount[3]) { uint8_t feedback; if (PAL) wlcount[3] = PALNoiseFreqTable[PSG[0xE] & 0xF]; else wlcount[3] = NTSCNoiseFreqTable[PSG[0xE] & 0xF]; feedback = ((nreg >> 8) & 1) ^ ((nreg >> 14) & 1); nreg = (nreg << 1) + feedback; nreg &= 0x7fff; outo = amptab[(nreg >> 0xe) & 1]; } } } else { for (V = ChannelBC[3]; V < SOUNDTS; V++) { WaveHi[V] += outo; wlcount[3]--; if (!wlcount[3]) { uint8_t feedback; if (PAL) wlcount[3] = PALNoiseFreqTable[PSG[0xE] & 0xF]; else wlcount[3] = NTSCNoiseFreqTable[PSG[0xE] & 0xF]; feedback = ((nreg >> 13) & 1) ^ ((nreg >> 14) & 1); nreg = (nreg << 1) + feedback; nreg &= 0x7fff; outo = amptab[(nreg >> 0xe) & 1]; } } } ChannelBC[3] = SOUNDTS; } static DECLFW(Write_IRQFM) { V = (V & 0xC0) >> 6; fcnt = 0; if (V & 2) FrameSoundUpdate(); /* fcnt = 1; */ fhcnt = fhinc; if (V & 1) { X6502_IRQEnd(FCEU_IQFCOUNT); SIRQStat &= ~0x40; } IRQFrameMode = V; } void SetNESSoundMap(void) { SetWriteHandler(0x4000, 0x400F, Write_PSG); SetWriteHandler(0x4010, 0x4013, Write_DMCRegs); SetWriteHandler(0x4017, 0x4017, Write_IRQFM); SetWriteHandler(0x4015, 0x4015, StatusWrite); SetReadHandler(0x4015, 0x4015, StatusRead); } static int32_t inbuf = 0; int FlushEmulateSound(void) { int x; int32_t end, left; if (!sound_timestamp) return(0); if (!FSettings.SndRate) { left = 0; end = 0; goto nosoundo; } DoSQ1(); DoSQ2(); DoTriangle(); DoNoise(); DoPCM(); if (FSettings.soundq >= 1) { int32_t *tmpo = &WaveHi[soundtsoffs]; if (GameExpSound.HiFill) GameExpSound.HiFill(); for (x = sound_timestamp; x; x--) { uint32_t b = *tmpo; *tmpo = (b & 65535) + wl2((b >> 16) & 255) + wlookup1[(b >> 24) & 31]; tmpo++; } end = NeoFilterSound(WaveHi, WaveFinal, SOUNDTS, &left); /* Slide the trailing `left` coefficient-history samples back * to the start of the buffer for next frame's filter, then * clear the area between left and SOUNDTS so next frame's * channel accumulators start at zero. * * The previous code cleared all the way to sizeof(WaveHi), * but only indices [left, SOUNDTS) were dirtied this frame - * everything past SOUNDTS is still zero from the prior * frame's clear (or from FCEUSND_Power on first frame). * WaveHi is 40000 entries = 160 KB; SOUNDTS is bounded by * NES cycles per frame (~30000), so this saves ~40 KB of * memset per HQ frame. The (SOUNDTS > left) guard handles * the degenerate case of a very short frame where SOUNDTS * may not have advanced past the coefficient history. */ memmove(WaveHi, WaveHi + SOUNDTS - left, left * sizeof(uint32_t)); if ((uint32_t)SOUNDTS > (uint32_t)left) memset(WaveHi + left, 0, (SOUNDTS - left) * sizeof(uint32_t)); if (GameExpSound.HiSync) GameExpSound.HiSync(left); for (x = 0; x < 5; x++) ChannelBC[x] = left; } else { end = (SOUNDTS << 16) / soundtsinc; if (GameExpSound.Fill) GameExpSound.Fill(end & 0xF); SexyFilter(Wave, WaveFinal, end >> 4); if (FSettings.lowpass) SexyFilter2(WaveFinal, end >> 4); if (end & 0xF) Wave[0] = Wave[(end >> 4)]; Wave[end >> 4] = 0; } nosoundo: if (FSettings.soundq >= 1) { soundtsoffs = left; } else { for (x = 0; x < 5; x++) ChannelBC[x] = end & 0xF; soundtsoffs = (soundtsinc * (end & 0xF)) >> 16; end >>= 4; } inbuf = end; return end; } int GetSoundBuffer(int32_t **W) { *W = WaveFinal; return(inbuf); } /* FIXME: Find out what sound registers get reset on reset. I know $4001/$4005 don't, due to that whole MegaMan 2 Game Genie thing. */ void FCEUSND_Reset(void) { int x; fhcnt = fhinc; fcnt = 0; /* Power-on noise shift register state. * * Real hardware initializes the 15-bit noise LFSR to $0001 with bit * 0 set (the output bit, muting the channel until the first feedback * cycle). This file stores the LFSR with the bit order reversed - * the output is read from bit 14, the feedback taps are at 13/14 * (long mode) or 8/14 (short mode), and the shift goes left rather * than right (see RDoNoise / NoLQNoise). Under that mirroring, the * real-hardware $0001 state corresponds to nreg = 0x4000 here, not * nreg = 1. * * Initialising to 1 left the LFSR running 14 long-mode steps ahead * of every other accurate emulator (Mesen, NSFPlay, _next), and made * short-mode output diverge entirely - the 93-cycle period is short * enough that the position offset is audible as "rougher" or * subtly wrong percussion. Reported as libretro-fceumm issue #466 * (Moon8 audio inaccuracy, by NSFPlay author Brad Smith). * * Per-channel bisection of moon8.nes against negativeExponent's * _next branch (which uses the un-mirrored layout from nesdev wiki) * shows the noise channel as the only meaningful divergence after * the music kicks in at ~22 s; squares and DMC are bit-identical. */ nreg = 0x4000; for (x = 0; x < 2; x++) { wlcount[x] = 2048; if (nesincsize) /* lq mode */ sqacc[x] = ((uint32_t)2048 << 17) / nesincsize; else sqacc[x] = 1; sweepon[x] = 0; curfreq[x] = 0; } wlcount[2] = 1; /* 2048; */ wlcount[3] = 2048; DMCHaveDMA = DMCHaveSample = 0; SIRQStat = 0x00; RawDALatch = 0x00; TriCount = 0; TriMode = 0; tristep = 0; EnabledChannels = 0; for (x = 0; x < 4; x++) lengthcount[x] = 0; DMCAddressLatch = 0; DMCSizeLatch = 0; DMCFormat = 0; DMCAddress = 0; DMCSize = 0; DMCShift = 0; DMCacc=1; DMCBitCount=0; } void FCEUSND_Power(void) { int x; SetNESSoundMap(); memset(PSG, 0x00, sizeof(PSG)); FCEUSND_Reset(); memset(Wave, 0, sizeof(Wave)); memset(WaveHi, 0, sizeof(WaveHi)); memset(&EnvUnits, 0, sizeof(EnvUnits)); for (x = 0; x < 5; x++) ChannelBC[x] = 0; soundtsoffs = 0; IRQFrameMode = 0x1; /* Only initialized by power-on reset, not by soft reset. NRS: don't start with Frame IRQ enabled for greater compatibility. Any game that actually uses frame IRQ will explicitly enable it, anyway. */ LoadDMCPeriod(DMCFormat & 0xF); /* Reset post-mix filter accumulators. These are file-scope in * filter.c and were not previously cleared on cart load, so a * second cart loaded in the same process inherited the first * cart's IIR state. Audibly minor on its own but breaks * frame-determinism for the first samples of a new run. */ SexyFilter_Reset(); } void SetSoundVariables(void) { int x; fhinc = PAL ? 16626 : 14915; /* *2 CPU clock rate */ fhinc *= 24; if (FSettings.SndRate) { wlookup1[0] = 0; for (x = 1; x < 32; x++) { wlookup1[x] = (double)16 * 16 * 16 * 4 * 95.52 / ((double)8128 / (double)x + 100); if (!FSettings.soundq) wlookup1[x] >>= 4; } wlookup2[0] = 0; for (x = 1; x < 203; x++) { wlookup2[x] = (double)16 * 16 * 16 * 4 * 163.67 / ((double)24329 / (double)x + 100); if (!FSettings.soundq) wlookup2[x] >>= 4; } if (FSettings.soundq >= 1) { DoNoise = RDoNoise; DoTriangle = RDoTriangle; DoPCM = RDoPCM; DoSQ1 = RDoSQ1; DoSQ2 = RDoSQ2; } else { /* All five Do* pointers in LQ mode end up at one of two * worker functions: RDoSQLQ (handles both squares) and * RDoTriangleNoisePCMLQ (handles tri/noise/PCM). * * Pass 6 had stubbed DoSQ2 / DoNoise / DoPCM to Dummyfunc * here on the reasoning that the workers guard with * "if (end <= start) return;" and re-entry within one * FlushEmulateSound is a no-op. That reasoning is * incorrect: the Do* hooks are also called from * Write_PSG (sound.c:189) on every APU register write * AND from FCEU_SoundCPUHook (line 493) on every DMC * bit advance. Between those callers, sound_timestamp * grows with each CPU instruction, so each Do* call IS * legitimately doing work - it flushes pending samples * up to the current SOUNDTS using the pre-write register * state, before the write updates the registers. Stubbing * those hooks to Dummyfunc skips the mid-frame flushes * and audibly changes output for any game that writes to * multiple APU registers in sequence (essentially all * of them). Verified bit-identical regression vs upstream * for the test_idle ROM (channels enabled with steady * settings) - the audio diverged starting at the first * post-init register write. Restored here. */ DoSQ1 = RDoSQLQ; DoSQ2 = RDoSQLQ; DoTriangle = RDoTriangleNoisePCMLQ; DoNoise = RDoTriangleNoisePCMLQ; DoPCM = RDoTriangleNoisePCMLQ; } } else { DoNoise = DoTriangle = DoPCM = DoSQ1 = DoSQ2 = Dummyfunc; return; } MakeFilters(FSettings.SndRate); if (GameExpSound.RChange) GameExpSound.RChange(); nesincsize = (int64_t)(((int64_t)1 << 17) * (double)(PAL ? PAL_CPU : NTSC_CPU) / (FSettings.SndRate * 16)); memset(sqacc, 0, sizeof(sqacc)); memset(ChannelBC, 0, sizeof(ChannelBC)); LoadDMCPeriod(DMCFormat & 0xF); /* For changing from PAL to NTSC */ /* Use double rather than long double here. long double has * platform-dependent precision (80-bit on x87, 64-bit with * -mfpmath=sse, 128-bit on some non-x86), so the cast-to-uint32 * result varies across platforms. double is guaranteed 64-bit * IEEE-754 on every platform we target, keeping soundtsinc * deterministic across builds for replay/netplay. */ soundtsinc = (uint32_t)((uint64_t)((double)(PAL ? PAL_CPU : NTSC_CPU) * 65536.0) / (FSettings.SndRate * 16)); } void FCEUI_Sound(int Rate) { FSettings.SndRate = Rate; SetSoundVariables(); } void FCEUI_SetLowPass(int q) { FSettings.lowpass = q; } void FCEUI_RemoveTriangleNoise(int d) { FSettings.RemoveTriangleNoise = d ? 1 : 0; } void FCEUI_ReduceDmcPopping(int d) { FSettings.ReduceDMCPopping = d ? 1 : 0; } void FCEUI_SetSoundQuality(int quality) { FSettings.soundq = quality; SetSoundVariables(); } void FCEUI_SetSoundVolume(uint32_t volume) { FSettings.SoundVolume = volume; } SFORMAT FCEUSND_STATEINFO[] = { { &fhcnt, 4 | FCEUSTATE_RLSB, "FHCN" }, { &fcnt, 1, "FCNT" }, { PSG, 0x10, "PSG" }, { &EnabledChannels, 1, "ENCH" }, { &IRQFrameMode, 1, "IQFM" }, { &nreg, 2 | FCEUSTATE_RLSB, "NREG" }, { &TriMode, 1, "TRIM" }, { &TriCount, 1, "TRIC" }, { &EnvUnits[0].Speed, 1, "E0SP" }, { &EnvUnits[1].Speed, 1, "E1SP" }, { &EnvUnits[2].Speed, 1, "E2SP" }, { &EnvUnits[0].Mode, 1, "E0MO" }, { &EnvUnits[1].Mode, 1, "E1MO" }, { &EnvUnits[2].Mode, 1, "E2MO" }, { &EnvUnits[0].DecCountTo1, 1, "E0D1" }, { &EnvUnits[1].DecCountTo1, 1, "E1D1" }, { &EnvUnits[2].DecCountTo1, 1, "E2D1" }, { &EnvUnits[0].decvolume, 1, "E0DV" }, { &EnvUnits[1].decvolume, 1, "E1DV" }, { &EnvUnits[2].decvolume, 1, "E2DV" }, { &lengthcount[0], 4 | FCEUSTATE_RLSB, "LEN0" }, { &lengthcount[1], 4 | FCEUSTATE_RLSB, "LEN1" }, { &lengthcount[2], 4 | FCEUSTATE_RLSB, "LEN2" }, { &lengthcount[3], 4 | FCEUSTATE_RLSB, "LEN3" }, { sweepon, 2, "SWEE" }, { &curfreq[0], 4 | FCEUSTATE_RLSB, "CRF1" }, { &curfreq[1], 4 | FCEUSTATE_RLSB, "CRF2" }, { SweepCount, 2, "SWCT" }, { &SIRQStat, 1, "SIRQ" }, { &DMCacc, 4 | FCEUSTATE_RLSB, "5ACC" }, { &DMCBitCount, 1, "5BIT" }, { &DMCAddress, 4 | FCEUSTATE_RLSB, "5ADD" }, { &DMCSize, 4 | FCEUSTATE_RLSB, "5SIZ" }, { &DMCShift, 1, "5SHF" }, { &DMCHaveDMA, 1, "5VDM" }, { &DMCHaveSample, 1, "5VSP" }, { &DMCSizeLatch, 1, "5SZL" }, { &DMCAddressLatch, 1, "5ADL" }, { &DMCFormat, 1, "5FMT" }, { &RawDALatch, 1, "RWDA" }, /* these are important for smooth sound after loading state */ { &sqacc[0], sizeof(sqacc[0]) | FCEUSTATE_RLSB, "SAC1" }, { &sqacc[1], sizeof(sqacc[1]) | FCEUSTATE_RLSB, "SAC2" }, { &RectDutyCount[0], sizeof(RectDutyCount[0]) | FCEUSTATE_RLSB, "RCD1"}, { &RectDutyCount[1], sizeof(RectDutyCount[1]) | FCEUSTATE_RLSB, "RCD2"}, { &tristep, sizeof(tristep) | FCEUSTATE_RLSB, "TRIS"}, { &lq_triacc, sizeof(lq_triacc) | FCEUSTATE_RLSB, "TACC" }, { &lq_noiseacc, sizeof(lq_noiseacc) | FCEUSTATE_RLSB, "NACC" }, /* less important but still necessary */ { &ChannelBC[0], sizeof(ChannelBC[0]) | FCEUSTATE_RLSB, "CBC1" }, { &ChannelBC[1], sizeof(ChannelBC[1]) | FCEUSTATE_RLSB, "CBC2" }, { &ChannelBC[2], sizeof(ChannelBC[2]) | FCEUSTATE_RLSB, "CBC3" }, { &ChannelBC[3], sizeof(ChannelBC[3]) | FCEUSTATE_RLSB, "CBC4" }, { &ChannelBC[4], sizeof(ChannelBC[4]) | FCEUSTATE_RLSB, "CBC5" }, { &sound_timestamp, sizeof(sound_timestamp) | FCEUSTATE_RLSB, "SNTS" }, { &soundtsoffs, sizeof(soundtsoffs) | FCEUSTATE_RLSB, "TSOF"}, { &wlcount[0], sizeof(wlcount[0]) | FCEUSTATE_RLSB, "WLC1" }, { &wlcount[1], sizeof(wlcount[1]) | FCEUSTATE_RLSB, "WLC2" }, { &wlcount[2], sizeof(wlcount[2]) | FCEUSTATE_RLSB, "WLC3" }, { &wlcount[3], sizeof(wlcount[3]) | FCEUSTATE_RLSB, "WLC4" }, { &sexyfilter_acc1, sizeof(sexyfilter_acc1) | FCEUSTATE_RLSB, "FAC1" }, { &sexyfilter_acc2, sizeof(sexyfilter_acc2) | FCEUSTATE_RLSB, "FAC2" }, { &sexyfilter2_acc, sizeof(sexyfilter2_acc) | FCEUSTATE_RLSB, "FAC3" }, { &lq_tcout, sizeof(lq_tcout) | FCEUSTATE_RLSB, "TCOU"}, /* 2018-12-14 - Wii and possibly other big-endian platforms are having * issues loading states with this. Increasing it only helps a few games. * Disabling this state variable for Wii/WiiU/GC for now. */ /* TODO: fix this for better runahead feature for big-endian */ /* UPDATE: Try to ignore this for all big-endian for now */ #ifndef MSB_FIRST /* wave buffer is used for filtering, only need first 17 values from it */ { &Wave, 32 * sizeof(int32_t), "WAVE"}, #endif { 0 } }; void FCEUSND_SaveState(void) { } void FCEUSND_LoadState(int version) { int i; LoadDMCPeriod(DMCFormat & 0xF); RawDALatch &= 0x7F; DMCAddress &= 0x7FFF; /* minimal validation */ for (i = 0; i < 5; i++) { uint32_t BC_max = 15; if (FSettings.soundq == 2) { BC_max = 1025; } else if (FSettings.soundq == 1) { BC_max = 485; } if (/* ChannelBC[i] < 0 || */ ChannelBC[i] > BC_max) { ChannelBC[i] = 0; } } for (i = 0; i < 4; i++) { if (wlcount[i] < 0 || wlcount[i] > 2048) { wlcount[i] = 2048; } } for (i = 0; i < 2; i++) { if (RectDutyCount[i] < 0 || RectDutyCount[i] > 7) { RectDutyCount[i] = 7; } } /* Comparison is always false because access to array >= 0. */ /* if (sound_timestamp < 0) { sound_timestamp = 0; } if (soundtsoffs < 0) { soundtsoffs = 0; } */ if (soundtsoffs + sound_timestamp >= soundtsinc) { soundtsoffs = 0; sound_timestamp = 0; } if (tristep > 32) { tristep &= 0x1F; } }