summaryrefslogtreecommitdiffstats
path: root/apps/codecs/nsf.c
diff options
context:
space:
mode:
authorAdam Gashlin <agashlin@gmail.com>2007-01-25 18:06:17 +0000
committerAdam Gashlin <agashlin@gmail.com>2007-01-25 18:06:17 +0000
commit5d20c9d1216f1ccce1921884bfa4c0737a6f69ee (patch)
tree00b7db687a040a03db99bf983c148bb1ff69bc2e /apps/codecs/nsf.c
parent2e020e6e1e12dc471c99399cdbc85006b482f6be (diff)
downloadrockbox-5d20c9d1216f1ccce1921884bfa4c0737a6f69ee.tar.gz
rockbox-5d20c9d1216f1ccce1921884bfa4c0737a6f69ee.tar.bz2
rockbox-5d20c9d1216f1ccce1921884bfa4c0737a6f69ee.zip
adding NSF (NES music) codec
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12112 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/codecs/nsf.c')
-rw-r--r--apps/codecs/nsf.c4513
1 files changed, 4513 insertions, 0 deletions
diff --git a/apps/codecs/nsf.c b/apps/codecs/nsf.c
new file mode 100644
index 0000000000..8ea7047177
--- /dev/null
+++ b/apps/codecs/nsf.c
@@ -0,0 +1,4513 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ *
+ * Copyright (C) 2006 Adam Gashlin (hcs)
+ * Copyright (C) 2004 Disch
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+/*
+ * This is a perversion of Disch's excellent NotSoFatso.
+ */
+
+#include "codeclib.h"
+#include "inttypes.h"
+#include "system.h"
+
+CODEC_HEADER
+
+/* arm doesn't benefit from IRAM? */
+#ifdef CPU_ARM
+#undef ICODE_ATTR
+#define ICODE_ATTR
+#undef IDATA_ATTR
+#define IDATA_ATTR
+#else
+#define ICODE_INSTEAD_OF_INLINE
+#endif
+
+/* Maximum number of bytes to process in one iteration */
+#define WAV_CHUNK_SIZE (1024*2)
+
+static int16_t samples[WAV_CHUNK_SIZE] IBSS_ATTR;
+
+#define ZEROMEMORY(addr,size) memset(addr,0,size)
+
+/* simple profiling with USEC_TIMER
+
+#define NSF_PROFILE
+
+*/
+
+#ifdef NSF_PROFILE
+
+#define CREATE_TIMER(name) uint32_t nsf_timer_##name##_start,\
+ nsf_timer_##name##_total
+#define ENTER_TIMER(name) nsf_timer_##name##_start=USEC_TIMER
+#define EXIT_TIMER(name) nsf_timer_##name##_total+=\
+ (USEC_TIMER-nsf_timer_##name##_start)
+#define READ_TIMER(name) (nsf_timer_##name##_total)
+#define RESET_TIMER(name) nsf_timer_##name##_total=0
+
+#define PRINT_TIMER_PCT(bname,tname,nstr) ci->fdprintf(
+ logfd,"%10ld ",READ_TIMER(bname));\
+ ci->fdprintf(logfd,"(%3d%%) " nstr "\t",\
+ ((uint64_t)READ_TIMER(bname))*100/READ_TIMER(tname))
+
+CREATE_TIMER(total);
+CREATE_TIMER(cpu);
+CREATE_TIMER(apu);
+CREATE_TIMER(squares);
+CREATE_TIMER(tnd);
+CREATE_TIMER(tnd_enter);
+CREATE_TIMER(tnd_tri);
+CREATE_TIMER(tnd_noise);
+CREATE_TIMER(tnd_dmc);
+CREATE_TIMER(fds);
+CREATE_TIMER(frame);
+CREATE_TIMER(mix);
+
+void reset_profile_timers(void) {
+ RESET_TIMER(total);
+ RESET_TIMER(cpu);
+ RESET_TIMER(apu);
+ RESET_TIMER(squares);
+ RESET_TIMER(tnd);
+ RESET_TIMER(tnd_enter);
+ RESET_TIMER(tnd_tri);
+ RESET_TIMER(tnd_noise);
+ RESET_TIMER(tnd_dmc);
+ RESET_TIMER(fds);
+ RESET_TIMER(frame);
+ RESET_TIMER(mix);
+}
+
+int logfd=-1;
+
+void print_timers(char * path, int track) {
+ logfd = ci->open("/nsflog.txt",O_WRONLY|O_CREAT|O_APPEND);
+ ci->fdprintf(logfd,"%s[%d]:\t",path,track);
+ ci->fdprintf(logfd,"%10ld total\t",READ_TIMER(total));
+ PRINT_TIMER_PCT(cpu,total,"CPU");
+ PRINT_TIMER_PCT(apu,total,"APU");
+ ci->fdprintf(logfd,"\n\t");
+ PRINT_TIMER_PCT(squares,apu,"squares");
+ PRINT_TIMER_PCT(frame,apu,"frame");
+ PRINT_TIMER_PCT(mix,apu,"mix");
+ PRINT_TIMER_PCT(fds,apu,"FDS");
+ PRINT_TIMER_PCT(tnd,apu,"tnd");
+ ci->fdprintf(logfd,"\n\t\t");
+ PRINT_TIMER_PCT(tnd_enter,tnd,"enter");
+ PRINT_TIMER_PCT(tnd_tri,tnd,"triangle");
+ PRINT_TIMER_PCT(tnd_noise,tnd,"noise");
+ PRINT_TIMER_PCT(tnd_dmc,tnd,"DMC");
+ ci->fdprintf(logfd,"\n");
+
+ ci->close(logfd);
+ logfd=-1;
+}
+
+#else
+
+#define CREATE_TIMER(name)
+#define ENTER_TIMER(name)
+#define EXIT_TIMER(name)
+#define READ_TIMER(name)
+#define RESET_TIMER(name)
+#define print_timers(path,track)
+#define reset_profile_timers()
+
+#endif
+
+/* proper handling of multibyte values */
+#ifdef ROCKBOX_LITTLE_ENDIAN
+union TWIN
+{
+ uint16_t W;
+ struct{ uint8_t l; uint8_t h; } B;
+};
+
+union QUAD
+{
+ uint32_t D;
+ struct{ uint8_t l; uint8_t h; uint16_t w; } B;
+};
+#else
+
+union TWIN
+{
+ uint16_t W;
+ struct{ uint8_t h; uint8_t l; } B;
+};
+
+union QUAD
+{
+ uint32_t D;
+ struct{uint16_t w; uint8_t h; uint8_t l; } B;
+};
+
+#endif
+
+#define NTSC_FREQUENCY 1789772.727273f
+#define PAL_FREQUENCY 1652097.692308f
+#define NTSC_NMIRATE 60.098814f
+#define PAL_NMIRATE 50.006982f
+
+#define NES_FREQUENCY 21477270
+#define NTSC_FRAME_COUNTER_FREQ (NTSC_FREQUENCY / (NES_FREQUENCY / 89490.0f))
+#define PAL_FRAME_COUNTER_FREQ (PAL_FREQUENCY / (NES_FREQUENCY / 89490.0f))
+
+/****************** tables */
+static const int32_t ModulationTable[8] ICONST_ATTR = {0,1,2,4,0,-4,-2,-1};
+const uint16_t DMC_FREQ_TABLE[2][0x10] = {
+ /* NTSC */
+ {0x1AC,0x17C,0x154,0x140,0x11E,0x0FE,0x0E2,0x0D6,0x0BE,0x0A0,0x08E,0x080,
+ 0x06A,0x054,0x048,0x036},
+ /* PAL */
+ {0x18C,0x160,0x13A,0x128,0x108,0x0EA,0x0D0,0x0C6,0x0B0,0x094,0x082,0x076,
+ 0x062,0x04E,0x042,0x032}
+};
+
+const uint8_t DUTY_CYCLE_TABLE[4] = {2,4,8,12};
+
+const uint8_t LENGTH_COUNTER_TABLE[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
+};
+
+const uint16_t NOISE_FREQ_TABLE[0x10] = {
+ 0x004,0x008,0x010,0x020,0x040,0x060,0x080,0x0A0,0x0CA,0x0FE,0x17C,0x1FC,
+ 0x2FA,0x3F8,0x7F2,0xFE4
+};
+
+/****************** NSF loading ******************/
+
+/* file format structs (both are little endian) */
+
+struct NESM_HEADER
+{
+ uint32_t nHeader;
+ uint8_t nHeaderExtra;
+ uint8_t nVersion;
+ uint8_t nTrackCount;
+ uint8_t nInitialTrack;
+ uint16_t nLoadAddress;
+ uint16_t nInitAddress;
+ uint16_t nPlayAddress;
+ uint8_t szGameTitle[32];
+ uint8_t szArtist[32];
+ uint8_t szCopyright[32];
+ uint16_t nSpeedNTSC;
+ uint8_t nBankSwitch[8];
+ uint16_t nSpeedPAL;
+ uint8_t nNTSC_PAL;
+ uint8_t nExtraChip;
+ uint8_t nExpansion[4];
+};
+
+struct NSFE_INFOCHUNK
+{
+ uint16_t nLoadAddress;
+ uint16_t nInitAddress;
+ uint16_t nPlayAddress;
+ uint8_t nIsPal;
+ uint8_t nExt;
+ uint8_t nTrackCount;
+ uint8_t nStartingTrack;
+};
+
+int32_t LoadFile(uint8_t *,size_t);
+
+int32_t LoadFile_NESM(uint8_t *,size_t);
+int32_t LoadFile_NSFE(uint8_t *,size_t);
+
+/* NSF file info */
+
+/* basic NSF info */
+int32_t bIsExtended=0; /* 0 = NSF, 1 = NSFE */
+uint8_t nIsPal=0; /* 0 = NTSC, 1 = PAL,
+ 2,3 = mixed NTSC/PAL (interpretted as NTSC) */
+int32_t nfileLoadAddress=0; /* The address to which the NSF code is
+ loaded */
+int32_t nfileInitAddress=0; /* The address of the Init routine
+ (called at track change) */
+int32_t nfilePlayAddress=0; /* The address of the Play routine
+ (called several times a second) */
+uint8_t nChipExtensions=0; /* Bitwise representation of the external chips
+ used by this NSF. */
+
+/* old NESM speed stuff (blarg) */
+int32_t nNTSC_PlaySpeed=0;
+int32_t nPAL_PlaySpeed=0;
+
+/* track info */
+/* The number of tracks in the NSF (1 = 1 track, 5 = 5 tracks, etc) */
+int32_t nTrackCount=0;
+/* The initial track (ZERO BASED: 0 = 1st track, 4 = 5th track, etc) */
+int32_t nInitialTrack=0;
+
+/* nsf data */
+uint8_t* pDataBuffer=0; /* the buffer containing NSF code. */
+int32_t nDataBufferSize=0; /* the size of the above buffer. */
+
+/* playlist */
+uint8_t nPlaylist[256]; /* Each entry is the zero based index of the
+ song to play */
+int32_t nPlaylistSize=0; /* the number of tracks in the playlist */
+
+/* track time / fade */
+int32_t nTrackTime[256]; /* track times -1 if no track times specified */
+int32_t nTrackFade[256]; /* track fade times -1 if none are specified */
+
+/* string info */
+uint8_t szGameTitle[0x101];
+uint8_t szArtist[0x101];
+uint8_t szCopyright[0x101];
+uint8_t szRipper[0x101];
+
+/* bankswitching info */
+uint8_t nBankswitch[8]={0}; /* The initial bankswitching registers needed
+ * for some NSFs. If the NSF does not use
+ * bankswitching, these values will all be zero
+ */
+
+int32_t LoadFile(uint8_t * inbuffer, size_t size)
+{
+ if(!inbuffer) return -1;
+
+ int32_t ret = -1;
+
+ if(!memcmp(inbuffer,"NESM",4)) ret = LoadFile_NESM(inbuffer,size);
+ if(!memcmp(inbuffer,"NSFE",4)) ret = LoadFile_NSFE(inbuffer,size);
+
+ /*
+ * Snake's revenge puts '00' for the initial track,
+ * which (after subtracting 1) makes it 256 or -1 (bad!)
+ * This prevents that crap
+ */
+ if(nInitialTrack >= nTrackCount)
+ nInitialTrack = 0;
+ if(nInitialTrack < 0)
+ nInitialTrack = 0;
+
+ /* if there's no tracks... this is a crap NSF */
+ if(nTrackCount < 1)
+ {
+ return -1;
+ }
+
+ return ret;
+}
+
+int32_t LoadFile_NESM(uint8_t* inbuffer, size_t size)
+{
+ uint8_t ignoreversion=1;
+ uint8_t needdata=1;
+
+ /* read the info */
+ struct NESM_HEADER hdr;
+
+ memcpy(&hdr,inbuffer,sizeof(hdr));
+
+ /* confirm the header */
+ if(memcmp("NESM",&(hdr.nHeader),4)) return -1;
+ if(hdr.nHeaderExtra != 0x1A) return -1;
+ /* stupid NSFs claim to be above version 1 >_> */
+ if((!ignoreversion) && (hdr.nVersion != 1)) return -1;
+
+ /*
+ * NESM is generally easier to work with (but limited!)
+ * just move the data over from NESM_HEADER over to our member data
+ */
+
+ bIsExtended = 0;
+ nIsPal = hdr.nNTSC_PAL & 0x03;
+ nPAL_PlaySpeed = letoh16(hdr.nSpeedPAL);
+ nNTSC_PlaySpeed = letoh16(hdr.nSpeedNTSC);
+ nfileLoadAddress = letoh16(hdr.nLoadAddress);
+ nfileInitAddress = letoh16(hdr.nInitAddress);
+ nfilePlayAddress = letoh16(hdr.nPlayAddress);
+ nChipExtensions = hdr.nExtraChip;
+
+
+ nTrackCount = hdr.nTrackCount;
+ nInitialTrack = hdr.nInitialTrack - 1;
+
+ memcpy(nBankswitch,hdr.nBankSwitch,8);
+
+ memcpy(szGameTitle,hdr.szGameTitle,32);
+ memcpy(szArtist ,hdr.szArtist ,32);
+ memcpy(szCopyright,hdr.szCopyright,32);
+
+ /* read the NSF data */
+ if(needdata)
+ {
+ pDataBuffer=inbuffer+0x80;
+ nDataBufferSize=size-0x80;
+ }
+
+ /* if we got this far... it was a successful read */
+ return 0;
+}
+
+int32_t LoadFile_NSFE(uint8_t* inbuffer, size_t size)
+{
+ /* the vars we'll be using */
+ uint32_t nChunkType;
+ int32_t nChunkSize;
+ int32_t nChunkUsed;
+ int32_t i;
+ uint8_t * nDataPos = 0;
+ uint8_t bInfoFound = 0;
+ uint8_t bEndFound = 0;
+ uint8_t bBankFound = 0;
+ nPlaylistSize=-1;
+
+ struct NSFE_INFOCHUNK info;
+ ZEROMEMORY(&info,sizeof(struct NSFE_INFOCHUNK));
+ ZEROMEMORY(nBankswitch,8);
+ info.nTrackCount = 1; /* default values */
+
+ if (size < 8) return -1; /* must have at least NSFE,NEND */
+
+ /* confirm the header! */
+ memcpy(&nChunkType,inbuffer,4);
+ inbuffer+=4;
+ if(memcmp(&nChunkType,"NSFE",4)) return -1;
+
+ for (i=0;i<256;i++) {
+ nTrackTime[i]=-1;
+ nTrackFade[i]=-1;
+ }
+
+ /* begin reading chunks */
+ while(!bEndFound)
+ {
+ memcpy(&nChunkSize,inbuffer,4);
+ nChunkSize=letoh32(nChunkSize);
+ inbuffer+=4;
+ memcpy(&nChunkType,inbuffer,4);
+ inbuffer+=4;
+
+ if(!memcmp(&nChunkType,"INFO",4)) {
+ /* only one info chunk permitted */
+ if(bInfoFound) return -1;
+ if(nChunkSize < 8) return -1; /* minimum size */
+
+ bInfoFound = 1;
+ nChunkUsed = MIN((int32_t)sizeof(struct NSFE_INFOCHUNK),
+ nChunkSize);
+
+ memcpy(&info,inbuffer,nChunkUsed);
+ inbuffer+=nChunkSize;
+
+ bIsExtended = 1;
+ nIsPal = info.nIsPal & 3;
+ nfileLoadAddress = letoh16(info.nLoadAddress);
+ nfileInitAddress = letoh16(info.nInitAddress);
+ nfilePlayAddress = letoh16(info.nPlayAddress);
+ nChipExtensions = info.nExt;
+ nTrackCount = info.nTrackCount;
+ nInitialTrack = info.nStartingTrack;
+
+ nPAL_PlaySpeed = (uint16_t)(1000000 / PAL_NMIRATE);
+ nNTSC_PlaySpeed = (uint16_t)(1000000 / NTSC_NMIRATE);
+ } else if (!memcmp(&nChunkType,"DATA",4)) {
+ if(!bInfoFound) return -1;
+ if(nDataPos) return -1;
+ if(nChunkSize < 1) return -1;
+
+ nDataBufferSize = nChunkSize;
+ nDataPos = inbuffer;
+
+ inbuffer+=nChunkSize;
+ } else if (!memcmp(&nChunkType,"NEND",4)) {
+ bEndFound = 1;
+ } else if (!memcmp(&nChunkType,"time",4)) {
+ if(!bInfoFound) return -1;
+ for (nChunkUsed=0; nChunkUsed < MIN(nChunkSize / 4,nTrackCount);
+ nChunkUsed++,inbuffer+=4) {
+ nTrackTime[nChunkUsed]=
+ ((uint32_t)inbuffer[0])|
+ ((uint32_t)inbuffer[1]<<8)|
+ ((uint32_t)inbuffer[2]<<16)|
+ ((uint32_t)inbuffer[3]<<24);
+ }
+
+ inbuffer+=nChunkSize-(nChunkUsed*4);
+
+ /* negative signals to use default time */
+ for(; nChunkUsed < nTrackCount; nChunkUsed++)
+ nTrackTime[nChunkUsed] = -1;
+ } else if (!memcmp(&nChunkType,"fade",4)) {
+ if(!bInfoFound) return -1;
+ for (nChunkUsed=0; nChunkUsed < MIN(nChunkSize / 4,nTrackCount);
+ nChunkUsed++,inbuffer+=4) {
+ nTrackFade[nChunkUsed]=
+ ((uint32_t)inbuffer[0])|
+ ((uint32_t)inbuffer[1]<<8)|
+ ((uint32_t)inbuffer[2]<<16)|
+ ((uint32_t)inbuffer[3]<<24);
+ }
+
+ inbuffer+=nChunkSize-(nChunkUsed*4);
+
+ /* negative signals to use default time */
+ for(; nChunkUsed < nTrackCount; nChunkUsed++)
+ nTrackFade[nChunkUsed] = -1;
+ } else if (!memcmp(&nChunkType,"BANK",4)) {
+ if(bBankFound) return -1;
+
+ bBankFound = 1;
+ nChunkUsed = MIN(8,nChunkSize);
+ memcpy(nBankswitch,inbuffer,nChunkUsed);
+
+ inbuffer+=nChunkSize;
+ } else if (!memcmp(&nChunkType,"plst",4)) {
+
+ nPlaylistSize = nChunkSize;
+ if(nPlaylistSize >= 1) {
+
+ memcpy(nPlaylist,inbuffer,nChunkSize);
+ inbuffer+=nChunkSize;
+ }
+ } else if (!memcmp(&nChunkType,"auth",4)) {
+ uint8_t* ptr;
+
+ ptr = inbuffer;
+
+ uint8_t* ar[4] = {szGameTitle,szArtist,szCopyright,szRipper};
+ int32_t i;
+ for(i = 0; (ptr-inbuffer)<nChunkSize && i < 4; i++)
+ {
+ nChunkUsed = strlen(ptr) + 1;
+ memcpy(ar[i],ptr,nChunkUsed);
+ ptr += nChunkUsed;
+ }
+ inbuffer+=nChunkSize;
+ } else if (!memcmp(&nChunkType,"tlbl",4)) {
+ /* we unfortunately can't use these anyway */
+ inbuffer+=nChunkSize;
+ } else { /* unknown chunk */
+ nChunkType = letoh32(nChunkType)>>24; /* check the first byte */
+ /* chunk is vital... don't continue */
+ if((nChunkType >= 'A') && (nChunkType <= 'Z'))
+ return -1;
+ /* otherwise, just skip it */
+ inbuffer+=nChunkSize;
+ } /* end if series */
+ } /* end while */
+
+ /*
+ * if we exited the while loop without a 'return', we must have hit an NEND
+ * chunk if this is the case, the file was layed out as it was expected.
+ * now.. make sure we found both an info chunk, AND a data chunk... since
+ * these are minimum requirements for a valid NSFE file
+ */
+
+ if(!bInfoFound) return -1;
+ if(!nDataPos) return -1;
+
+ /* if both those chunks existed, this file is valid.
+ Load the data if it's needed */
+
+ pDataBuffer=nDataPos;
+
+ /* return success! */
+ return 0;
+}
+
+
+/****************** Audio Device Structures ******************/
+
+struct FDSWave
+{
+ /* Envelope Unit */
+ uint8_t bEnvelopeEnable;
+ uint8_t nEnvelopeSpeed;
+
+ /* Volume Envelope */
+ uint8_t nVolEnv_Mode;
+ uint8_t nVolEnv_Decay;
+ uint8_t nVolEnv_Gain;
+ int32_t nVolEnv_Timer;
+ int32_t nVolEnv_Count;
+ uint8_t nVolume;
+ uint8_t bVolEnv_On;
+
+ /* Sweep Envenlope */
+ uint8_t nSweep_Mode;
+ uint8_t nSweep_Decay;
+ int32_t nSweep_Timer;
+ int32_t nSweep_Count;
+ uint8_t nSweep_Gain;
+ uint8_t bSweepEnv_On;
+
+ /* Effector / LFO / Modulation Unit */
+ int32_t nSweepBias;
+ uint8_t bLFO_Enabled;
+ union TWIN nLFO_Freq;
+ /*float fLFO_Timer;*/
+ /*float fLFO_Count;*/
+ int32_t nLFO_Timer; /* -17.14*/
+ int32_t nLFO_Count; /* -17.14*/
+ uint8_t nLFO_Addr;
+ uint8_t nLFO_Table[0x40];
+ uint8_t bLFO_On;
+
+ /* Main Output */
+ uint8_t nMainVolume;
+ uint8_t bEnabled;
+ union TWIN nFreq;
+ /*float fFreqCount;*/
+ int32_t nFreqCount; /* -17.14 */
+ uint8_t nMainAddr;
+ uint8_t nWaveTable[0x40];
+ uint8_t bWaveWrite;
+ uint8_t bMain_On;
+
+ /* Output and Downsampling */
+ int32_t nMixL;
+
+ /* Pop Reducer */
+ uint8_t bPopReducer;
+ uint8_t nPopOutput;
+ int32_t nPopCount;
+
+};
+int16_t FDS_nOutputTable_L[4][0x21][0x40];
+
+struct FME07Wave
+{
+ /* Frequency Control */
+ union TWIN nFreqTimer;
+ int32_t nFreqCount;
+
+ /* Channel Disabling */
+ uint8_t bChannelEnabled;
+
+ /* Volume */
+ uint8_t nVolume;
+
+ /* Duty Cycle */
+ uint8_t nDutyCount;
+
+ /* Output and Downsampling */
+ int32_t nMixL;
+};
+
+int16_t FME07_nOutputTable_L[3][0x10] IDATA_ATTR;
+
+struct N106Wave
+{
+ /* All Channel Stuff */
+
+ uint8_t nActiveChannels;
+ uint8_t bAutoIncrement;
+ uint8_t nCurrentAddress;
+ uint8_t nRAM[0x100]; /* internal memory for registers/wave data */
+ float fFrequencyLookupTable[8]; /* lookup tbl for freq conversions */
+
+ /*
+ * Individual channel stuff
+ */
+ /* Wavelength / Frequency */
+ union QUAD nFreqReg[8];
+ float fFreqTimer[8];
+ float fFreqCount[8];
+
+ /* Wave data length / remaining */
+ uint8_t nWaveSize[8];
+ uint8_t nWaveRemaining[8];
+
+ /* Wave data position */
+ uint8_t nWavePosStart[8];
+ uint8_t nWavePos[8];
+ uint8_t nOutput[8];
+
+ /* Volume */
+ uint8_t nVolume[8];
+
+ /* Pop Reducer */
+ uint8_t nPreVolume[8];
+ uint8_t nPopCheck[8];
+
+ /* Mixing */
+ int32_t nMixL[8];
+};
+
+int16_t N106_nOutputTable_L[8][0x10][0x10];
+
+struct VRC6PulseWave
+{
+
+ /* Frequency Control */
+ union TWIN nFreqTimer;
+ int32_t nFreqCount;
+
+ /* Flags */
+ uint8_t bChannelEnabled;
+ uint8_t bDigitized;
+
+ /* Volume */
+ uint8_t nVolume;
+
+ /* Duty Cycle */
+ uint8_t nDutyCycle;
+ uint8_t nDutyCount;
+
+ /* Output and Downsampling */
+ int32_t nMixL;
+
+};
+
+int16_t VRC6Pulse_nOutputTable_L[2][0x10] IDATA_ATTR;
+
+struct VRC6SawWave
+{
+
+ /* Frequency Control */
+ union TWIN nFreqTimer;
+ int32_t nFreqCount;
+
+ /* Flags */
+ uint8_t bChannelEnabled;
+
+ /* Phase Accumulator */
+ uint8_t nAccumRate;
+ uint8_t nAccum;
+ uint8_t nAccumStep;
+
+ /* Output and Downsampling */
+ int32_t nMixL;
+
+};
+
+int16_t VRC6Saw_nOutputTable_L[0x20] IDATA_ATTR;
+
+struct Wave_Squares
+{
+
+ /* Programmable Timer */
+ union TWIN nFreqTimer[2];
+ int32_t nFreqCount[2];
+
+ /* Length Counter */
+ uint8_t nLengthCount[2];
+ uint8_t bLengthEnabled[2];
+ uint8_t bChannelEnabled[2];
+
+ /* Volume / Decay */
+ uint8_t nVolume[2];
+ uint8_t nDecayVolume[2];
+ uint8_t bDecayEnable[2];
+ uint8_t bDecayLoop[2];
+ uint8_t nDecayTimer[2];
+ uint8_t nDecayCount[2];
+
+ /* Sweep Unit */
+ uint8_t bSweepEnable[2];
+ uint8_t bSweepMode[2];
+ uint8_t bSweepForceSilence[2];
+ uint8_t nSweepTimer[2];
+ uint8_t nSweepCount[2];
+ uint8_t nSweepShift[2];
+
+ /* Duty Cycle */
+ uint8_t nDutyCount[2];
+ uint8_t nDutyCycle[2];
+
+ /* Output and Downsampling */
+ int32_t nMixL;
+};
+
+int16_t Squares_nOutputTable_L[0x100] IDATA_ATTR;
+
+struct Wave_TND
+{
+
+ /*
+ * Triangle
+ */
+
+ /* Programmable Timer */
+ union TWIN nTriFreqTimer;
+ int32_t nTriFreqCount;
+
+ /* Length Counter */
+ uint8_t nTriLengthCount;
+ uint8_t bTriLengthEnabled;
+ uint8_t bTriChannelEnabled;
+
+ /* Linear Counter */
+ uint8_t nTriLinearCount;
+ uint8_t nTriLinearLoad;
+ uint8_t bTriLinearHalt;
+ uint8_t bTriLinearControl;
+
+ /* Tri-Step Generator / Output */
+ uint8_t nTriStep;
+ uint8_t nTriOutput;
+
+ /*
+ * Noise
+ */
+
+ /* Programmable Timer */
+ uint16_t nNoiseFreqTimer;
+ int32_t nNoiseFreqCount;
+
+ /* Length Counter */
+ uint8_t nNoiseLengthCount;
+ uint8_t bNoiseLengthEnabled;
+ uint8_t bNoiseChannelEnabled;
+
+ /* Volume / Decay */
+ uint8_t nNoiseVolume;
+ uint8_t nNoiseDecayVolume;
+ uint8_t bNoiseDecayEnable;
+ uint8_t bNoiseDecayLoop;
+ uint8_t nNoiseDecayTimer;
+ uint8_t nNoiseDecayCount;
+
+ /* Random Number Generator */
+ uint16_t nNoiseRandomShift;
+ uint8_t bNoiseRandomMode; /* 1 = 32k, 6 = 93-bit */
+ uint8_t bNoiseRandomOut;
+
+ /*
+ * DMC
+ */
+
+ /* Play Mode */
+ uint8_t bDMCLoop;
+ uint8_t bDMCIRQEnabled;
+ uint8_t bDMCIRQPending;
+
+ /* Address / DMA */
+ uint8_t nDMCDMABank_Load;
+ uint16_t nDMCDMAAddr_Load;
+ uint8_t nDMCDMABank;
+ uint16_t nDMCDMAAddr;
+ uint8_t* pDMCDMAPtr[8];
+
+ /* Length / Input */
+ uint16_t nDMCLength;
+ uint16_t nDMCBytesRemaining;
+ uint8_t nDMCDelta;
+ uint8_t nDMCDeltaBit;
+ uint8_t bDMCDeltaSilent;
+ uint8_t nDMCSampleBuffer;
+ uint8_t bDMCSampleBufferEmpty;
+
+ /* Frequency */
+ uint16_t nDMCFreqTimer;
+ int32_t nDMCFreqCount;
+
+ /* Output */
+ uint8_t bDMCActive;
+ uint8_t nDMCOutput;
+
+ int32_t nMixL;
+};
+
+/* channels */
+struct Wave_Squares mWave_Squares IDATA_ATTR; /* Square channels 1 and 2 */
+struct Wave_TND mWave_TND IDATA_ATTR; /* Triangle/Noise/DMC channels */
+struct VRC6PulseWave mWave_VRC6Pulse[2] IDATA_ATTR;
+struct VRC6SawWave mWave_VRC6Saw IDATA_ATTR;
+struct N106Wave mWave_N106;
+struct FDSWave mWave_FDS IDATA_ATTR;
+struct FME07Wave mWave_FME07[3] IDATA_ATTR; /* FME-07's 3 pulse channels */
+
+
+/****************** MMC5 ******************/
+/* will include MMC5 sound channels some day,
+ currently only multiply is supported */
+
+/****************** N106 (Disch loves this chip) ******************/
+
+#ifdef ICODE_INSTEAD_OF_INLINE
+void Wave_N106_DoTicks(const int32_t ticks) ICODE_ATTR;
+void Wave_N106_DoTicks(const int32_t ticks)
+#else
+inline void Wave_N106_DoTicks(const int32_t ticks);
+inline void Wave_N106_DoTicks(const int32_t ticks)
+#endif
+{
+ register int32_t i;
+
+ for(i = (7 - mWave_N106.nActiveChannels); i < 8; i++)
+ {
+ if(!mWave_N106.nFreqReg[i].D)
+ {
+ /* written frequency of zero will cause divide by zero error
+ makes me wonder if the formula was supposed to be Reg+1 */
+ mWave_N106.nVolume[i] = mWave_N106.nPreVolume[i];
+ continue;
+ }
+
+ {
+ mWave_N106.nMixL[i] =
+ N106_nOutputTable_L[i][mWave_N106.nVolume[i]]
+ [mWave_N106.nOutput[i]];
+
+ if(mWave_N106.fFreqTimer[i] < 0)
+ mWave_N106.fFreqTimer[i] =
+ (mWave_N106.fFrequencyLookupTable[mWave_N106.nActiveChannels] /
+ mWave_N106.nFreqReg[i].D);
+ if(mWave_N106.fFreqCount[i] > mWave_N106.fFreqTimer[i])
+ mWave_N106.fFreqCount[i] = mWave_N106.fFreqTimer[i];
+
+ mWave_N106.fFreqCount[i] -= ticks;
+ while(mWave_N106.fFreqCount[i] <= 0)
+ {
+ mWave_N106.fFreqCount[i] += mWave_N106.fFreqTimer[i];
+ if(mWave_N106.nWaveRemaining[i])
+ {
+ mWave_N106.nWaveRemaining[i]--;
+ mWave_N106.nWavePos[i]++;
+ }
+ if(!mWave_N106.nWaveRemaining[i])
+ {
+ mWave_N106.nWaveRemaining[i] = mWave_N106.nWaveSize[i];
+ mWave_N106.nWavePos[i] = mWave_N106.nWavePosStart[i];
+ if(mWave_N106.nVolume[i] != mWave_N106.nPreVolume[i])
+ {
+ if(++mWave_N106.nPopCheck[i] >= 2)
+ {
+ mWave_N106.nPopCheck[i] = 0;
+ mWave_N106.nVolume[i] = mWave_N106.nPreVolume[i];
+ }
+ }
+ }
+
+ mWave_N106.nOutput[i] =
+ mWave_N106.nRAM[mWave_N106.nWavePos[i]];
+
+ if(!mWave_N106.nOutput[i])
+ {
+ mWave_N106.nPopCheck[i] = 0;
+ mWave_N106.nVolume[i] = mWave_N106.nPreVolume[i];
+ }
+
+ }
+ }
+ }
+}
+
+/****************** Square waves ******************/
+
+/* decay */
+#ifdef ICODE_INSTEAD_OF_INLINE
+void Wave_Squares_ClockMajor(void) ICODE_ATTR;
+void Wave_Squares_ClockMajor()
+#else
+inline void Wave_Squares_ClockMajor(void);
+inline void Wave_Squares_ClockMajor()
+#endif
+{
+ if(mWave_Squares.nDecayCount[0])
+ mWave_Squares.nDecayCount[0]--;
+ else
+ {
+ mWave_Squares.nDecayCount[0] = mWave_Squares.nDecayTimer[0];
+ if(mWave_Squares.nDecayVolume[0])
+ mWave_Squares.nDecayVolume[0]--;
+ else
+ {
+ if(mWave_Squares.bDecayLoop[0])
+ mWave_Squares.nDecayVolume[0] = 0x0F;
+ }
+
+ if(mWave_Squares.bDecayEnable[0])
+ mWave_Squares.nVolume[0] = mWave_Squares.nDecayVolume[0];
+ }
+
+ if(mWave_Squares.nDecayCount[1])
+ mWave_Squares.nDecayCount[1]--;
+ else
+ {
+ mWave_Squares.nDecayCount[1] = mWave_Squares.nDecayTimer[1];
+ if(mWave_Squares.nDecayVolume[1])
+ mWave_Squares.nDecayVolume[1]--;
+ else
+ {
+ if(mWave_Squares.bDecayLoop[1])
+ mWave_Squares.nDecayVolume[1] = 0x0F;
+ }
+
+ if(mWave_Squares.bDecayEnable[1])
+ mWave_Squares.nVolume[1] = mWave_Squares.nDecayVolume[1];
+ }
+
+}
+
+
+#ifdef ICODE_INSTEAD_OF_INLINE
+void Wave_Squares_CheckSweepForcedSilence(const int32_t i) ICODE_ATTR;
+void Wave_Squares_CheckSweepForcedSilence(const int32_t i)
+#else
+inline void Wave_Squares_CheckSweepForcedSilence(const int32_t i);
+inline void Wave_Squares_CheckSweepForcedSilence(const int32_t i)
+#endif
+{
+ if(mWave_Squares.nFreqTimer[i].W < 8) {
+ mWave_Squares.bSweepForceSilence[i] = 1; return;
+ }
+ if(!mWave_Squares.bSweepMode[i] &&
+ (( mWave_Squares.nFreqTimer[i].W +
+ (mWave_Squares.nFreqTimer[i].W >> mWave_Squares.nSweepShift[i]))
+ >= 0x0800)) { mWave_Squares.bSweepForceSilence[i] = 1; return; }
+
+ mWave_Squares.bSweepForceSilence[i] = 0;
+}
+
+/* sweep / length */
+#ifdef ICODE_INSTEAD_OF_INLINE
+void Wave_Squares_ClockMinor(void) ICODE_ATTR;
+void Wave_Squares_ClockMinor()
+#else
+inline void Wave_Squares_ClockMinor(void);
+inline void Wave_Squares_ClockMinor()
+#endif
+{
+/* unrolled a little loop
+ static int i = 0;
+ for(i = 0; i < 2; i++)
+ {
+*/
+ if(mWave_Squares.bLengthEnabled[0] && mWave_Squares.nLengthCount[0])
+ mWave_Squares.nLengthCount[0]--;
+
+ if(!mWave_Squares.bSweepEnable[0] || !mWave_Squares.nLengthCount[0] ||
+ mWave_Squares.bSweepForceSilence[0] || !mWave_Squares.nSweepShift[0])
+ goto other_square;
+
+ if(mWave_Squares.nSweepCount[0])
+ mWave_Squares.nSweepCount[0]--;
+ else
+ {
+ mWave_Squares.nSweepCount[0] = mWave_Squares.nSweepTimer[0];
+ if(mWave_Squares.bSweepMode[0]) mWave_Squares.nFreqTimer[0].W -=
+ (mWave_Squares.nFreqTimer[0].W >> mWave_Squares.nSweepShift[0])+1;
+ else mWave_Squares.nFreqTimer[0].W +=
+ (mWave_Squares.nFreqTimer[0].W >> mWave_Squares.nSweepShift[0]);
+
+ Wave_Squares_CheckSweepForcedSilence(0);
+ }
+
+ /* */
+other_square:
+ if(mWave_Squares.bLengthEnabled[1] && mWave_Squares.nLengthCount[1])
+ mWave_Squares.nLengthCount[1]--;
+
+ if(!mWave_Squares.bSweepEnable[1] || !mWave_Squares.nLengthCount[1] ||
+ mWave_Squares.bSweepForceSilence[1] || !mWave_Squares.nSweepShift[1])
+ return;
+
+ if(mWave_Squares.nSweepCount[1])
+ mWave_Squares.nSweepCount[1]--;
+ else
+ {
+ mWave_Squares.nSweepCount[1] = mWave_Squares.nSweepTimer[1];
+ if(mWave_Squares.bSweepMode[1]) mWave_Squares.nFreqTimer[1].W -=
+ (mWave_Squares.nFreqTimer[1].W >> mWave_Squares.nSweepShift[1]);
+ else mWave_Squares.nFreqTimer[1].W +=
+ (mWave_Squares.nFreqTimer[1].W >> mWave_Squares.nSweepShift[1]);
+
+ Wave_Squares_CheckSweepForcedSilence(1);
+ }
+}
+
+/****************** Triangle/noise/DMC ******************/
+
+/* decay (noise), linear (tri) */
+
+#ifdef ICODE_INSTEAD_OF_INLINE
+void Wave_TND_ClockMajor(void) ICODE_ATTR;
+void Wave_TND_ClockMajor()
+#else
+inline void Wave_TND_ClockMajor(void);
+inline void Wave_TND_ClockMajor()
+#endif
+{
+ /* noise's decay */
+ if(mWave_TND.nNoiseDecayCount)
+ mWave_TND.nNoiseDecayCount--;
+ else
+ {
+ mWave_TND.nNoiseDecayCount = mWave_TND.nNoiseDecayTimer;
+ if(mWave_TND.nNoiseDecayVolume)
+ mWave_TND.nNoiseDecayVolume--;
+ else
+ {
+ if(mWave_TND.bNoiseDecayLoop)
+ mWave_TND.nNoiseDecayVolume = 0x0F;
+ }
+
+ if(mWave_TND.bNoiseDecayEnable)
+ mWave_TND.nNoiseVolume = mWave_TND.nNoiseDecayVolume;
+ }
+
+ /* triangle's linear */
+ if(mWave_TND.bTriLinearHalt)
+ mWave_TND.nTriLinearCount = mWave_TND.nTriLinearLoad;
+ else if(mWave_TND.nTriLinearCount)
+ mWave_TND.nTriLinearCount--;
+
+ if(!mWave_TND.bTriLinearControl)
+ mWave_TND.bTriLinearHalt = 0;
+}
+
+/* length */
+
+#ifdef ICODE_INSTEAD_OF_INLINE
+void Wave_TND_ClockMinor(void) ICODE_ATTR;
+void Wave_TND_ClockMinor()
+#else
+inline void Wave_TND_ClockMinor(void);
+inline void Wave_TND_ClockMinor()
+#endif
+{
+ if(mWave_TND.bNoiseLengthEnabled && mWave_TND.nNoiseLengthCount)
+ mWave_TND.nNoiseLengthCount--;
+
+ if(mWave_TND.bTriLengthEnabled && mWave_TND.nTriLengthCount)
+ mWave_TND.nTriLengthCount--;
+}
+
+/*#undef this*/
+
+/****************** NSF Core ******************/
+
+/* start globals */
+/*
+ * Initialization flags (TODO: make extinct)
+ */
+uint8_t bMemoryOK; /* did memory get allocated ok? */
+uint8_t bFileLoaded; /* is a file loaded? */
+uint8_t bTrackSelected; /* did they select a track? */
+uint8_t bIsGeneratingSamples; /* currently generating samples... */
+
+/*
+ * Memory
+ */
+/* RAM: 0x0000 - 0x07FF */
+uint8_t pRAM[0x800] IDATA_ATTR;
+/* SRAM: 0x6000 - 0x7FFF (non-FDS only) */
+uint8_t pSRAM[0x2000];
+/* ExRAM: 0x5C00 - 0x5FF5 (MMC5 only)
+ * Also holds NSF player code (at 0x5000 - 0x500F) */
+uint8_t pExRAM[0x1000];
+/* Full ROM buffer */
+uint8_t* pROM_Full IDATA_ATTR;
+
+uint16_t main_nOutputTable_L[0x8000];
+
+uint8_t* pROM[10] IDATA_ATTR;/* ROM banks (point to areas in pROM_Full) */
+ /* 0x8000 - 0xFFFF */
+ /* also includes 0x6000 - 0x7FFF (FDS only) */
+uint8_t* pStack; /* the stack (points to areas in pRAM) */
+ /* 0x0100 - 0x01FF */
+
+int32_t nROMSize; /* size of this ROM file in bytes */
+int32_t nROMBankCount; /* max number of 4k banks */
+int32_t nROMMaxSize; /* size of allocated pROM_Full buffer */
+
+/*
+ * Memory Proc Pointers
+ */
+
+typedef uint8_t ( *ReadProc)(uint16_t);
+typedef void ( *WriteProc)(uint16_t,uint8_t);
+ReadProc ReadMemory[0x10] IDATA_ATTR;
+WriteProc WriteMemory[0x10] IDATA_ATTR;
+
+/*
+ * 6502 Registers / Mode
+ */
+
+uint8_t regA IDATA_ATTR; /* Accumulator */
+uint8_t regX IDATA_ATTR; /* X-Index */
+uint8_t regY IDATA_ATTR; /* Y-Index */
+uint8_t regP IDATA_ATTR; /* Processor Status */
+uint8_t regSP IDATA_ATTR; /* Stack Pointer */
+uint16_t regPC IDATA_ATTR; /* Program Counter */
+
+uint8_t bPALMode IDATA_ATTR;/* 1 if in PAL emulation mode, 0 if in NTSC */
+uint8_t bCPUJammed IDATA_ATTR; /* 0 = not jammed. 1 = really jammed.
+ * 2 = 'fake' jammed */
+ /* fake jam caused by the NSF code to signal
+ * the end of the play/init routine */
+
+/* Multiplication Register, for MMC5 chip only (5205+5206) */
+uint8_t nMultIn_Low;
+uint8_t nMultIn_High;
+
+/*
+ * NSF Preparation Information
+ */
+
+uint8_t nBankswitchInitValues[10]; /* banks to swap to on tune init */
+uint16_t nPlayAddress; /* Play routine address */
+uint16_t nInitAddress; /* Init routine address */
+
+uint8_t nExternalSound; /* external sound chips */
+uint8_t nCurTrack;
+
+float fNSFPlaybackSpeed;
+
+/*
+ * pAPU
+ */
+
+uint8_t nFrameCounter; /* Frame Sequence Counter */
+uint8_t nFrameCounterMax; /* Frame Sequence Counter Size
+ (3 or 4 depending on $4017.7) */
+uint8_t bFrameIRQEnabled; /* TRUE if frame IRQs are enabled */
+uint8_t bFrameIRQPending; /* TRUE if the frame sequencer is holding down
+ an IRQ */
+
+uint8_t nFME07_Address;
+
+/*
+ * Timing and Counters
+ */
+/* fixed point -15.16 */
+
+int32_t nTicksUntilNextFrame;
+int32_t nTicksPerPlay;
+int32_t nTicksUntilNextPlay;
+int32_t nTicksPerSample;
+int32_t nTicksUntilNextSample;
+
+uint32_t nCPUCycle IDATA_ATTR;
+uint32_t nAPUCycle IDATA_ATTR;
+
+
+uint32_t nTotalPlays; /* number of times the play subroutine has been called
+ (for tracking output time) */
+/*
+ * Silence Tracker
+ */
+int32_t nSilentSamples;
+int32_t nSilentSampleMax;
+int32_t nSilenceTrackMS;
+uint8_t bNoSilenceIfTime;
+uint8_t bTimeNotDefault;
+
+/*
+ * Sound output options
+ */
+const int32_t nSampleRate=44100;
+
+/*
+ * Volume/fading/filter tracking
+ */
+
+uint32_t nStartFade; /* play call to start fading out */
+uint32_t nEndFade; /* play call to stop fading out (song is over) */
+uint8_t bFade; /* are we fading? */
+float fFadeVolume;
+float fFadeChange;
+
+/*
+ * Designated Output Buffer
+ */
+uint8_t* pOutput IDATA_ATTR;
+
+const uint8_t bDMCPopReducer=1;
+uint8_t nDMCPop_Prev IDATA_ATTR = 0;
+uint8_t bDMCPop_Skip IDATA_ATTR = 0;
+uint8_t bDMCPop_SamePlay IDATA_ATTR = 0;
+
+const uint8_t nForce4017Write=0;
+const uint8_t bN106PopReducer=0;
+const uint8_t bIgnore4011Writes=0;
+
+const uint8_t bIgnoreBRK=0;
+const uint8_t bIgnoreIllegalOps=0;
+const uint8_t bNoWaitForReturn=0;
+const uint8_t bPALPreference=0;
+const uint8_t bCleanAXY=0;
+const uint8_t bResetDuty=0;
+
+/*
+ * Sound Filter
+ */
+
+int64_t nFilterAccL IDATA_ATTR;
+int64_t nHighPass IDATA_ATTR;
+
+int32_t nHighPassBase IDATA_ATTR;
+
+uint8_t bHighPassEnabled IDATA_ATTR;
+
+/* end globals */
+
+#define CLOCK_MAJOR() { Wave_Squares_ClockMajor(); Wave_TND_ClockMajor(); }
+#define CLOCK_MINOR() { Wave_Squares_ClockMinor(); Wave_TND_ClockMinor(); }
+
+#define EXTSOUND_VRC6 0x01
+#define EXTSOUND_VRC7 0x02
+#define EXTSOUND_FDS 0x04
+#define EXTSOUND_MMC5 0x08
+#define EXTSOUND_N106 0x10
+#define EXTSOUND_FME07 0x20
+
+#define SILENCE_THRESHOLD 3
+
+/*
+ * prototypes
+ */
+
+uint32_t Emulate6502(uint32_t runto) ICODE_ATTR;
+void EmulateAPU(uint8_t bBurnCPUCycles) ICODE_ATTR;
+
+int NSFCore_Initialize(void); /* 1 = initialized ok,
+ 0 = couldn't initialize (memory allocation error) */
+
+/*
+ * Song Loading
+ */
+int LoadNSF(int32_t); /* grab data from an existing file
+ 1 = loaded ok, 0 = error loading */
+
+/*
+ * Track Control
+ */
+void SetTrack(uint8_t track); /* Change tracks */
+
+/*
+ * Getting Samples
+ */
+/* fill a buffer with samples */
+int32_t GetSamples(uint8_t* buffer, int32_t buffersize);
+
+/*
+ * Playback options
+ */
+/* Set desired playback options (0 = bad options couldn't be set) */
+int SetPlaybackOptions(int32_t samplerate);
+/* Speed throttling (0 = uses NSF specified speed) */
+void SetPlaybackSpeed(float playspersec);
+
+float GetPlaybackSpeed(void);
+float GetMasterVolume(void);
+
+/*
+ * Seeking
+ */
+/* gets the number of 'play' routine calls executed */
+float GetPlayCalls(void);
+
+/* gets the output time (based on the given play rate,
+ if basedplayspersec is zero, current playback speed is used */
+uint32_t GetWrittenTime(float basedplayspersec);
+/* sets the number of 'plays' routines executed (for precise seeking) */
+void SetPlayCalls(float plays);
+/* sets the written time (approx. seeking) */
+void SetWrittenTime(uint32_t ms,float basedplays);
+
+/*
+ * Fading
+ */
+
+void StopFade(void); /* stops all fading (plays indefinitely) */
+uint8_t SongCompleted(void); /* song has faded out (samples have stopped
+ being generated) */
+/* parameters are play calls */
+void SetFade(int32_t fadestart,int32_t fadestop,uint8_t bNotDefault);
+void SetFadeTime(uint32_t fadestart,uint32_t fadestop,float basedplays,
+ uint8_t bNotDefault); /* parameters are in milliseconds */
+
+/*
+ * Internal Functions
+ */
+void RebuildOutputTables(void);
+void RecalculateFade(void); /* called when fade status is changed. */
+void RecalcFilter(void);
+void RecalcSilenceTracker(void);
+
+void WriteMemory_VRC6(uint16_t a,uint8_t v) ICODE_ATTR;
+void WriteMemory_MMC5(uint16_t a,uint8_t v) ICODE_ATTR;
+void WriteMemory_N106(uint16_t a,uint8_t v) ICODE_ATTR;
+void WriteMemory_FME07(uint16_t a,uint8_t v) ICODE_ATTR;
+
+/*
+ * Memory Read/Write routines
+ */
+
+uint8_t ReadMemory_RAM(uint16_t a) ICODE_ATTR;
+uint8_t ReadMemory_ExRAM(uint16_t a) ICODE_ATTR;
+uint8_t ReadMemory_SRAM(uint16_t a) ICODE_ATTR;
+uint8_t ReadMemory_pAPU(uint16_t a) ICODE_ATTR;
+uint8_t ReadMemory_ROM(uint16_t a) ICODE_ATTR;
+uint8_t ReadMemory_Default(uint16_t a) ICODE_ATTR;
+
+uint8_t ReadMemory_N106(uint16_t a) ICODE_ATTR;
+
+void WriteMemory_RAM(uint16_t a,uint8_t v) ICODE_ATTR;
+void WriteMemory_ExRAM(uint16_t a,uint8_t v) ICODE_ATTR;
+void WriteMemory_SRAM(uint16_t a,uint8_t v) ICODE_ATTR;
+void WriteMemory_pAPU(uint16_t a,uint8_t v) ICODE_ATTR;
+void WriteMemory_FDSRAM(uint16_t a,uint8_t v) ICODE_ATTR;
+void WriteMemory_Default(uint16_t a,uint8_t v) ICODE_ATTR;
+
+uint8_t ReadMemory_RAM(uint16_t a) { return pRAM[a & 0x07FF]; }
+uint8_t ReadMemory_ExRAM(uint16_t a) { return pExRAM[a & 0x0FFF]; }
+uint8_t ReadMemory_SRAM(uint16_t a) { return pSRAM[a & 0x1FFF]; }
+uint8_t ReadMemory_ROM(uint16_t a)
+ { return pROM[(a >> 12) - 6][a & 0x0FFF]; }
+uint8_t ReadMemory_Default(uint16_t a) { return (a >> 8); }
+
+void WriteMemory_RAM(uint16_t a,uint8_t v)
+ { pRAM[a & 0x07FF] = v; }
+void WriteMemory_ExRAM(uint16_t a,uint8_t v);
+void WriteMemory_SRAM(uint16_t a,uint8_t v)
+ { pSRAM[a & 0x1FFF] = v; }
+void WriteMemory_FDSRAM(uint16_t a,uint8_t v)
+ { pROM[(a >> 12) - 6][a & 0x0FFF] = v; }
+void WriteMemory_Default(uint16_t a,uint8_t v) { (void)a; (void)v; }
+
+
+/* Read Memory Procs */
+
+uint8_t ReadMemory_pAPU(uint16_t a)
+{
+ EmulateAPU(1);
+
+ if(a == 0x4015)
+ {
+ uint8_t ret = 0;
+ if(mWave_Squares.nLengthCount[0]) ret |= 0x01;
+ if(mWave_Squares.nLengthCount[1]) ret |= 0x02;
+ if(mWave_TND.nTriLengthCount) ret |= 0x04;
+ if(mWave_TND.nNoiseLengthCount) ret |= 0x08;
+ if(mWave_TND.nDMCBytesRemaining) ret |= 0x10;
+
+ if(bFrameIRQPending) ret |= 0x40;
+ if(mWave_TND.bDMCIRQPending) ret |= 0x80;
+
+ bFrameIRQPending = 0;
+ return ret;
+ }
+
+ if(!(nExternalSound & EXTSOUND_FDS)) return 0x40;
+ if(bPALMode) return 0x40;
+
+ if((a >= 0x4040) && (a <= 0x407F))
+ return mWave_FDS.nWaveTable[a & 0x3F] | 0x40;
+ if(a == 0x4090)
+ return (mWave_FDS.nVolEnv_Gain & 0x3F) | 0x40;
+ if(a == 0x4092)
+ return (mWave_FDS.nSweep_Gain & 0x3F) | 0x40;
+
+ return 0x40;
+}
+
+uint8_t ReadMemory_N106(uint16_t a)
+{
+ if(a != 0x4800)
+ return ReadMemory_pAPU(a);
+
+ uint8_t ret = mWave_N106.nRAM[(mWave_N106.nCurrentAddress << 1)] |
+ (mWave_N106.nRAM[(mWave_N106.nCurrentAddress << 1) + 1] << 4);
+ if(mWave_N106.bAutoIncrement)
+ mWave_N106.nCurrentAddress = (mWave_N106.nCurrentAddress + 1) & 0x7F;
+
+ return ret;
+}
+
+
+/* Write Memory Procs */
+
+void WriteMemory_ExRAM(uint16_t a,uint8_t v)
+{
+ if(a < 0x5FF6) /* Invalid */
+ return;
+
+ a -= 0x5FF6;
+
+ /* Swap out banks */
+
+ EmulateAPU(1);
+ /* stop it from swapping to a bank that doesn't exist */
+ if(v >= nROMBankCount)
+ v = 0;
+
+ pROM[a] = pROM_Full + (v << 12);
+
+ /* Update the DMC's DMA pointer, as well */
+ if(a >= 2)
+ mWave_TND.pDMCDMAPtr[a - 2] = pROM[a];
+}
+
+void WriteMemory_pAPU(uint16_t a,uint8_t v)
+{
+ EmulateAPU(1);
+ switch(a)
+ {
+ /* Square 1 */
+ case 0x4000:
+ mWave_Squares.nDutyCycle[0] = DUTY_CYCLE_TABLE[v >> 6];
+ mWave_Squares.bLengthEnabled[0] =
+ !(mWave_Squares.bDecayLoop[0] = (v & 0x20));
+ mWave_Squares.bDecayEnable[0] = !(v & 0x10);
+ mWave_Squares.nDecayTimer[0] = (v & 0x0F);
+
+ if(!mWave_Squares.bDecayEnable[0])
+ mWave_Squares.nVolume[0] = mWave_Squares.nDecayTimer[0];
+ break;
+
+ case 0x4001:
+ mWave_Squares.bSweepEnable[0] = (v & 0x80);
+ mWave_Squares.nSweepTimer[0] = (v & 0x70) >> 4;
+ mWave_Squares.bSweepMode[0] = v & 0x08;
+ mWave_Squares.nSweepShift[0] = v & 0x07;
+ Wave_Squares_CheckSweepForcedSilence(0);
+ break;
+
+ case 0x4002:
+ mWave_Squares.nFreqTimer[0].B.l = v;
+ Wave_Squares_CheckSweepForcedSilence(0);
+ break;
+
+ case 0x4003:
+ mWave_Squares.nFreqTimer[0].B.h = v & 0x07;
+ Wave_Squares_CheckSweepForcedSilence(0);
+
+ mWave_Squares.nDecayVolume[0] = 0x0F;
+
+ if(mWave_Squares.bChannelEnabled[0])
+ mWave_Squares.nLengthCount[0] = LENGTH_COUNTER_TABLE[v >> 3];
+
+ if(bResetDuty)
+ mWave_Squares.nDutyCount[0] = 0;
+ break;
+
+
+ /* Square 2 */
+ case 0x4004:
+ mWave_Squares.nDutyCycle[1] = DUTY_CYCLE_TABLE[v >> 6];
+ mWave_Squares.bLengthEnabled[1] =
+ !(mWave_Squares.bDecayLoop[1] = (v & 0x20));
+ mWave_Squares.bDecayEnable[1] = !(v & 0x10);
+ mWave_Squares.nDecayTimer[1] = (v & 0x0F);
+
+ if(!mWave_Squares.bDecayEnable[1])
+ mWave_Squares.nVolume[1] = mWave_Squares.nDecayTimer[1];
+ break;
+
+ case 0x4005:
+ mWave_Squares.bSweepEnable[1] = (v & 0x80);
+ mWave_Squares.nSweepTimer[1] = (v & 0x70) >> 4;
+ mWave_Squares.bSweepMode[1] = v & 0x08;
+ mWave_Squares.nSweepShift[1] = v & 0x07;
+ Wave_Squares_CheckSweepForcedSilence(1);
+ break;
+
+ case 0x4006:
+ mWave_Squares.nFreqTimer[1].B.l = v;
+ Wave_Squares_CheckSweepForcedSilence(1);
+ break;
+
+ case 0x4007:
+ mWave_Squares.nFreqTimer[1].B.h = v & 0x07;
+ Wave_Squares_CheckSweepForcedSilence(1);
+
+ mWave_Squares.nDecayVolume[1] = 0x0F;
+
+ if(mWave_Squares.bChannelEnabled[1])
+ mWave_Squares.nLengthCount[1] = LENGTH_COUNTER_TABLE[v >> 3];
+
+ if(bResetDuty)
+ mWave_Squares.nDutyCount[1] = 0;
+ break;
+
+
+ /* Triangle */
+ case 0x4008:
+ mWave_TND.nTriLinearLoad = v & 0x7F;
+ mWave_TND.bTriLinearControl = v & 0x80;
+ mWave_TND.bTriLengthEnabled = !(v & 0x80);
+ break;
+
+ case 0x400A:
+ mWave_TND.nTriFreqTimer.B.l = v;
+ break;
+
+ case 0x400B:
+ mWave_TND.nTriFreqTimer.B.h = v & 0x07;
+ mWave_TND.bTriLinearHalt = 1;
+
+ if(mWave_TND.bTriChannelEnabled)
+ mWave_TND.nTriLengthCount = LENGTH_COUNTER_TABLE[v >> 3];
+ break;
+
+ /* Noise */
+ case 0x400C:
+ mWave_TND.bNoiseLengthEnabled =
+ !(mWave_TND.bNoiseDecayLoop = (v & 0x20));
+ mWave_TND.bNoiseDecayEnable = !(v & 0x10);
+ mWave_TND.nNoiseDecayTimer = (v & 0x0F);
+
+ if(mWave_TND.bNoiseDecayEnable)
+ mWave_TND.nNoiseVolume = mWave_TND.nNoiseDecayVolume;
+ else
+ mWave_TND.nNoiseVolume = mWave_TND.nNoiseDecayTimer;
+ break;
+
+ case 0x400E:
+ mWave_TND.nNoiseFreqTimer = NOISE_FREQ_TABLE[v & 0x0F];
+ mWave_TND.bNoiseRandomMode = (v & 0x80) ? 6 : 1;
+ break;
+
+ case 0x400F:
+ if(mWave_TND.bNoiseChannelEnabled)
+ mWave_TND.nNoiseLengthCount = LENGTH_COUNTER_TABLE[v >> 3];
+
+ mWave_TND.nNoiseDecayVolume = 0x0F;
+ if(mWave_TND.bNoiseDecayEnable)
+ mWave_TND.nNoiseVolume = 0x0F;
+ break;
+
+ /* DMC */
+ case 0x4010:
+ mWave_TND.bDMCLoop = v & 0x40;
+ mWave_TND.bDMCIRQEnabled = v & 0x80;
+ /* IRQ can't be pending if disabled */
+ if(!mWave_TND.bDMCIRQEnabled)
+ mWave_TND.bDMCIRQPending = 0;
+
+ mWave_TND.nDMCFreqTimer = DMC_FREQ_TABLE[bPALMode][v & 0x0F];
+ break;
+
+ case 0x4011:
+ if(bIgnore4011Writes)
+ break;
+ v &= 0x7F;
+ if(bDMCPopReducer)
+ {
+ if(bDMCPop_SamePlay)
+ mWave_TND.nDMCOutput = v;
+ else
+ {
+ if(bDMCPop_Skip)
+ {
+ bDMCPop_Skip = 0;
+ break;
+ }
+ if(nDMCPop_Prev == v) break;
+ if(mWave_TND.nDMCOutput == v) break;
+ mWave_TND.nDMCOutput = nDMCPop_Prev;
+ nDMCPop_Prev = v;
+ bDMCPop_SamePlay = 1;
+ }
+ }
+ else
+ mWave_TND.nDMCOutput = v;
+ break;
+
+ case 0x4012:
+ mWave_TND.nDMCDMABank_Load = (v >> 6) | 0x04;
+ mWave_TND.nDMCDMAAddr_Load = (v << 6) & 0x0FFF;
+ break;
+
+ case 0x4013:
+ mWave_TND.nDMCLength = (v << 4) + 1;
+ break;
+
+ /* All / General Purpose */
+ case 0x4015:
+ mWave_TND.bDMCIRQPending = 0;
+
+ if(v & 0x01){ mWave_Squares.bChannelEnabled[0] = 1; }
+ else { mWave_Squares.bChannelEnabled[0] =
+ mWave_Squares.nLengthCount[0] = 0; }
+ if(v & 0x02){ mWave_Squares.bChannelEnabled[1] = 1; }
+ else { mWave_Squares.bChannelEnabled[1] =
+ mWave_Squares.nLengthCount[1] = 0; }
+ if(v & 0x04){ mWave_TND.bTriChannelEnabled = 1; }
+ else { mWave_TND.bTriChannelEnabled =
+ mWave_TND.nTriLengthCount = 0; }
+ if(v & 0x08){ mWave_TND.bNoiseChannelEnabled = 1; }
+ else { mWave_TND.bNoiseChannelEnabled =
+ mWave_TND.nNoiseLengthCount = 0; }
+
+ if(v & 0x10)
+ {
+ if(!mWave_TND.nDMCBytesRemaining)
+ {
+ bDMCPop_Skip = 1;
+ mWave_TND.nDMCDMAAddr = mWave_TND.nDMCDMAAddr_Load;
+ mWave_TND.nDMCDMABank = mWave_TND.nDMCDMABank_Load;
+ mWave_TND.nDMCBytesRemaining = mWave_TND.nDMCLength;
+ mWave_TND.bDMCActive = 1;
+ }
+ }
+ else
+ mWave_TND.nDMCBytesRemaining = 0;
+ break;
+
+ case 0x4017:
+ bFrameIRQEnabled = !(v & 0x40);
+ bFrameIRQPending = 0;
+ nFrameCounter = 0;
+ nFrameCounterMax = (v & 0x80) ? 4 : 3;
+ nTicksUntilNextFrame =
+ (bPALMode ? PAL_FRAME_COUNTER_FREQ : NTSC_FRAME_COUNTER_FREQ)
+ * 0x10000;
+
+ CLOCK_MAJOR();
+ if(v & 0x80) CLOCK_MINOR();
+ break;
+ }
+
+ if(!(nExternalSound & EXTSOUND_FDS)) return;
+ if(bPALMode) return;
+
+ /* FDS Sound registers */
+
+ if(a < 0x4040) return;
+
+ /* wave table */
+ if(a <= 0x407F)
+ {
+ if(mWave_FDS.bWaveWrite)
+ mWave_FDS.nWaveTable[a - 0x4040] = v;
+ }
+ else
+ {
+ switch(a)
+ {
+ case 0x4080:
+ mWave_FDS.nVolEnv_Mode = (v >> 6);
+ if(v & 0x80)
+ {
+ mWave_FDS.nVolEnv_Gain = v & 0x3F;
+ if(!mWave_FDS.nMainAddr)
+ {
+ if(mWave_FDS.nVolEnv_Gain < 0x20)
+ mWave_FDS.nVolume = mWave_FDS.nVolEnv_Gain;
+ else mWave_FDS.nVolume = 0x20;
+ }
+ }
+ mWave_FDS.nVolEnv_Decay = v & 0x3F;
+ mWave_FDS.nVolEnv_Timer =
+ ((mWave_FDS.nVolEnv_Decay + 1) * mWave_FDS.nEnvelopeSpeed * 8);
+
+ mWave_FDS.bVolEnv_On = mWave_FDS.bEnvelopeEnable &&
+ mWave_FDS.nEnvelopeSpeed && !(v & 0x80);
+ break;
+
+ case 0x4082:
+ mWave_FDS.nFreq.B.l = v;
+ mWave_FDS.bMain_On = mWave_FDS.nFreq.W && mWave_FDS.bEnabled &&
+ !mWave_FDS.bWaveWrite;
+ break;
+
+ case 0x4083:
+ mWave_FDS.bEnabled = !(v & 0x80);
+ mWave_FDS.bEnvelopeEnable = !(v & 0x40);
+ if(v & 0x80)
+ {
+ if(mWave_FDS.nVolEnv_Gain < 0x20)
+ mWave_FDS.nVolume = mWave_FDS.nVolEnv_Gain;
+ else mWave_FDS.nVolume = 0x20;
+ }
+ mWave_FDS.nFreq.B.h = v & 0x0F;
+ mWave_FDS.bMain_On = mWave_FDS.nFreq.W && mWave_FDS.bEnabled &&
+ !mWave_FDS.bWaveWrite;
+
+ mWave_FDS.bVolEnv_On = mWave_FDS.bEnvelopeEnable &&
+ mWave_FDS.nEnvelopeSpeed && !(mWave_FDS.nVolEnv_Mode & 2);
+ mWave_FDS.bSweepEnv_On = mWave_FDS.bEnvelopeEnable &&
+ mWave_FDS.nEnvelopeSpeed && !(mWave_FDS.nSweep_Mode & 2);
+ break;
+
+
+ case 0x4084:
+ mWave_FDS.nSweep_Mode = v >> 6;
+ if(v & 0x80)
+ mWave_FDS.nSweep_Gain = v & 0x3F;
+ mWave_FDS.nSweep_Decay = v & 0x3F;
+ mWave_FDS.nSweep_Timer =
+ ((mWave_FDS.nSweep_Decay + 1) * mWave_FDS.nEnvelopeSpeed * 8);
+ mWave_FDS.bSweepEnv_On =
+ mWave_FDS.bEnvelopeEnable && mWave_FDS.nEnvelopeSpeed &&
+ !(v & 0x80);
+ break;
+
+
+ case 0x4085:
+ if(v & 0x40) mWave_FDS.nSweepBias = (v & 0x3F) - 0x40;
+ else mWave_FDS.nSweepBias = v & 0x3F;
+ mWave_FDS.nLFO_Addr = 0;
+ break;
+
+
+ case 0x4086:
+ mWave_FDS.nLFO_Freq.B.l = v;
+ mWave_FDS.bLFO_On =
+ mWave_FDS.bLFO_Enabled && mWave_FDS.nLFO_Freq.W;
+ if(mWave_FDS.nLFO_Freq.W)
+ mWave_FDS.nLFO_Timer = (0x10000<<14) / mWave_FDS.nLFO_Freq.W;
+ break;
+
+ case 0x4087:
+ mWave_FDS.bLFO_Enabled = !(v & 0x80);
+ mWave_FDS.nLFO_Freq.B.h = v & 0x0F;
+ mWave_FDS.bLFO_On =
+ mWave_FDS.bLFO_Enabled && mWave_FDS.nLFO_Freq.W;
+ if(mWave_FDS.nLFO_Freq.W)
+ mWave_FDS.nLFO_Timer = (0x10000<<14) / mWave_FDS.nLFO_Freq.W;
+ break;
+
+ case 0x4088:
+ if(mWave_FDS.bLFO_Enabled) break;
+ register int32_t i;
+ for(i = 0; i < 62; i++)
+ mWave_FDS.nLFO_Table[i] = mWave_FDS.nLFO_Table[i + 2];
+ mWave_FDS.nLFO_Table[62] = mWave_FDS.nLFO_Table[63] = v & 7;
+ break;
+
+ case 0x4089:
+ mWave_FDS.nMainVolume = v & 3;
+ mWave_FDS.bWaveWrite = v & 0x80;
+ mWave_FDS.bMain_On = mWave_FDS.nFreq.W && mWave_FDS.bEnabled &&
+ !mWave_FDS.bWaveWrite;
+ break;
+
+ case 0x408A:
+ mWave_FDS.nEnvelopeSpeed = v;
+ mWave_FDS.bVolEnv_On =
+ mWave_FDS.bEnvelopeEnable &&
+ mWave_FDS.nEnvelopeSpeed && !(mWave_FDS.nVolEnv_Mode & 2);
+ mWave_FDS.bSweepEnv_On =
+ mWave_FDS.bEnvelopeEnable &&
+ mWave_FDS.nEnvelopeSpeed && !(mWave_FDS.nSweep_Mode & 2);
+ break;
+ }
+ }
+}
+
+void WriteMemory_VRC6(uint16_t a,uint8_t v)
+{
+ EmulateAPU(1);
+
+ if((a < 0xA000) && (nExternalSound & EXTSOUND_VRC7)) return;
+ else if(nExternalSound & EXTSOUND_FDS)
+ WriteMemory_FDSRAM(a,v);
+
+ switch(a)
+ {
+ /* Pulse 1 */
+ case 0x9000:
+ mWave_VRC6Pulse[0].nVolume = v & 0x0F;
+ mWave_VRC6Pulse[0].nDutyCycle = (v >> 4) & 0x07;
+ mWave_VRC6Pulse[0].bDigitized = v & 0x80;
+ if(mWave_VRC6Pulse[0].bDigitized)
+ mWave_VRC6Pulse[0].nDutyCount = 0;
+ break;
+
+ case 0x9001:
+ mWave_VRC6Pulse[0].nFreqTimer.B.l = v;
+ break;
+
+ case 0x9002:
+ mWave_VRC6Pulse[0].nFreqTimer.B.h = v & 0x0F;
+ mWave_VRC6Pulse[0].bChannelEnabled = v & 0x80;
+ break;
+
+
+ /* Pulse 2 */
+ case 0xA000:
+ mWave_VRC6Pulse[1].nVolume = v & 0x0F;
+ mWave_VRC6Pulse[1].nDutyCycle = (v >> 4) & 0x07;
+ mWave_VRC6Pulse[1].bDigitized = v & 0x80;
+ if(mWave_VRC6Pulse[1].bDigitized)
+ mWave_VRC6Pulse[1].nDutyCount = 0;
+ break;
+
+ case 0xA001:
+ mWave_VRC6Pulse[1].nFreqTimer.B.l = v;
+ break;
+
+ case 0xA002:
+ mWave_VRC6Pulse[1].nFreqTimer.B.h = v & 0x0F;
+ mWave_VRC6Pulse[1].bChannelEnabled = v & 0x80;
+ break;
+
+ /* Sawtooth */
+ case 0xB000:
+ mWave_VRC6Saw.nAccumRate = (v & 0x3F);
+ break;
+
+ case 0xB001:
+ mWave_VRC6Saw.nFreqTimer.B.l = v;
+ break;
+
+ case 0xB002:
+ mWave_VRC6Saw.nFreqTimer.B.h = v & 0x0F;
+ mWave_VRC6Saw.bChannelEnabled = v & 0x80;
+ break;
+ }
+}
+
+void WriteMemory_MMC5(uint16_t a,uint8_t v)
+{
+ if((a <= 0x5015) && !bPALMode)
+ {
+ /* no audio emulation */
+ return;
+ }
+
+ if(a == 0x5205)
+ {
+ nMultIn_Low = v;
+ goto multiply;
+ }
+ if(a == 0x5206)
+ {
+ nMultIn_High = v;
+multiply:
+ a = nMultIn_Low * nMultIn_High;
+ pExRAM[0x205] = a & 0xFF;
+ pExRAM[0x206] = a >> 8;
+ return;
+ }
+
+ if(a < 0x5C00) return;
+
+ pExRAM[a & 0x0FFF] = v;
+ if(a >= 0x5FF6)
+ WriteMemory_ExRAM(a,v);
+}
+
+void WriteMemory_N106(uint16_t a,uint8_t v)
+{
+ if(a < 0x4800)
+ {
+ WriteMemory_pAPU(a,v);
+ return;
+ }
+
+ if(a == 0xF800)
+ {
+ mWave_N106.nCurrentAddress = v & 0x7F;
+ mWave_N106.bAutoIncrement = (v & 0x80);
+ return;
+ }
+
+ if(a == 0x4800)
+ {
+ EmulateAPU(1);
+ mWave_N106.nRAM[mWave_N106.nCurrentAddress << 1] = v & 0x0F;
+ mWave_N106.nRAM[(mWave_N106.nCurrentAddress << 1) + 1] = v >> 4;
+ a = mWave_N106.nCurrentAddress;
+ if(mWave_N106.bAutoIncrement)
+ mWave_N106.nCurrentAddress =
+ (mWave_N106.nCurrentAddress + 1) & 0x7F;
+
+#define N106REGWRITE(ch,r0,r1,r2,r3,r4) \
+ case r0: if(mWave_N106.nFreqReg[ch].B.l == v) break; \
+ mWave_N106.nFreqReg[ch].B.l = v; \
+ mWave_N106.fFreqTimer[ch] = -1.0f; \
+ break; \
+ case r1: if(mWave_N106.nFreqReg[ch].B.h == v) break; \
+ mWave_N106.nFreqReg[ch].B.h = v; \
+ mWave_N106.fFreqTimer[ch] = -1.0f; \
+ break; \
+ case r2: if(mWave_N106.nFreqReg[ch].B.w != (v & 3)){ \
+ mWave_N106.nFreqReg[ch].B.w = v & 0x03; \
+ mWave_N106.fFreqTimer[ch] = -1.0f;} \
+ mWave_N106.nWaveSize[ch] = 0x20 - (v & 0x1C); \
+ break; \
+ case r3: mWave_N106.nWavePosStart[ch] = v; \
+ break; \
+ case r4: mWave_N106.nPreVolume[ch] = v & 0x0F; \
+ if(!bN106PopReducer) \
+ mWave_N106.nVolume[ch] = v & 0x0F
+
+ switch(a)
+ {
+ N106REGWRITE(0,0x40,0x42,0x44,0x46,0x47); break;
+ N106REGWRITE(1,0x48,0x4A,0x4C,0x4E,0x4F); break;
+ N106REGWRITE(2,0x50,0x52,0x54,0x56,0x57); break;
+ N106REGWRITE(3,0x58,0x5A,0x5C,0x5E,0x5F); break;
+ N106REGWRITE(4,0x60,0x62,0x64,0x66,0x67); break;
+ N106REGWRITE(5,0x68,0x6A,0x6C,0x6E,0x6F); break;
+ N106REGWRITE(6,0x70,0x72,0x74,0x76,0x77); break;
+ N106REGWRITE(7,0x78,0x7A,0x7C,0x7E,0x7F);
+ v = (v >> 4) & 7;
+ if(mWave_N106.nActiveChannels == v) break;
+ mWave_N106.nActiveChannels = v;
+ mWave_N106.fFreqTimer[0] = -1.0f;
+ mWave_N106.fFreqTimer[1] = -1.0f;
+ mWave_N106.fFreqTimer[2] = -1.0f;
+ mWave_N106.fFreqTimer[3] = -1.0f;
+ mWave_N106.fFreqTimer[4] = -1.0f;
+ mWave_N106.fFreqTimer[5] = -1.0f;
+ mWave_N106.fFreqTimer[6] = -1.0f;
+ mWave_N106.fFreqTimer[7] = -1.0f;
+ break;
+ }
+#undef N106REGWRITE
+ }
+}
+
+void WriteMemory_FME07(uint16_t a,uint8_t v)
+{
+ if((a < 0xD000) && (nExternalSound & EXTSOUND_FDS))
+ WriteMemory_FDSRAM(a,v);
+
+ if(a == 0xC000)
+ nFME07_Address = v;
+ if(a == 0xE000)
+ {
+ switch(nFME07_Address)
+ {
+ case 0x00: mWave_FME07[0].nFreqTimer.B.l = v; break;
+ case 0x01: mWave_FME07[0].nFreqTimer.B.h = v & 0x0F; break;
+ case 0x02: mWave_FME07[1].nFreqTimer.B.l = v; break;
+ case 0x03: mWave_FME07[1].nFreqTimer.B.h = v & 0x0F; break;
+ case 0x04: mWave_FME07[2].nFreqTimer.B.l = v; break;
+ case 0x05: mWave_FME07[2].nFreqTimer.B.h = v & 0x0F; break;
+ case 0x07:
+ mWave_FME07[0].bChannelEnabled = !(v & 0x01);
+ mWave_FME07[1].bChannelEnabled = !(v & 0x02);
+ mWave_FME07[2].bChannelEnabled = !(v & 0x03);
+ break;
+ case 0x08: mWave_FME07[0].nVolume = v & 0x0F; break;
+ case 0x09: mWave_FME07[1].nVolume = v & 0x0F; break;
+ case 0x0A: mWave_FME07[2].nVolume = v & 0x0F; break;
+ }
+ }
+}
+
+/*
+ * Emulate APU
+ */
+
+int32_t fulltick;
+void EmulateAPU(uint8_t bBurnCPUCycles)
+{
+ int32_t tick;
+ int64_t diff;
+
+ int32_t tnd_out;
+ uint8_t square_out;
+
+ ENTER_TIMER(apu);
+
+ fulltick += (signed)(nCPUCycle - nAPUCycle);
+
+ int32_t burned;
+ int32_t mixL;
+
+ if(bFade && nSilentSampleMax && (nSilentSamples >= nSilentSampleMax))
+ fulltick = 0;
+
+ while(fulltick>0)
+ {
+ tick = (nTicksUntilNextSample+0xffff)>>16;
+
+ fulltick -= tick;
+
+ /*
+ * Sample Generation
+ */
+
+ ENTER_TIMER(squares);
+ /* Square generation */
+
+ mWave_Squares.nFreqCount[0] -= tick;
+ mWave_Squares.nFreqCount[1] -= tick;
+
+ if((mWave_Squares.nDutyCount[0] < mWave_Squares.nDutyCycle[0]) &&
+ mWave_Squares.nLengthCount[0] &&
+ !mWave_Squares.bSweepForceSilence[0])
+ square_out = (mWave_Squares.nVolume[0] << 4);
+ else
+ square_out = 0;
+
+ if((mWave_Squares.nDutyCount[1] < mWave_Squares.nDutyCycle[1]) &&
+ mWave_Squares.nLengthCount[1] &&
+ !mWave_Squares.bSweepForceSilence[1])
+ square_out |= mWave_Squares.nVolume[1];
+
+ mWave_Squares.nMixL = Squares_nOutputTable_L[square_out];
+
+ if(mWave_Squares.nFreqCount[0]<=0)
+ {
+ int cycles =
+ (-mWave_Squares.nFreqCount[0])/
+ (mWave_Squares.nFreqTimer[0].W + 1) + 1;
+ mWave_Squares.nFreqCount[0] =
+ (mWave_Squares.nFreqTimer[0].W + 1)-
+ (-mWave_Squares.nFreqCount[0])%
+ (mWave_Squares.nFreqTimer[0].W + 1);
+ mWave_Squares.nDutyCount[0] =
+ (mWave_Squares.nDutyCount[0]+cycles)%0x10;
+ }
+ if(mWave_Squares.nFreqCount[1]<=0)
+ {
+ int cycles =
+ (-mWave_Squares.nFreqCount[1])/
+ (mWave_Squares.nFreqTimer[1].W + 1) + 1;
+ mWave_Squares.nFreqCount[1] =
+ (mWave_Squares.nFreqTimer[1].W + 1)-
+ (-mWave_Squares.nFreqCount[1])%
+ (mWave_Squares.nFreqTimer[1].W + 1);
+ mWave_Squares.nDutyCount[1] = (mWave_Squares.nDutyCount[1]+cycles)%
+ 0x10;
+ }
+ /* end of Square generation */
+ EXIT_TIMER(squares);
+ ENTER_TIMER(tnd);
+
+ ENTER_TIMER(tnd_enter);
+
+ burned=0;
+
+ /* TND generation */
+
+ if(mWave_TND.nNoiseFreqTimer) mWave_TND.nNoiseFreqCount -= tick;
+
+ if(mWave_TND.nTriFreqTimer.W > 8)
+ mWave_TND.nTriFreqCount -= tick;
+
+ tnd_out = mWave_TND.nTriOutput << 11;
+
+ if(mWave_TND.bNoiseRandomOut && mWave_TND.nNoiseLengthCount)
+ tnd_out |= mWave_TND.nNoiseVolume << 7;
+
+ tnd_out |= mWave_TND.nDMCOutput;
+
+ mWave_TND.nMixL = main_nOutputTable_L[tnd_out];
+
+ EXIT_TIMER(tnd_enter);
+
+ ENTER_TIMER(tnd_tri);
+
+ /* Tri */
+
+ if(mWave_TND.nTriFreqCount<=0)
+ {
+ if(mWave_TND.nTriLengthCount && mWave_TND.nTriLinearCount)
+ {
+ do mWave_TND.nTriStep++;
+ while ((mWave_TND.nTriFreqCount +=
+ mWave_TND.nTriFreqTimer.W + 1) <= 0);
+ mWave_TND.nTriStep &= 0x1F;
+
+ if(mWave_TND.nTriStep & 0x10)
+ mWave_TND.nTriOutput = mWave_TND.nTriStep ^ 0x1F;
+ else mWave_TND.nTriOutput = mWave_TND.nTriStep;
+ } else mWave_TND.nTriFreqCount=mWave_TND.nTriFreqTimer.W+1;
+ }
+
+ EXIT_TIMER(tnd_tri);
+
+ ENTER_TIMER(tnd_noise);
+
+ /* Noise */
+
+ if(mWave_TND.nNoiseFreqTimer &&
+ mWave_TND.nNoiseVolume && mWave_TND.nNoiseFreqCount<=0)
+ {
+ mWave_TND.nNoiseFreqCount = mWave_TND.nNoiseFreqTimer;
+ mWave_TND.nNoiseRandomShift <<= 1;
+ mWave_TND.bNoiseRandomOut = (((mWave_TND.nNoiseRandomShift <<
+ mWave_TND.bNoiseRandomMode) ^
+ mWave_TND.nNoiseRandomShift) & 0x8000 ) ? 1 : 0;
+ if(mWave_TND.bNoiseRandomOut)
+ mWave_TND.nNoiseRandomShift |= 0x01;
+ }
+
+ EXIT_TIMER(tnd_noise);
+
+ ENTER_TIMER(tnd_dmc);
+
+ /* DMC */
+ if(mWave_TND.bDMCActive)
+ {
+ mWave_TND.nDMCFreqCount -= tick;
+ while (mWave_TND.nDMCFreqCount <= 0) {
+ if (!mWave_TND.bDMCActive) {
+ mWave_TND.nDMCFreqCount = mWave_TND.nDMCFreqTimer;
+ break;
+ }
+
+ mWave_TND.nDMCFreqCount += mWave_TND.nDMCFreqTimer;
+
+ if(mWave_TND.bDMCSampleBufferEmpty &&
+ mWave_TND.nDMCBytesRemaining)
+ {
+ burned += 4; /* 4 cycle burn! */
+ mWave_TND.nDMCSampleBuffer =
+ mWave_TND.pDMCDMAPtr[mWave_TND.nDMCDMABank]
+ [mWave_TND.nDMCDMAAddr];
+ mWave_TND.nDMCDMAAddr++;
+ if(mWave_TND.nDMCDMAAddr & 0x1000)
+ {
+ mWave_TND.nDMCDMAAddr &= 0x0FFF;
+ mWave_TND.nDMCDMABank =
+ (mWave_TND.nDMCDMABank + 1) & 0x07;
+ }
+
+ mWave_TND.bDMCSampleBufferEmpty = 0;
+ mWave_TND.nDMCBytesRemaining--;
+ if(!mWave_TND.nDMCBytesRemaining)
+ {
+ if(mWave_TND.bDMCLoop)
+ {
+ mWave_TND.nDMCDMABank = mWave_TND.nDMCDMABank_Load;
+ mWave_TND.nDMCDMAAddr = mWave_TND.nDMCDMAAddr_Load;
+ mWave_TND.nDMCBytesRemaining =mWave_TND.nDMCLength;
+ }
+ else if(mWave_TND.bDMCIRQEnabled)
+ mWave_TND.bDMCIRQPending = 1;
+ }
+ }
+
+ if(!mWave_TND.nDMCDeltaBit)
+ {
+ mWave_TND.nDMCDeltaBit = 8;
+ mWave_TND.bDMCDeltaSilent =mWave_TND.bDMCSampleBufferEmpty;
+ mWave_TND.nDMCDelta = mWave_TND.nDMCSampleBuffer;
+ mWave_TND.bDMCSampleBufferEmpty = 1;
+ }
+
+ if(mWave_TND.nDMCDeltaBit) {
+ mWave_TND.nDMCDeltaBit--;
+ if(!mWave_TND.bDMCDeltaSilent)
+ {
+ if(mWave_TND.nDMCDelta & 0x01)
+ {
+ if(mWave_TND.nDMCOutput < 0x7E)
+ mWave_TND.nDMCOutput += 2;
+ }
+ else if(mWave_TND.nDMCOutput > 1)
+ mWave_TND.nDMCOutput -= 2;
+ }
+ mWave_TND.nDMCDelta >>= 1;
+ }
+
+ if(!mWave_TND.nDMCBytesRemaining &&
+ mWave_TND.bDMCSampleBufferEmpty &&
+ mWave_TND.bDMCDeltaSilent)
+ mWave_TND.bDMCActive = mWave_TND.nDMCDeltaBit = 0;
+ }
+ }
+
+ EXIT_TIMER(tnd_dmc);
+
+ /* end of TND generation */
+ EXIT_TIMER(tnd);
+
+ if(nExternalSound && !bPALMode)
+ {
+ if(nExternalSound & EXTSOUND_VRC6)
+ {
+
+ if(mWave_VRC6Pulse[0].bChannelEnabled) {
+
+ mWave_VRC6Pulse[0].nFreqCount -= tick;
+
+ if(mWave_VRC6Pulse[0].nDutyCount <=
+ mWave_VRC6Pulse[0].nDutyCycle)
+ {
+ mWave_VRC6Pulse[0].nMixL =
+ VRC6Pulse_nOutputTable_L[0]
+ [mWave_VRC6Pulse[0].nVolume];
+ } else mWave_VRC6Pulse[0].nMixL = 0;
+
+ while(mWave_VRC6Pulse[0].nFreqCount <= 0) {
+ mWave_VRC6Pulse[0].nFreqCount +=
+ mWave_VRC6Pulse[0].nFreqTimer.W + 1;
+
+ if(!mWave_VRC6Pulse[0].bDigitized)
+ mWave_VRC6Pulse[0].nDutyCount =
+ (mWave_VRC6Pulse[0].nDutyCount + 1) & 0x0F;
+ }
+ }
+
+ if(mWave_VRC6Pulse[1].bChannelEnabled) {
+
+ mWave_VRC6Pulse[1].nFreqCount -= tick;
+
+ if(mWave_VRC6Pulse[1].nDutyCount <=
+ mWave_VRC6Pulse[1].nDutyCycle)
+ {
+ mWave_VRC6Pulse[1].nMixL =
+ VRC6Pulse_nOutputTable_L[1]
+ [mWave_VRC6Pulse[1].nVolume];
+ } else mWave_VRC6Pulse[1].nMixL = 0;
+
+ while(mWave_VRC6Pulse[1].nFreqCount <= 0) {
+ mWave_VRC6Pulse[1].nFreqCount +=
+ mWave_VRC6Pulse[1].nFreqTimer.W + 1;
+
+ if(!mWave_VRC6Pulse[1].bDigitized)
+ mWave_VRC6Pulse[1].nDutyCount =
+ (mWave_VRC6Pulse[1].nDutyCount + 1) & 0x0F;
+ }
+ }
+
+ mWave_VRC6Saw.nFreqCount -= tick;
+
+ mWave_VRC6Saw.nMixL =
+ VRC6Saw_nOutputTable_L[mWave_VRC6Saw.nAccum >> 3];
+
+ while(mWave_VRC6Saw.nFreqCount <= 0) {
+
+ mWave_VRC6Saw.nFreqCount += mWave_VRC6Saw.nFreqTimer.W + 1;
+
+ mWave_VRC6Saw.nAccumStep++;
+ if(mWave_VRC6Saw.nAccumStep == 14)
+ {
+ mWave_VRC6Saw.nAccumStep = 0;
+ mWave_VRC6Saw.nAccum = 0;
+ }
+ else if(!(mWave_VRC6Saw.nAccumStep & 1))
+ mWave_VRC6Saw.nAccum += mWave_VRC6Saw.nAccumRate;
+ }
+
+ } /* end VRC6 */
+ if(nExternalSound & EXTSOUND_N106)
+ Wave_N106_DoTicks(tick);
+ if(nExternalSound & EXTSOUND_FME07)
+ {
+ if (mWave_FME07[0].bChannelEnabled &&
+ mWave_FME07[0].nFreqTimer.W) {
+ mWave_FME07[0].nFreqCount -= tick;
+
+ if(mWave_FME07[0].nDutyCount < 16)
+ {
+ mWave_FME07[0].nMixL =
+ FME07_nOutputTable_L[0][mWave_FME07[0].nVolume];
+ } else mWave_FME07[0].nMixL = 0;
+ while(mWave_FME07[0].nFreqCount <= 0) {
+ mWave_FME07[0].nFreqCount +=
+ mWave_FME07[0].nFreqTimer.W;
+
+ mWave_FME07[0].nDutyCount=
+ (mWave_FME07[0].nDutyCount+1)&0x1f;
+ }
+ }
+
+ if (mWave_FME07[1].bChannelEnabled &&
+ mWave_FME07[1].nFreqTimer.W) {
+ mWave_FME07[1].nFreqCount -= tick;
+
+ if(mWave_FME07[1].nDutyCount < 16)
+ {
+ mWave_FME07[1].nMixL =
+ FME07_nOutputTable_L[1][mWave_FME07[1].nVolume];
+ } else mWave_FME07[1].nMixL = 0;
+ while(mWave_FME07[1].nFreqCount <= 0) {
+ mWave_FME07[1].nFreqCount +=
+ mWave_FME07[1].nFreqTimer.W;
+
+ mWave_FME07[1].nDutyCount=
+ (mWave_FME07[1].nDutyCount+1)&0x1f;
+ }
+ }
+
+ if (mWave_FME07[2].bChannelEnabled &&
+ mWave_FME07[2].nFreqTimer.W) {
+ mWave_FME07[2].nFreqCount -= tick;
+
+ if(mWave_FME07[2].nDutyCount < 16)
+ {
+ mWave_FME07[2].nMixL =
+ FME07_nOutputTable_L[2][mWave_FME07[2].nVolume];
+ } else mWave_FME07[2].nMixL = 0;
+ while(mWave_FME07[2].nFreqCount <= 0) {
+ mWave_FME07[2].nFreqCount +=
+ mWave_FME07[2].nFreqTimer.W;
+
+ mWave_FME07[2].nDutyCount=
+ (mWave_FME07[2].nDutyCount+1)&0x1f;
+ }
+ }
+
+ } /* end FME07 */
+ ENTER_TIMER(fds);
+ if(nExternalSound & EXTSOUND_FDS) {
+
+ /* Volume Envelope Unit */
+ if(mWave_FDS.bVolEnv_On)
+ {
+ mWave_FDS.nVolEnv_Count -= tick;
+ while(mWave_FDS.nVolEnv_Count <= 0)
+ {
+ mWave_FDS.nVolEnv_Count += mWave_FDS.nVolEnv_Timer;
+ if(mWave_FDS.nVolEnv_Mode) {
+ if(mWave_FDS.nVolEnv_Gain < 0x20)
+ mWave_FDS.nVolEnv_Gain++;
+ }
+ else {
+ if(mWave_FDS.nVolEnv_Gain)
+ mWave_FDS.nVolEnv_Gain--;
+ }
+ }
+ }
+
+ /* Sweep Envelope Unit */
+ if(mWave_FDS.bSweepEnv_On)
+ {
+ mWave_FDS.nSweep_Count -= tick;
+ while(mWave_FDS.nSweep_Count <= 0)
+ {
+ mWave_FDS.nSweep_Count += mWave_FDS.nSweep_Timer;
+ if(mWave_FDS.nSweep_Mode) {
+ if(mWave_FDS.nSweep_Gain < 0x20)
+ mWave_FDS.nSweep_Gain++;
+ } else {
+ if(mWave_FDS.nSweep_Gain) mWave_FDS.nSweep_Gain--;
+ }
+ }
+ }
+
+ /* Effector / LFO */
+ int32_t subfreq = 0;
+ if(mWave_FDS.bLFO_On)
+ {
+ mWave_FDS.nLFO_Count -= tick<<14;
+ while(mWave_FDS.nLFO_Count <= 0)
+ {
+ mWave_FDS.nLFO_Count += mWave_FDS.nLFO_Timer;
+ if(mWave_FDS.nLFO_Table[mWave_FDS.nLFO_Addr] == 4)
+ mWave_FDS.nSweepBias = 0;
+ else
+ mWave_FDS.nSweepBias +=
+ ModulationTable[
+ mWave_FDS.nLFO_Table[mWave_FDS.nLFO_Addr]
+ ];
+ mWave_FDS.nLFO_Addr = (mWave_FDS.nLFO_Addr + 1) & 0x3F;
+ }
+
+ while(mWave_FDS.nSweepBias > 63)
+ mWave_FDS.nSweepBias -= 128;
+ while(mWave_FDS.nSweepBias < -64)
+ mWave_FDS.nSweepBias += 128;
+
+ register int32_t temp =
+ mWave_FDS.nSweepBias * mWave_FDS.nSweep_Gain;
+ if(temp & 0x0F)
+ {
+ temp /= 16;
+ if(mWave_FDS.nSweepBias < 0) temp--;
+ else temp += 2;
+ }
+ else
+ temp /= 16;
+
+ if(temp > 193) temp -= 258;
+ if(temp < -64) temp += 256;
+
+ subfreq = mWave_FDS.nFreq.W * temp / 64;
+ }
+
+ /* Main Unit */
+ if(mWave_FDS.bMain_On)
+ {
+ mWave_FDS.nMixL =
+ FDS_nOutputTable_L[mWave_FDS.nMainVolume]
+ [mWave_FDS.nVolume]
+ [mWave_FDS.nWaveTable[mWave_FDS.nMainAddr] ];
+
+ if((subfreq + mWave_FDS.nFreq.W) > 0)
+ {
+ int32_t freq = (0x10000<<14) / (subfreq + mWave_FDS.nFreq.W);
+
+ mWave_FDS.nFreqCount -= tick<<14;
+ while(mWave_FDS.nFreqCount <= 0)
+ {
+ mWave_FDS.nFreqCount += freq;
+
+ mWave_FDS.nMainAddr =
+ (mWave_FDS.nMainAddr + 1) & 0x3F;
+ mWave_FDS.nPopOutput =
+ mWave_FDS.nWaveTable[mWave_FDS.nMainAddr];
+ if(!mWave_FDS.nMainAddr)
+ {
+ if(mWave_FDS.nVolEnv_Gain < 0x20)
+ mWave_FDS.nVolume = mWave_FDS.nVolEnv_Gain;
+ else mWave_FDS.nVolume = 0x20;
+ }
+ }
+ }
+ else
+ mWave_FDS.nFreqCount = mWave_FDS.nLFO_Count;
+ }
+ else if(mWave_FDS.bPopReducer && mWave_FDS.nPopOutput)
+ {
+ mWave_FDS.nMixL = FDS_nOutputTable_L[mWave_FDS.nMainVolume]
+ [mWave_FDS.nVolume]
+ [mWave_FDS.nPopOutput];
+
+ mWave_FDS.nPopCount -= tick;
+ while(mWave_FDS.nPopCount <= 0)
+ {
+ mWave_FDS.nPopCount += 500;
+ mWave_FDS.nPopOutput--;
+ if(!mWave_FDS.nPopOutput)
+ mWave_FDS.nMainAddr = 0;
+ }
+ } /* end FDS */
+ }
+ EXIT_TIMER(fds);
+ } /* end while fulltick */
+
+ if(bBurnCPUCycles)
+ {
+ nCPUCycle += burned;
+ fulltick += burned;
+ }
+
+ /* Frame Sequencer */
+
+ ENTER_TIMER(frame);
+ nTicksUntilNextFrame -= tick<<16;
+ while(nTicksUntilNextFrame <= 0)
+ {
+ nTicksUntilNextFrame +=
+ (bPALMode ? PAL_FRAME_COUNTER_FREQ : NTSC_FRAME_COUNTER_FREQ) *
+ 0x10000;
+ nFrameCounter++;
+ if(nFrameCounter > nFrameCounterMax)
+ nFrameCounter = 0;
+
+ if(nFrameCounterMax == 4)
+ {
+ if(nFrameCounter < 4)
+ {
+ CLOCK_MAJOR();
+ if(!(nFrameCounter & 1))
+ CLOCK_MINOR();
+ }
+ }
+ else
+ {
+ CLOCK_MAJOR();
+ if(nFrameCounter & 1)
+ CLOCK_MINOR();
+
+ if((nFrameCounter == 3) && bFrameIRQEnabled)
+ bFrameIRQPending = 1;
+ }
+ }
+ EXIT_TIMER(frame);
+
+ ENTER_TIMER(mix);
+ nTicksUntilNextSample -= tick<<16;
+ if(nTicksUntilNextSample <= 0)
+ {
+ nTicksUntilNextSample += nTicksPerSample;
+
+ mixL = mWave_Squares.nMixL;
+ mixL += mWave_TND.nMixL;
+
+ if(nExternalSound && !bPALMode)
+ {
+ if(nExternalSound & EXTSOUND_VRC6)
+ {
+ mixL += (mWave_VRC6Pulse[0].nMixL);
+ mixL += (mWave_VRC6Pulse[1].nMixL);
+ mixL += (mWave_VRC6Saw.nMixL);
+ }
+ if(nExternalSound & EXTSOUND_N106) {
+ mixL += (mWave_N106.nMixL[0]);
+ mixL += (mWave_N106.nMixL[1]);
+ mixL += (mWave_N106.nMixL[2]);
+ mixL += (mWave_N106.nMixL[3]);
+ mixL += (mWave_N106.nMixL[4]);
+ mixL += (mWave_N106.nMixL[5]);
+ mixL += (mWave_N106.nMixL[6]);
+ mixL += (mWave_N106.nMixL[7]);
+ }
+ if(nExternalSound & EXTSOUND_FME07)
+ {
+ mixL += (mWave_FME07[0].nMixL);
+ mixL += (mWave_FME07[1].nMixL);
+ mixL += (mWave_FME07[2].nMixL);
+ }
+ if(nExternalSound & EXTSOUND_FDS)
+ mixL += mWave_FDS.nMixL;
+ }
+
+ /* Filter */
+ diff = ((int64_t)mixL << 25) - nFilterAccL;
+ nFilterAccL += (diff * nHighPass) >> 16;
+ mixL = (int32_t)(diff >> 23);
+ /* End Filter */
+
+ if(bFade && (fFadeVolume < 1))
+ mixL = (int32_t)(mixL * fFadeVolume);
+
+ if(mixL < -32768) mixL = -32768;
+ if(mixL > 32767) mixL = 32767;
+
+ *((uint16_t*)pOutput) = (uint16_t)mixL;
+ pOutput += 2;
+ }
+
+ }
+ EXIT_TIMER(mix);
+
+ nAPUCycle = nCPUCycle;
+
+ EXIT_TIMER(apu);
+}
+
+
+/*
+ * Initialize
+ *
+ * Initializes Memory
+ */
+
+int NSFCore_Initialize()
+{
+ int32_t i;
+ /* clear globals */
+ /* why, yes, this was easier when they were in a struct */
+
+ /* Initialization flags */
+ bMemoryOK=0;
+ bFileLoaded=0;
+ bTrackSelected=0;
+ bIsGeneratingSamples=0;
+
+ /*
+ * Memory
+ */
+
+ ZEROMEMORY(pRAM,0x800);
+ ZEROMEMORY(pSRAM,0x2000);
+ ZEROMEMORY(pExRAM,0x1000);
+ pROM_Full=0;
+
+ ZEROMEMORY(pROM,10);
+ pStack=0;
+
+ nROMSize=0;
+ nROMBankCount=0;
+ nROMMaxSize=0;
+
+ /*
+ * Memory Proc Pointers
+ */
+
+ ZEROMEMORY(ReadMemory,sizeof(ReadProc)*0x10);
+ ZEROMEMORY(WriteMemory,sizeof(WriteProc)*0x10);
+
+ /*
+ * 6502 Registers / Mode
+ */
+
+ regA=0;
+ regX=0;
+ regY=0;
+ regP=0;
+ regSP=0;
+ regPC=0;
+
+ bPALMode=0;
+ bCPUJammed=0;
+
+ nMultIn_Low=0;
+ nMultIn_High=0;
+
+ /*
+ * NSF Preparation Information
+ */
+
+ ZEROMEMORY(nBankswitchInitValues,10);
+ nPlayAddress=0;
+ nInitAddress=0;
+
+ nExternalSound=0;
+ nCurTrack=0;
+
+ fNSFPlaybackSpeed=0;
+
+ /*
+ * pAPU
+ */
+
+ nFrameCounter=0;
+ nFrameCounterMax=0;
+ bFrameIRQEnabled=0;
+ bFrameIRQPending=0;
+
+ /*
+ * Timing and Counters
+ */
+ nTicksUntilNextFrame=0;
+
+ nTicksPerPlay=0;
+ nTicksUntilNextPlay=0;
+
+ nTicksPerSample=0;
+ nTicksUntilNextSample=0;
+
+ nCPUCycle=0;
+ nAPUCycle=0;
+ nTotalPlays=0;
+
+ /*
+ * Silence Tracker
+ */
+ nSilentSamples=0;
+ nSilentSampleMax=0;
+ nSilenceTrackMS=0;
+ bNoSilenceIfTime=0;
+ bTimeNotDefault=0;
+
+ /*
+ * Volume/fading/filter tracking
+ */
+
+ nStartFade=0;
+ nEndFade=0;
+ bFade=0;
+ fFadeVolume=0;
+ fFadeChange=0;
+
+ pOutput=0;
+
+ nDMCPop_Prev=0;
+ bDMCPop_Skip=0;
+ bDMCPop_SamePlay=0;
+
+ /*
+ * Sound Filter
+ */
+
+ nFilterAccL=0;
+ nHighPass=0;
+
+ nHighPassBase=0;
+
+ bHighPassEnabled=0;
+
+ /* channels */
+
+ ZEROMEMORY(&mWave_Squares,sizeof(struct Wave_Squares));
+ ZEROMEMORY(&mWave_TND,sizeof(struct Wave_TND));
+ ZEROMEMORY(mWave_VRC6Pulse,sizeof(struct VRC6PulseWave)*2);
+ ZEROMEMORY(&mWave_VRC6Saw,sizeof(struct VRC6SawWave));
+ ZEROMEMORY(&mWave_N106,sizeof(struct N106Wave));
+ ZEROMEMORY(mWave_FME07,sizeof(struct FME07Wave)*3);
+ ZEROMEMORY(&mWave_FDS,sizeof(struct FDSWave));
+
+ /* end clear globals */
+
+ // Default filter bases
+ nHighPassBase = 150;
+
+ bHighPassEnabled = 1;
+
+ mWave_TND.nNoiseRandomShift = 1;
+ for(i = 0; i < 8; i++)
+ mWave_TND.pDMCDMAPtr[i] = pROM[i + 2];
+
+
+ SetPlaybackOptions(nSampleRate);
+
+ for(i = 0; i < 8; i++)
+ mWave_N106.fFrequencyLookupTable[i] =
+ (((i + 1) * 45 * 0x40000) / (float)NES_FREQUENCY) *
+ (float)NTSC_FREQUENCY;
+
+ if(bMemoryOK) return 1;
+
+ ZEROMEMORY(pRAM,0x800);
+ ZEROMEMORY(pSRAM,0x2000);
+ ZEROMEMORY(pExRAM,0x1000);
+ pStack = pRAM + 0x100;
+ bMemoryOK = 1;
+ return 1;
+}
+
+/*
+ * LoadNSF
+ */
+
+int LoadNSF(int32_t datasize)
+{
+ if(!bMemoryOK) return 0;
+
+ if(!pDataBuffer) return 0;
+
+ int32_t i;
+
+ bFileLoaded = 0;
+ bTrackSelected = 0;
+ nExternalSound = nChipExtensions;
+ if(nIsPal & 2)
+ bPALMode = bPALPreference;
+ else
+ bPALMode = nIsPal & 1;
+
+ SetPlaybackOptions(nSampleRate);
+
+ int32_t neededsize = datasize + (nfileLoadAddress & 0x0FFF);
+ if(neededsize & 0x0FFF) neededsize += 0x1000 - (neededsize & 0x0FFF);
+ if(neededsize < 0x1000) neededsize = 0x1000;
+
+ uint8_t specialload = 0;
+
+ for(i = 0; (i < 8) && (!nBankswitch[i]); i++);
+ if(i < 8) /* uses bankswitching */
+ {
+ memcpy(&nBankswitchInitValues[2],nBankswitch,8);
+ nBankswitchInitValues[0] = nBankswitch[6];
+ nBankswitchInitValues[1] = nBankswitch[7];
+ if(nExternalSound & EXTSOUND_FDS)
+ {
+ if(!(nBankswitchInitValues[0] || nBankswitchInitValues[1]))
+ {
+ /*
+ * FDS sound with '00' specified for both $6000 and $7000 banks.
+ * point this to an area of fresh RAM (sort of hackish solution
+ * for those FDS tunes that don't quite follow the nsf specs.
+ */
+ nBankswitchInitValues[0] = (uint8_t)(neededsize >> 12);
+ nBankswitchInitValues[1] = (uint8_t)(neededsize >> 12) + 1;
+ neededsize += 0x2000;
+ }
+ }
+ }
+ else /* doesn't use bankswitching */
+ {
+ if(nExternalSound & EXTSOUND_FDS)
+ {
+ /* bad load address */
+ if(nfileLoadAddress < 0x6000) return 0;
+
+ if(neededsize < 0xA000)
+ neededsize = 0xA000;
+ specialload = 1;
+ for(i = 0; i < 10; i++)
+ nBankswitchInitValues[i] = (uint8_t)i;
+ }
+ else
+ {
+ /* bad load address */
+ if(nfileLoadAddress < 0x8000) return 0;
+
+ int32_t j = (nfileLoadAddress >> 12) - 6;
+ for(i = 0; i < j; i++)
+ nBankswitchInitValues[i] = 0;
+ for(j = 0; i < 10; i++, j++)
+ nBankswitchInitValues[i] = (uint8_t)j;
+ }
+ }
+
+ nROMSize = neededsize;
+ nROMBankCount = neededsize >> 12;
+
+ if(specialload)
+ pROM_Full = pDataBuffer-(nfileLoadAddress-0x6000);
+ else
+ pROM_Full = pDataBuffer-(nfileLoadAddress&0x0FFF);
+
+ ZEROMEMORY(pRAM,0x0800);
+ ZEROMEMORY(pExRAM,0x1000);
+ ZEROMEMORY(pSRAM,0x2000);
+
+ nExternalSound = nChipExtensions;
+ fNSFPlaybackSpeed = (bPALMode ? PAL_NMIRATE : NTSC_NMIRATE);
+
+ bFileLoaded = 1;
+
+ SetPlaybackSpeed(0);
+
+ nPlayAddress = nfilePlayAddress;
+ nInitAddress = nfileInitAddress;
+
+ pExRAM[0x00] = 0x20; /* JSR */
+ pExRAM[0x01] = nInitAddress&0xff; /* Init Address */
+ pExRAM[0x02] = (nInitAddress>>8)&0xff;
+ pExRAM[0x03] = 0xF2; /* JAM */
+ pExRAM[0x04] = 0x20; /* JSR */
+ pExRAM[0x05] = nPlayAddress&0xff; /* Play Address */
+ pExRAM[0x06] = (nPlayAddress>>8)&0xff;
+ pExRAM[0x07] = 0x4C; /* JMP */
+ pExRAM[0x08] = 0x03;/* $5003 (JAM right before the JSR to play address) */
+ pExRAM[0x09] = 0x50;
+
+ regA = regX = regY = 0;
+ regP = 0x04; /* I_FLAG */
+ regSP = 0xFF;
+
+ nFilterAccL = 0;
+
+ /* Reset Read/Write Procs */
+
+ ReadMemory[0] = ReadMemory[1] = ReadMemory_RAM;
+ ReadMemory[2] = ReadMemory[3] = ReadMemory_Default;
+ ReadMemory[4] = ReadMemory_pAPU;
+ ReadMemory[5] = ReadMemory_ExRAM;
+ ReadMemory[6] = ReadMemory[7] = ReadMemory_SRAM;
+
+ WriteMemory[0] = WriteMemory[1] = WriteMemory_RAM;
+ WriteMemory[2] = WriteMemory[3] = WriteMemory_Default;
+ WriteMemory[4] = WriteMemory_pAPU;
+ WriteMemory[5] = WriteMemory_ExRAM;
+ WriteMemory[6] = WriteMemory[7] = WriteMemory_SRAM;
+
+ for(i = 8; i < 16; i++)
+ {
+ ReadMemory[i] = ReadMemory_ROM;
+ WriteMemory[i] = WriteMemory_Default;
+ }
+
+ if(nExternalSound & EXTSOUND_FDS)
+ {
+ WriteMemory[0x06] = WriteMemory_FDSRAM;
+ WriteMemory[0x07] = WriteMemory_FDSRAM;
+ WriteMemory[0x08] = WriteMemory_FDSRAM;
+ WriteMemory[0x09] = WriteMemory_FDSRAM;
+ WriteMemory[0x0A] = WriteMemory_FDSRAM;
+ WriteMemory[0x0B] = WriteMemory_FDSRAM;
+ WriteMemory[0x0C] = WriteMemory_FDSRAM;
+ WriteMemory[0x0D] = WriteMemory_FDSRAM;
+ ReadMemory[0x06] = ReadMemory_ROM;
+ ReadMemory[0x07] = ReadMemory_ROM;
+ }
+
+ if(!bPALMode) /* no expansion sound available on a PAL system */
+ {
+ if(nExternalSound & EXTSOUND_VRC6)
+ {
+ /* if both VRC6+VRC7... it MUST go to WriteMemory_VRC6
+ * or register writes will be lost (WriteMemory_VRC6 calls
+ * WriteMemory_VRC7 if needed) */
+ WriteMemory[0x09] = WriteMemory_VRC6;
+ WriteMemory[0x0A] = WriteMemory_VRC6;
+ WriteMemory[0x0B] = WriteMemory_VRC6;
+ }
+ if(nExternalSound & EXTSOUND_N106)
+ {
+ WriteMemory[0x04] = WriteMemory_N106;
+ ReadMemory[0x04] = ReadMemory_N106;
+ WriteMemory[0x0F] = WriteMemory_N106;
+ }
+ if(nExternalSound & EXTSOUND_FME07)
+ {
+ WriteMemory[0x0C] = WriteMemory_FME07;
+ WriteMemory[0x0E] = WriteMemory_FME07;
+ }
+ }
+
+ /* MMC5 still has a multiplication reg that needs to be available on
+ PAL tunes */
+ if(nExternalSound & EXTSOUND_MMC5)
+ WriteMemory[0x05] = WriteMemory_MMC5;
+
+ return 1;
+}
+
+/*
+ * SetTrack
+ */
+
+void SetTrack(uint8_t track)
+{
+ int32_t i;
+ if(!bFileLoaded) return;
+
+ bTrackSelected = 1;
+ nCurTrack = track;
+
+ regPC = 0x5000;
+ regA = track;
+ regX = bPALMode;
+ regY = bCleanAXY ? 0 : 0xCD;
+ regSP = 0xFF;
+ if(bCleanAXY)
+ regP = 0x04;
+ bCPUJammed = 0;
+
+ nCPUCycle = nAPUCycle = 0;
+ nDMCPop_Prev = 0;
+ bDMCPop_Skip = 0;
+
+ for(i = 0x4000; i < 0x400F; i++)
+ WriteMemory_pAPU(i,0);
+ WriteMemory_pAPU(0x4010,0);
+ WriteMemory_pAPU(0x4012,0);
+ WriteMemory_pAPU(0x4013,0);
+ WriteMemory_pAPU(0x4014,0);
+ WriteMemory_pAPU(0x4015,0);
+ WriteMemory_pAPU(0x4015,0x0F);
+ WriteMemory_pAPU(0x4017,0);
+
+ for(i = 0; i < 10; i++)
+ WriteMemory_ExRAM(0x5FF6 + i,nBankswitchInitValues[i]);
+
+ ZEROMEMORY(pRAM,0x0800);
+ ZEROMEMORY(pSRAM,0x2000);
+ ZEROMEMORY(&pExRAM[0x10],0x0FF0);
+ bFade = 0;
+
+
+ nTicksUntilNextSample = nTicksPerSample;
+ nTicksUntilNextFrame =
+ (bPALMode ? PAL_FRAME_COUNTER_FREQ : NTSC_FRAME_COUNTER_FREQ)*0x10000;
+ nTicksUntilNextPlay = nTicksPerPlay;
+ nTotalPlays = 0;
+
+ /* Clear mixing vals */
+ mWave_Squares.nMixL = 0;
+ mWave_TND.nMixL = 0;
+ mWave_VRC6Pulse[0].nMixL = 0;
+ mWave_VRC6Pulse[1].nMixL = 0;
+ mWave_VRC6Saw.nMixL = 0;
+
+ /* Reset Tri/Noise/DMC */
+ mWave_TND.nTriStep = mWave_TND.nTriOutput = 0;
+ mWave_TND.nDMCOutput = 0;
+ mWave_TND.bNoiseRandomOut = 0;
+ mWave_Squares.nDutyCount[0] = mWave_Squares.nDutyCount[1] = 0;
+ mWave_TND.bDMCActive = 0;
+ mWave_TND.nDMCBytesRemaining = 0;
+ mWave_TND.bDMCSampleBufferEmpty = 1;
+ mWave_TND.bDMCDeltaSilent = 1;
+
+ /* Reset VRC6 */
+ mWave_VRC6Pulse[0].nVolume = 0;
+ mWave_VRC6Pulse[1].nVolume = 0;
+ mWave_VRC6Saw.nAccumRate = 0;
+
+ /* Reset N106 */
+ ZEROMEMORY(mWave_N106.nRAM,0x100);
+ ZEROMEMORY(mWave_N106.nVolume,8);
+ ZEROMEMORY(mWave_N106.nOutput,8);
+ ZEROMEMORY(mWave_N106.nMixL,32);
+
+ /* Reset FME-07 */
+ mWave_FME07[0].nVolume = 0;
+ mWave_FME07[1].nVolume = 0;
+ mWave_FME07[2].nVolume = 0;
+
+ /* Clear FDS crap */
+
+ mWave_FDS.bEnvelopeEnable = 0;
+ mWave_FDS.nEnvelopeSpeed = 0xFF;
+ mWave_FDS.nVolEnv_Mode = 2;
+ mWave_FDS.nVolEnv_Decay = 0;
+ mWave_FDS.nVolEnv_Gain = 0;
+ mWave_FDS.nVolume = 0;
+ mWave_FDS.bVolEnv_On = 0;
+ mWave_FDS.nSweep_Mode = 2;
+ mWave_FDS.nSweep_Decay = 0;
+ mWave_FDS.nSweep_Gain = 0;
+ mWave_FDS.bSweepEnv_On = 0;
+ mWave_FDS.nSweepBias = 0;
+ mWave_FDS.bLFO_Enabled = 0;
+ mWave_FDS.nLFO_Freq.W = 0;
+/* mWave_FDS.fLFO_Timer = 0;
+ mWave_FDS.fLFO_Count = 0;*/
+ mWave_FDS.nLFO_Timer = 0;
+ mWave_FDS.nLFO_Count = 0;
+ mWave_FDS.nLFO_Addr = 0;
+ mWave_FDS.bLFO_On = 0;
+ mWave_FDS.nMainVolume = 0;
+ mWave_FDS.bEnabled = 0;
+ mWave_FDS.nFreq.W = 0;
+/* mWave_FDS.fFreqCount = 0;*/
+ mWave_FDS.nFreqCount = 0;
+ mWave_FDS.nMainAddr = 0;
+ mWave_FDS.bWaveWrite = 0;
+ mWave_FDS.bMain_On = 0;
+ mWave_FDS.nMixL = 0;
+ ZEROMEMORY(mWave_FDS.nWaveTable,0x40);
+ ZEROMEMORY(mWave_FDS.nLFO_Table,0x40);
+
+ mWave_FDS.nSweep_Count = mWave_FDS.nSweep_Timer =
+ ((mWave_FDS.nSweep_Decay + 1) * mWave_FDS.nEnvelopeSpeed * 8);
+ mWave_FDS.nVolEnv_Count = mWave_FDS.nVolEnv_Timer =
+ ((mWave_FDS.nVolEnv_Decay + 1) * mWave_FDS.nEnvelopeSpeed * 8);
+
+ nSilentSamples = 0;
+
+ nFilterAccL = 0;
+
+ nSilentSamples = 0;
+
+ fulltick=0;
+}
+
+/*
+ * SetPlaybackOptions
+ */
+
+int SetPlaybackOptions(int32_t samplerate)
+{
+ if(samplerate < 2000) return 0;
+ if(samplerate > 96000) return 0;
+
+ nTicksPerSample =
+ (bPALMode ? PAL_FREQUENCY : NTSC_FREQUENCY) / samplerate * 0x10000;
+ nTicksUntilNextSample = nTicksPerSample;
+
+ RecalcFilter();
+ RecalcSilenceTracker();
+
+ return 1;
+}
+
+/*
+ * SetPlaybackSpeed
+ */
+
+void SetPlaybackSpeed(float playspersec)
+{
+ if(playspersec < 1)
+ {
+ if(!bFileLoaded) return;
+ playspersec = fNSFPlaybackSpeed;
+ }
+
+ nTicksPerPlay = nTicksUntilNextPlay =
+ (bPALMode ? PAL_FREQUENCY : NTSC_FREQUENCY) / playspersec * 0x10000;
+}
+
+/*
+* GetPlaybackSpeed
+*/
+
+float GetPlaybackSpeed()
+{
+ if(nTicksPerPlay <= 0) return 0;
+ return ((bPALMode ? PAL_FREQUENCY : NTSC_FREQUENCY) / (nTicksPerPlay>>16));
+}
+
+/*
+ * RecalcFilter
+ */
+
+void RecalcFilter()
+{
+ if(!nSampleRate) return;
+
+ nHighPass = ((int64_t)nHighPassBase << 16) / nSampleRate;
+
+ if(nHighPass > (1<<16)) nHighPass = 1<<16;
+}
+
+/*
+ * RecalcSilenceTracker
+ */
+
+void RecalcSilenceTracker()
+{
+ if(nSilenceTrackMS <= 0 || !nSampleRate ||
+ (bNoSilenceIfTime && bTimeNotDefault))
+ {
+ nSilentSampleMax = 0;
+ return;
+ }
+
+ nSilentSampleMax = nSilenceTrackMS * nSampleRate / 500;
+ nSilentSampleMax /= 2;
+}
+
+void RebuildOutputTables(void) {
+ int32_t i,j;
+ float l[3];
+ int32_t v;
+ int32_t temp;
+ float ftemp;
+
+ /* tnd */
+ for(i = 0; i < 3; i++)
+ {
+ l[i] = 255;
+ }
+
+ for(i = 0; i < 0x8000; i++)
+ {
+ ftemp = (l[0] * (i >> 11)) / 2097885;
+ ftemp += (l[1] * ((i >> 7) & 0x0F)) / 3121455;
+ ftemp += (l[2] * (i & 0x7F)) / 5772690;
+
+ if(!ftemp)
+ main_nOutputTable_L[i] = 0;
+ else
+ main_nOutputTable_L[i] =
+ (int16_t)(2396850 / ((1.0f / ftemp) + 100));
+ }
+
+ /* squares */
+ for(i = 0; i < 2; i++)
+ {
+ l[i] = 255;
+ }
+
+ for(i = 0; i < 0x100; i++)
+ {
+ temp = (int32_t)(l[0] * (i >> 4));
+ temp += (int32_t)(l[1] * (i & 0x0F));
+
+ if(!temp)
+ Squares_nOutputTable_L[i] = 0;
+ else
+ Squares_nOutputTable_L[i] = 1438200 / ((2072640 / temp) + 100);
+
+ }
+
+ /* TODO: only one table needed for both */
+ /* VRC6 Pulse 1,2 */
+ /*CalculateChannelVolume(1875,&tl,255);*/
+ for(i = 0; i < 0x10; i++)
+ {
+ VRC6Pulse_nOutputTable_L[0][i] = VRC6Pulse_nOutputTable_L[1][i] =
+ 1875 * i / 0x0F;
+ }
+ /* VRC6 Saw */
+ /*CalculateChannelVolume(3750,&tl,255);*/
+ for(i = 0; i < 0x20; i++)
+ {
+ VRC6Saw_nOutputTable_L[i] = 3750 * i / 0x1F;
+ }
+
+ /* TODO: only one table needed for all 8 */
+ /* N106 channels */
+ for(v = 0; v < 8; v++)
+ {
+ /*CalculateChannelVolume(3000,&tl,255);
+ this amplitude is just a guess */
+
+ for(i = 0; i < 0x10; i++)
+ {
+ for(j = 0; j < 0x10; j++)
+ {
+ N106_nOutputTable_L[v][i][j] = (3000 * i * j) / 0xE1;
+ }
+ }
+ }
+
+ /* TODO: only one table needed for all 3 */
+ /* FME-07 Square A,B,C */
+ /*CalculateChannelVolume(3000,&tl,255);*/
+ FME07_nOutputTable_L[0][15] = FME07_nOutputTable_L[1][15] =
+ FME07_nOutputTable_L[2][15] = 3000;
+ FME07_nOutputTable_L[0][0] = FME07_nOutputTable_L[1][0] =
+ FME07_nOutputTable_L[2][0] = 0;
+ for(i = 14; i > 0; i--)
+ {
+ FME07_nOutputTable_L[0][i] = FME07_nOutputTable_L[1][i] =
+ FME07_nOutputTable_L[2][i] = FME07_nOutputTable_L[0][i + 1] * 80 / 100;
+ }
+
+ /*
+ * FDS
+ */
+ /* this base volume (4000) is just a guess to what sounds right.
+ * Given the number of steps available in an FDS wave... it seems like
+ * it should be much much more... but then it's TOO loud.
+ */
+ /*CalculateChannelVolume(4000,&tl,255);*/
+ for(i = 0; i < 0x21; i++)
+ {
+ for(j = 0; j < 0x40; j++)
+ {
+ FDS_nOutputTable_L[0][i][j] =
+ (4000 * i * j * 30) / (0x21 * 0x40 * 30);
+ FDS_nOutputTable_L[1][i][j] =
+ (4000 * i * j * 20) / (0x21 * 0x40 * 30);
+ FDS_nOutputTable_L[2][i][j] =
+ (4000 * i * j * 15) / (0x21 * 0x40 * 30);
+ FDS_nOutputTable_L[3][i][j] =
+ (4000 * i * j * 12) / (0x21 * 0x40 * 30);
+ }
+ }
+}
+
+/*
+ * GetPlayCalls
+ */
+
+float GetPlayCalls()
+{
+ if(!nTicksPerPlay) return 0;
+
+ return ((float)nTotalPlays) +
+ (1.0f - (nTicksUntilNextPlay*1.0f / nTicksPerPlay));
+}
+
+/*
+ * GetWrittenTime
+ */
+uint32_t GetWrittenTime(float basedplayspersec /* = 0 */)
+{
+ if(basedplayspersec <= 0)
+ basedplayspersec = GetPlaybackSpeed();
+
+ if(basedplayspersec <= 0)
+ return 0;
+
+ return (uint32_t)((GetPlayCalls() * 1000) / basedplayspersec);
+}
+
+/*
+ * StopFade
+ */
+void StopFade()
+{
+ bFade = 0;
+ fFadeVolume = 1;
+}
+
+/*
+ * SongCompleted
+ */
+
+uint8_t SongCompleted()
+{
+ if(!bFade) return 0;
+ if(nTotalPlays >= nEndFade) return 1;
+ if(nSilentSampleMax) return (nSilentSamples >= nSilentSampleMax);
+
+ return 0;
+}
+
+/*
+ * SetFade
+ */
+
+void SetFade(int32_t fadestart,int32_t fadestop,
+ uint8_t bNotDefault) /* play routine calls */
+{
+ if(fadestart < 0) fadestart = 0;
+ if(fadestop < fadestart) fadestop = fadestart;
+
+ nStartFade = (uint32_t)fadestart;
+ nEndFade = (uint32_t)fadestop;
+ bFade = 1;
+ bTimeNotDefault = bNotDefault;
+
+ RecalcSilenceTracker();
+ RecalculateFade();
+}
+
+/*
+ * SetFadeTime
+ */
+
+void SetFadeTime(uint32_t fadestart,uint32_t fadestop,float basedplays,
+ uint8_t bNotDefault) /* time in MS */
+{
+ if(basedplays <= 0)
+ basedplays = GetPlaybackSpeed();
+ if(basedplays <= 0)
+ return;
+
+ SetFade((int32_t)(fadestart * basedplays / 1000),
+ (int32_t)(fadestop * basedplays / 1000),bNotDefault);
+}
+
+/*
+ * RecalculateFade
+ */
+
+void RecalculateFade()
+{
+ if(!bFade) return;
+
+ /* make it hit silence a little before the song ends...
+ otherwise we're not really fading OUT, we're just fading umm...
+ quieter =P */
+ int32_t temp = (int32_t)(GetPlaybackSpeed() / 4);
+
+ if(nEndFade <= nStartFade)
+ {
+ nEndFade = nStartFade;
+ fFadeChange = 1.0f;
+ }
+ else if((nEndFade - temp) <= nStartFade)
+ fFadeChange = 1.0f;
+ else
+ fFadeChange = 1.0f / (nEndFade - nStartFade - temp);
+
+ if(nTotalPlays < nStartFade)
+ fFadeVolume = 1.0f;
+ else if(nTotalPlays >= nEndFade)
+ fFadeVolume = 0.0f;
+ else
+ {
+ fFadeVolume = 1.0f - ( (nTotalPlays - nStartFade + 1) * fFadeChange );
+ if(fFadeVolume < 0)
+ fFadeVolume = 0;
+ }
+
+}
+
+int32_t GetSamples(uint8_t* buffer,int32_t buffersize)
+{
+ if(!buffer) return 0;
+ if(buffersize < 16) return 0;
+ if(!bTrackSelected) return 0;
+ if(bFade && (nTotalPlays >= nEndFade)) return 0;
+ if(bIsGeneratingSamples) return 0;
+
+ bIsGeneratingSamples = 1;
+
+
+ pOutput = buffer;
+ uint32_t runtocycle =
+ (uint32_t)((buffersize / 2) * nTicksPerSample / 0x10000);
+ nCPUCycle = nAPUCycle = 0;
+ uint32_t tick;
+
+ while(1)
+ {
+ /*tick = (uint32_t)ceil(fTicksUntilNextPlay);*/
+ tick = (nTicksUntilNextPlay+0xffff)>>16;
+ if((tick + nCPUCycle) > runtocycle)
+ tick = runtocycle - nCPUCycle;
+
+ if(bCPUJammed)
+ {
+ nCPUCycle += tick;
+ EmulateAPU(0);
+ }
+ else
+ {
+ tick = Emulate6502(tick + nCPUCycle);
+ EmulateAPU(1);
+ }
+
+ nTicksUntilNextPlay -= tick<<16;
+ if(nTicksUntilNextPlay <= 0)
+ {
+ nTicksUntilNextPlay += nTicksPerPlay;
+ if((bCPUJammed == 2) || bNoWaitForReturn)
+ {
+ regX = regY = regA = (bCleanAXY ? 0 : 0xCD);
+ regPC = 0x5004;
+ nTotalPlays++;
+ bDMCPop_SamePlay = 0;
+ bCPUJammed = 0;
+ if(nForce4017Write == 1) WriteMemory_pAPU(0x4017,0x00);
+ if(nForce4017Write == 2) WriteMemory_pAPU(0x4017,0x80);
+ }
+
+ if(bFade && (nTotalPlays >= nStartFade))
+ {
+ fFadeVolume -= fFadeChange;
+ if(fFadeVolume < 0)
+ fFadeVolume = 0;
+ if(nTotalPlays >= nEndFade)
+ break;
+ }
+ }
+
+ if(nCPUCycle >= runtocycle)
+ break;
+ }
+
+ nCPUCycle = nAPUCycle = 0;
+ bIsGeneratingSamples = 0;
+
+ if(nSilentSampleMax && bFade)
+ {
+ int16_t* tempbuf = (int16_t*)buffer;
+ while( ((uint8_t*)tempbuf) < pOutput)
+ {
+ if( (*tempbuf < -SILENCE_THRESHOLD) ||
+ (*tempbuf > SILENCE_THRESHOLD) )
+ nSilentSamples = 0;
+ else
+ {
+ if(++nSilentSamples >= nSilentSampleMax)
+ return (int32_t)( ((uint8_t*)tempbuf) - buffer);
+ }
+ tempbuf++;
+ }
+ }
+
+ return (int32_t)(pOutput - buffer);
+}
+
+/****************** 6502 emulation ******************/
+
+/* Memory reading/writing and other defines */
+
+/* reads zero page memory */
+#define Zp(a) pRAM[a]
+/* reads zero page memory in word form */
+#define ZpWord(a) (Zp(a) | (Zp((uint8_t)(a + 1)) << 8))
+/* reads memory */
+#define Rd(a) ((ReadMemory[((uint16_t)(a)) >> 12])(a))
+/* reads memory in word form */
+#define RdWord(a) (Rd(a) | (Rd(a + 1) << 8))
+/* writes memory */
+#define Wr(a,v) (WriteMemory[((uint16_t)(a)) >> 12])(a,v)
+/* writes zero paged memory */
+#define WrZ(a,v) pRAM[a] = v
+/* pushes a value onto the stack */
+#define PUSH(v) pStack[SP--] = v
+/* pulls a value from the stack */
+#define PULL(v) v = pStack[++SP]
+
+/* Addressing Modes */
+
+/* first set - gets the value that's being addressed */
+/*Immediate*/
+#define Ad_VlIm() val = Rd(PC.W); PC.W++
+/*Zero Page*/
+#define Ad_VlZp() final.W = Rd(PC.W); val = Zp(final.W); PC.W++
+/*Zero Page, X*/
+#define Ad_VlZx() front.W = final.W = Rd(PC.W); final.B.l += X; \
+ val = Zp(final.B.l); PC.W++
+/*Zero Page, Y*/
+#define Ad_VlZy() front.W = final.W = Rd(PC.W); final.B.l += Y; \
+ val = Zp(final.B.l); PC.W++
+/*Absolute*/
+#define Ad_VlAb() final.W = RdWord(PC.W); val = Rd(final.W); PC.W += 2
+/*Absolute, X [uses extra cycle if crossed page]*/
+#define Ad_VlAx() front.W = final.W = RdWord(PC.W); final.W += X; PC.W += 2;\
+ if(front.B.h != final.B.h) nCPUCycle++; val = Rd(final.W)
+/*Absolute, X [uses extra cycle if crossed page]*/
+#define Ad_VlAy() front.W = final.W = RdWord(PC.W); final.W += Y; PC.W += 2;\
+ if(front.B.h != final.B.h) nCPUCycle++; val = Rd(final.W)
+/*(Indirect, X)*/
+#define Ad_VlIx() front.W = final.W = Rd(PC.W); final.B.l += X; PC.W++; \
+ final.W = ZpWord(final.B.l); val = Rd(final.W)
+/*(Indirect), Y [uses extra cycle if crossed page]*/
+#define Ad_VlIy() val = Rd(PC.W); front.W = final.W = ZpWord(val); PC.W++;\
+ final.W += Y; if(final.B.h != front.B.h) nCPUCycle++; \
+ front.W = val; val = Rd(final.W)
+
+/* second set - gets the ADDRESS that the mode is referring to (for operators
+ * that write to memory) note that AbsoluteX, AbsoluteY, and
+ * IndirectY modes do NOT check for page boundary crossing here
+ * since that extra cycle isn't added for operators that write to
+ * memory (it only applies to ones that only read from memory.. in
+ * which case the 1st set should be used)
+ */
+/*Zero Page*/
+#define Ad_AdZp() final.W = Rd(PC.W); PC.W++
+/*Zero Page, X*/
+#define Ad_AdZx() final.W = front.W = Rd(PC.W); final.B.l += X; PC.W++
+/*Zero Page, Y*/
+#define Ad_AdZy() final.W = front.W = Rd(PC.W); final.B.l += Y; PC.W++
+/*Absolute*/
+#define Ad_AdAb() final.W = RdWord(PC.W); PC.W += 2
+/*Absolute, X*/
+#define Ad_AdAx() front.W = final.W = RdWord(PC.W); PC.W += 2; \
+ final.W += X
+/*Absolute, Y*/
+#define Ad_AdAy() front.W = final.W = RdWord(PC.W); PC.W += 2; \
+ final.W += Y
+/*(Indirect, X)*/
+#define Ad_AdIx() front.W = final.W = Rd(PC.W); PC.W++; final.B.l += X; \
+ final.W = ZpWord(final.B.l)
+/*(Indirect), Y*/
+#define Ad_AdIy() front.W = Rd(PC.W); final.W = ZpWord(front.W) + Y; \
+ PC.W++
+
+/* third set - reads memory, performs the desired operation on the value, then
+ * writes back to memory
+ * used for operators that directly change memory (ASL, INC, DEC, etc)
+ */
+/*Zero Page*/
+#define MRW_Zp(cmd) Ad_AdZp(); val = Zp(final.W); cmd(val); WrZ(final.W,val)
+/*Zero Page, X*/
+#define MRW_Zx(cmd) Ad_AdZx(); val = Zp(final.W); cmd(val); WrZ(final.W,val)
+/*Zero Page, Y*/
+#define MRW_Zy(cmd) Ad_AdZy(); val = Zp(final.W); cmd(val); WrZ(final.W,val)
+/*Absolute*/
+#define MRW_Ab(cmd) Ad_AdAb(); val = Rd(final.W); cmd(val); Wr(final.W,val)
+/*Absolute, X*/
+#define MRW_Ax(cmd) Ad_AdAx(); val = Rd(final.W); cmd(val); Wr(final.W,val)
+/*Absolute, Y*/
+#define MRW_Ay(cmd) Ad_AdAy(); val = Rd(final.W); cmd(val); Wr(final.W,val)
+/*(Indirect, X)*/
+#define MRW_Ix(cmd) Ad_AdIx(); val = Rd(final.W); cmd(val); Wr(final.W,val)
+/*(Indirect), Y*/
+#define MRW_Iy(cmd) Ad_AdIy(); val = Rd(final.W); cmd(val); Wr(final.W,val)
+
+/* Relative modes are special in that they're only used by branch commands
+ * this macro handles the jump, and should only be called if the branch
+ * condition was true if the branch condition was false, the PC must be
+ * incremented
+ */
+
+#define RelJmp(cond) val = Rd(PC.W); PC.W++; final.W = PC.W + (int8_t)(val);\
+ if(cond) {\
+ nCPUCycle += ((final.B.h != PC.B.h) ? 2 : 1);\
+ PC.W = final.W; }
+
+/* Status Flags */
+
+#define C_FLAG 0x01 /* carry flag */
+#define Z_FLAG 0x02 /* zero flag */
+#define I_FLAG 0x04 /* mask interrupt flag */
+#define D_FLAG 0x08 /* decimal flag (decimal mode is unsupported on
+ NES) */
+#define B_FLAG 0x10 /* break flag (not really in the status register
+ It's value in ST is never used. When ST is
+ put in memory (by an interrupt or PHP), this
+ flag is set only if BRK was called)
+ ** also when PHP is called due to a bug */
+#define R_FLAG 0x20 /* reserved flag (not really in the register.
+ It's value is never used.
+ Whenever ST is put in memory,
+ this flag is always set) */
+#define V_FLAG 0x40 /* overflow flag */
+#define N_FLAG 0x80 /* sign flag */
+
+
+/* Lookup Tables */
+
+/* the number of CPU cycles used for each instruction */
+static const uint8_t CPU_Cycles[0x100] = {
+7,6,0,8,3,3,5,5,3,2,2,2,4,4,6,6,
+2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
+6,6,0,8,3,3,5,5,4,2,2,2,4,4,6,6,
+2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
+6,6,0,8,3,3,5,5,3,2,2,2,3,4,6,6,
+2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
+6,6,0,8,3,3,5,5,4,2,2,2,5,4,6,6,
+2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
+2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
+2,6,0,6,4,4,4,4,2,5,2,5,5,5,5,5,
+2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
+2,5,0,5,4,4,4,4,2,4,2,4,4,4,4,4,
+2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
+2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
+2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
+2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 };
+
+/* the status of the NZ flags for the given value */
+static const uint8_t NZTable[0x100] = {
+Z_FLAG,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,
+N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG,N_FLAG };
+
+/* A quick macro for working with the above table */
+#define UpdateNZ(v) ST = (ST & ~(N_FLAG|Z_FLAG)) | NZTable[v]
+
+
+/*
+ * Opcodes
+ *
+ * These opcodes perform the action with the given value (changing that
+ * value if necessary). Registers and flags associated with the operation
+ * are changed accordingly. There are a few exceptions which will be noted
+ * when they arise
+ */
+
+
+/* ADC
+ Adds the value to the accumulator with carry
+ Changes: A, NVZC
+ - Decimal mode not supported on the NES
+ - Due to a bug, NVZ flags are not altered if the Decimal flag is on
+ --(taken out)-- */
+#define ADC() \
+ tw.W = A + val + (ST & C_FLAG); \
+ ST = (ST & (I_FLAG|D_FLAG)) | tw.B.h | NZTable[tw.B.l] | \
+ ( (0x80 & ~(A ^ val) & (A ^ tw.B.l)) ? V_FLAG : 0 ); \
+ A = tw.B.l
+
+/* AND
+ Combines the value with the accumulator using a bitwise AND operation
+ Changes: A, NZ */
+#define AND() \
+ A &= val; \
+ UpdateNZ(A)
+
+/* ASL
+ Left shifts the value 1 bit. The bit that gets shifted out goes to
+ the carry flag.
+ Changes: value, NZC */
+#define ASL(value) \
+ tw.W = value << 1; \
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | tw.B.h | NZTable[tw.B.l]; \
+ value = tw.B.l
+
+/* BIT
+ Compares memory with the accumulator with an AND operation, but changes
+ neither.
+ The two high bits of memory get transferred to the status reg
+ Z is set if the AND operation yielded zero, otherwise it's cleared
+ Changes: NVZ */
+#define BIT() \
+ ST = (ST & ~(N_FLAG|V_FLAG|Z_FLAG)) | (val & (N_FLAG|V_FLAG)) | \
+ ((A & val) ? 0 : Z_FLAG)
+
+/* CMP, CPX, CPY
+ Compares memory with the given register with a subtraction operation.
+ Flags are set accordingly depending on the result:
+ Reg < Memory: Z=0, C=0
+ Reg = Memory: Z=1, C=1
+ Reg > Memory: Z=0, C=1
+ N is set according to the result of the subtraction operation
+ Changes: NZC
+
+ NOTE -- CMP, CPX, CPY all share this same routine, so the desired
+ register (A, X, or Y respectively) must be given when calling
+ this macro... as well as the memory to compare it with. */
+#define CMP(reg) \
+ tw.W = reg - val; \
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | (tw.B.h ? 0 : C_FLAG) | \
+ NZTable[tw.B.l]
+
+/* DEC, DEX, DEY
+ Decriments a value by one.
+ Changes: value, NZ */
+#define DEC(value) \
+ value--; \
+ UpdateNZ(value)
+
+/* EOR
+ Combines a value with the accumulator using a bitwise exclusive-OR
+ operation
+ Changes: A, NZ */
+#define EOR() \
+ A ^= val; \
+ UpdateNZ(A)
+
+/* INC, INX, INY
+ Incriments a value by one.
+ Changes: value, NZ */
+#define INC(value) \
+ value++; \
+ UpdateNZ(value)
+
+/* LSR
+ Shifts value one bit to the right. Bit that gets shifted out goes to
+ the Carry flag.
+ Changes: value, NZC */
+#define LSR(value) \
+ tw.W = value >> 1; \
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[tw.B.l] | \
+ (value & 0x01); \
+ value = tw.B.l
+
+/* ORA
+ Combines a value with the accumulator using a bitwise inclusive-OR
+ operation
+ Changes: A, NZ */
+#define ORA() \
+ A |= val; \
+ UpdateNZ(A)
+
+/* ROL
+ Rotates a value one bit to the left:
+ C <- 7<-6<-5<-4<-3<-2<-1<-0 <- C
+ Changes: value, NZC */
+#define ROL(value) \
+ tw.W = (value << 1) | (ST & 0x01); \
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[tw.B.l] | tw.B.h; \
+ value = tw.B.l
+
+/* ROR
+ Rotates a value one bit to the right:
+ C -> 7->6->5->4->3->2->1->0 -> C
+ Changes: value, NZC */
+#define ROR(value) \
+ tw.W = (value >> 1) | (ST << 7); \
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[tw.B.l] | \
+ (value & 0x01); \
+ value = tw.B.l
+
+/* SBC
+ Subtracts a value from the accumulator with borrow (inverted carry)
+ Changes: A, NVZC
+ - Decimal mode not supported on the NES
+ - Due to a bug, NVZ flags are not altered if the Decimal flag is on
+ --(taken out)-- */
+#define SBC() \
+ tw.W = A - val - ((ST & C_FLAG) ? 0 : 1); \
+ ST = (ST & (I_FLAG|D_FLAG)) | (tw.B.h ? 0 : C_FLAG) | NZTable[tw.B.l] | \
+ (((A ^ val) & (A ^ tw.B.l) & 0x80) ? V_FLAG : 0); \
+ A = tw.B.l
+
+/* Undocumented Opcodes
+ *
+ * These opcodes are not included in the official specifications. However,
+ * some of the unused opcode values perform operations which have since been
+ * documented.
+ */
+
+
+/* ASO
+ Left shifts a value, then ORs the result with the accumulator
+ Changes: value, A, NZC */
+#define ASO(value) \
+ tw.W = value << 1; \
+ A |= tw.B.l; \
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[A] | tw.B.h; \
+ value = tw.B.l
+
+/* RLA
+ Roll memory left 1 bit, then AND the result with the accumulator
+ Changes: value, A, NZC */
+#define RLA(value) \
+ tw.W = (value << 1) | (ST & 0x01); \
+ A &= tw.B.l; \
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[A] | tw.B.h; \
+ value = tw.B.l
+
+/* LSE
+ Right shifts a value one bit, then EORs the result with the accumulator
+ Changes: value, A, NZC */
+#define LSE(value) \
+ tw.W = value >> 1; \
+ A ^= tw.B.l; \
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[A] | (value & 0x01); \
+ value = tw.B.l
+
+/* RRA
+ Roll memory right one bit, then ADC the result
+ Changes: value, A, NVZC */
+#define RRA(value) \
+ tw.W = (value >> 1) | (ST << 7); \
+ ST = (ST & ~C_FLAG) | (value & 0x01); \
+ value = tw.B.l; \
+ ADC()
+
+/* AXS
+ ANDs the contents of the X and A registers and stores the result
+ int memory.
+ Changes: value [DOES NOT CHANGE X, A, or any flags] */
+#define AXS(value) \
+ value = A & X
+
+/* DCM
+ Decriments a value and compares it with the A register.
+ Changes: value, NZC */
+#define DCM(value) \
+ value--; \
+ CMP(A)
+
+/* INS
+ Incriments a value then SBCs it
+ Changes: value, A, NVZC */
+#define INS(value) \
+ value++; \
+ SBC()
+
+/* AXA */
+#define AXA(value) \
+ value = A & X & (Rd(PC.W - 1) + 1)
+
+
+/* The 6502 emulation function! */
+
+union TWIN front;
+union TWIN final;
+uint8_t val;
+uint8_t op;
+
+uint32_t Emulate6502(uint32_t runto)
+{
+ /* If the CPU is jammed... don't bother */
+ if(bCPUJammed == 1)
+ return 0;
+
+ register union TWIN tw; /* used in calculations */
+ register uint8_t ST = regP;
+ register union TWIN PC;
+ uint8_t SP = regSP;
+ register uint8_t A = regA;
+ register uint8_t X = regX;
+ register uint8_t Y = regY;
+ union TWIN front;
+ union TWIN final;
+ PC.W = regPC;
+
+ uint32_t ret = nCPUCycle;
+
+ ENTER_TIMER(cpu);
+
+ /* Start the loop */
+
+ while(nCPUCycle < runto)
+ {
+ op = Rd(PC.W);
+ PC.W++;
+
+ nCPUCycle += CPU_Cycles[op];
+ switch(op)
+ {
+ /* Documented Opcodes first */
+
+ /* Flag setting/clearing */
+ case 0x18: ST &= ~C_FLAG; break; /* CLC */
+ case 0x38: ST |= C_FLAG; break; /* SEC */
+ case 0x58: ST &= ~I_FLAG; break; /* CLI */
+ case 0x78: ST |= I_FLAG; break; /* SEI */
+ case 0xB8: ST &= ~V_FLAG; break; /* CLV */
+ case 0xD8: ST &= ~D_FLAG; break; /* CLD */
+ case 0xF8: ST |= D_FLAG; break; /* SED */
+
+ /* Branch commands */
+ case 0x10: RelJmp(!(ST & N_FLAG)); break; /* BPL */
+ case 0x30: RelJmp( (ST & N_FLAG)); break; /* BMI */
+ case 0x50: RelJmp(!(ST & V_FLAG)); break; /* BVC */
+ case 0x70: RelJmp( (ST & V_FLAG)); break; /* BVS */
+ case 0x90: RelJmp(!(ST & C_FLAG)); break; /* BCC */
+ case 0xB0: RelJmp( (ST & C_FLAG)); break; /* BCS */
+ case 0xD0: RelJmp(!(ST & Z_FLAG)); break; /* BNE */
+ case 0xF0: RelJmp( (ST & Z_FLAG)); break; /* BEQ */
+
+ /* Direct stack alteration commands (push/pull commands) */
+ case 0x08: PUSH(ST | R_FLAG | B_FLAG); break; /* PHP */
+ case 0x28: PULL(ST); break; /* PLP */
+ case 0x48: PUSH(A); break; /* PHA */
+ case 0x68: PULL(A); UpdateNZ(A); break; /* PLA */
+
+ /* Register Transfers */
+ case 0x8A: A = X; UpdateNZ(A); break; /* TXA */
+ case 0x98: A = Y; UpdateNZ(A); break; /* TYA */
+ case 0x9A: SP = X; break; /* TXS */
+ case 0xA8: Y = A; UpdateNZ(A); break; /* TAY */
+ case 0xAA: X = A; UpdateNZ(A); break; /* TAX */
+ case 0xBA: X = SP; UpdateNZ(X); break; /* TSX */
+
+ /* Other commands */
+
+ /* ADC */
+ case 0x61: Ad_VlIx(); ADC(); break;
+ case 0x65: Ad_VlZp(); ADC(); break;
+ case 0x69: Ad_VlIm(); ADC(); break;
+ case 0x6D: Ad_VlAb(); ADC(); break;
+ case 0x71: Ad_VlIy(); ADC(); break;
+ case 0x75: Ad_VlZx(); ADC(); break;
+ case 0x79: Ad_VlAy(); ADC(); break;
+ case 0x7D: Ad_VlAx(); ADC(); break;
+
+ /* AND */
+ case 0x21: Ad_VlIx(); AND(); break;
+ case 0x25: Ad_VlZp(); AND(); break;
+ case 0x29: Ad_VlIm(); AND(); break;
+ case 0x2D: Ad_VlAb(); AND(); break;
+ case 0x31: Ad_VlIy(); AND(); break;
+ case 0x35: Ad_VlZx(); AND(); break;
+ case 0x39: Ad_VlAy(); AND(); break;
+ case 0x3D: Ad_VlAx(); AND(); break;
+
+ /* ASL */
+ case 0x0A: ASL(A); break;
+ case 0x06: MRW_Zp(ASL); break;
+ case 0x0E: MRW_Ab(ASL); break;
+ case 0x16: MRW_Zx(ASL); break;
+ case 0x1E: MRW_Ax(ASL); break;
+
+ /* BIT */
+ case 0x24: Ad_VlZp(); BIT(); break;
+ case 0x2C: Ad_VlAb(); BIT(); break;
+
+ /* BRK */
+ case 0x00:
+ if(bIgnoreBRK)
+ break;
+ PC.W++; /*BRK has a padding byte*/
+ PUSH(PC.B.h); /*push high byte of the return address*/
+ PUSH(PC.B.l); /*push low byte of return address*/
+ PUSH(ST | R_FLAG | B_FLAG); /*push processor status with R|B flags*/
+ ST |= I_FLAG; /*mask interrupts*/
+ PC.W = RdWord(0xFFFE); /*read the IRQ vector and jump to it*/
+
+ /* extra check to make sure we didn't hit an infinite BRK loop */
+ if(!Rd(PC.W)) /* next command will be BRK */
+ {
+ /* the CPU will endlessly loop...
+ just jam it to ease processing power */
+ bCPUJammed = 1;
+ goto jammed;
+ }
+ break;
+
+ /* CMP */
+ case 0xC1: Ad_VlIx(); CMP(A); break;
+ case 0xC5: Ad_VlZp(); CMP(A); break;
+ case 0xC9: Ad_VlIm(); CMP(A); break;
+ case 0xCD: Ad_VlAb(); CMP(A); break;
+ case 0xD1: Ad_VlIy(); CMP(A); break;
+ case 0xD5: Ad_VlZx(); CMP(A); break;
+ case 0xD9: Ad_VlAy(); CMP(A); break;
+ case 0xDD: Ad_VlAx(); CMP(A); break;
+
+ /* CPX */
+ case 0xE0: Ad_VlIm(); CMP(X); break;
+ case 0xE4: Ad_VlZp(); CMP(X); break;
+ case 0xEC: Ad_VlAb(); CMP(X); break;
+
+ /* CPY */
+ case 0xC0: Ad_VlIm(); CMP(Y); break;
+ case 0xC4: Ad_VlZp(); CMP(Y); break;
+ case 0xCC: Ad_VlAb(); CMP(Y); break;
+
+ /* DEC */
+ case 0xCA: DEC(X); break; /* DEX */
+ case 0x88: DEC(Y); break; /* DEY */
+ case 0xC6: MRW_Zp(DEC); break;
+ case 0xCE: MRW_Ab(DEC); break;
+ case 0xD6: MRW_Zx(DEC); break;
+ case 0xDE: MRW_Ax(DEC); break;
+
+ /* EOR */
+ case 0x41: Ad_VlIx(); EOR(); break;
+ case 0x45: Ad_VlZp(); EOR(); break;
+ case 0x49: Ad_VlIm(); EOR(); break;
+ case 0x4D: Ad_VlAb(); EOR(); break;
+ case 0x51: Ad_VlIy(); EOR(); break;
+ case 0x55: Ad_VlZx(); EOR(); break;
+ case 0x59: Ad_VlAy(); EOR(); break;
+ case 0x5D: Ad_VlAx(); EOR(); break;
+
+ /* INC */
+ case 0xE8: INC(X); break; /* INX */
+ case 0xC8: INC(Y); break; /* INY */
+ case 0xE6: MRW_Zp(INC); break;
+ case 0xEE: MRW_Ab(INC); break;
+ case 0xF6: MRW_Zx(INC); break;
+ case 0xFE: MRW_Ax(INC); break;
+
+ /* JMP */
+ /* Absolute JMP */
+ case 0x4C: final.W = RdWord(PC.W); PC.W = final.W; val = 0; break;
+ /* Indirect JMP -- must take caution:
+ Indirection at 01FF will read from 01FF and 0100 (not 0200) */
+ case 0x6C: front.W = final.W = RdWord(PC.W);
+ PC.B.l = Rd(final.W); final.B.l++;
+ PC.B.h = Rd(final.W); final.W = PC.W;
+ break;
+ /* JSR */
+ case 0x20:
+ val = 0;
+ final.W = RdWord(PC.W);
+ PC.W++; /* JSR only increments the return address by one.
+ It's incremented again upon RTS */
+ PUSH(PC.B.h); /* push high byte of return address */
+ PUSH(PC.B.l); /* push low byte of return address */
+ PC.W = final.W;
+ break;
+
+ /* LDA */
+ case 0xA1: Ad_VlIx(); A = val; UpdateNZ(A); break;
+ case 0xA5: Ad_VlZp(); A = val; UpdateNZ(A); break;
+ case 0xA9: Ad_VlIm(); A = val; UpdateNZ(A); break;
+ case 0xAD: Ad_VlAb(); A = val; UpdateNZ(A); break;
+ case 0xB1: Ad_VlIy(); A = val; UpdateNZ(A); break;
+ case 0xB5: Ad_VlZx(); A = val; UpdateNZ(A); break;
+ case 0xB9: Ad_VlAy(); A = val; UpdateNZ(A); break;
+ case 0xBD: Ad_VlAx(); A = val; UpdateNZ(A); break;
+
+ /* LDX */
+ case 0xA2: Ad_VlIm(); X = val; UpdateNZ(X); break;
+ case 0xA6: Ad_VlZp(); X = val; UpdateNZ(X); break;
+ case 0xAE: Ad_VlAb(); X = val; UpdateNZ(X); break;
+ case 0xB6: Ad_VlZy(); X = val; UpdateNZ(X); break;
+ case 0xBE: Ad_VlAy(); X = val; UpdateNZ(X); break;
+
+ /* LDY */
+ case 0xA0: Ad_VlIm(); Y = val; UpdateNZ(Y); break;
+ case 0xA4: Ad_VlZp(); Y = val; UpdateNZ(Y); break;
+ case 0xAC: Ad_VlAb(); Y = val; UpdateNZ(Y); break;
+ case 0xB4: Ad_VlZx(); Y = val; UpdateNZ(Y); break;
+ case 0xBC: Ad_VlAx(); Y = val; UpdateNZ(Y); break;
+
+ /* LSR */
+ case 0x4A: LSR(A); break;
+ case 0x46: MRW_Zp(LSR); break;
+ case 0x4E: MRW_Ab(LSR); break;
+ case 0x56: MRW_Zx(LSR); break;
+ case 0x5E: MRW_Ax(LSR); break;
+
+ /* NOP */
+ case 0xEA:
+
+ /* --- Undocumented ---
+ These opcodes perform the same action as NOP */
+ case 0x1A: case 0x3A: case 0x5A:
+ case 0x7A: case 0xDA: case 0xFA: break;
+
+ /* ORA */
+ case 0x01: Ad_VlIx(); ORA(); break;
+ case 0x05: Ad_VlZp(); ORA(); break;
+ case 0x09: Ad_VlIm(); ORA(); break;
+ case 0x0D: Ad_VlAb(); ORA(); break;
+ case 0x11: Ad_VlIy(); ORA(); break;
+ case 0x15: Ad_VlZx(); ORA(); break;
+ case 0x19: Ad_VlAy(); ORA(); break;
+ case 0x1D: Ad_VlAx(); ORA(); break;
+
+ /* ROL */
+ case 0x2A: ROL(A); break;
+ case 0x26: MRW_Zp(ROL); break;
+ case 0x2E: MRW_Ab(ROL); break;
+ case 0x36: MRW_Zx(ROL); break;
+ case 0x3E: MRW_Ax(ROL); break;
+
+ /* ROR */
+ case 0x6A: ROR(A); break;
+ case 0x66: MRW_Zp(ROR); break;
+ case 0x6E: MRW_Ab(ROR); break;
+ case 0x76: MRW_Zx(ROR); break;
+ case 0x7E: MRW_Ax(ROR); break;
+
+ /* RTI */
+ case 0x40:
+ PULL(ST); /*pull processor status*/
+ PULL(PC.B.l); /*pull low byte of return address*/
+ PULL(PC.B.h); /*pull high byte of return address*/
+ break;
+
+ /* RTS */
+ case 0x60:
+ PULL(PC.B.l);
+ PULL(PC.B.h);
+ PC.W++; /* the return address is one less of what it needs */
+ break;
+
+ /* SBC */
+ case 0xE1: Ad_VlIx(); SBC(); break;
+ case 0xE5: Ad_VlZp(); SBC(); break;
+ /* - Undocumented - EB performs the same operation as SBC immediate */
+ case 0xEB:
+ case 0xE9: Ad_VlIm(); SBC(); break;
+ case 0xED: Ad_VlAb(); SBC(); break;
+ case 0xF1: Ad_VlIy(); SBC(); break;
+ case 0xF5: Ad_VlZx(); SBC(); break;
+ case 0xF9: Ad_VlAy(); SBC(); break;
+ case 0xFD: Ad_VlAx(); SBC(); break;
+
+ /* STA */
+ case 0x81: Ad_AdIx(); val = A; Wr(final.W,A); break;
+ case 0x85: Ad_AdZp(); val = A; WrZ(final.W,A); break;
+ case 0x8D: Ad_AdAb(); val = A; Wr(final.W,A); break;
+ case 0x91: Ad_AdIy(); val = A; Wr(final.W,A); break;
+ case 0x95: Ad_AdZx(); val = A; WrZ(final.W,A); break;
+ case 0x99: Ad_AdAy(); val = A; Wr(final.W,A); break;
+ case 0x9D: Ad_AdAx(); val = A; Wr(final.W,A); break;
+
+ /* STX */
+ case 0x86: Ad_AdZp(); val = X; WrZ(final.W,X); break;
+ case 0x8E: Ad_AdAb(); val = X; Wr(final.W,X); break;
+ case 0x96: Ad_AdZy(); val = X; WrZ(final.W,X); break;
+
+ /* STY */
+ case 0x84: Ad_AdZp(); val = Y; WrZ(final.W,Y); break;
+ case 0x8C: Ad_AdAb(); val = Y; Wr(final.W,Y); break;
+ case 0x94: Ad_AdZx(); val = Y; WrZ(final.W,Y); break;
+
+ /* Undocumented Opcodes */
+ /* ASO */
+ case 0x03: if(bIgnoreIllegalOps) break; MRW_Ix(ASO); break;
+ case 0x07: if(bIgnoreIllegalOps) break; MRW_Zp(ASO); break;
+ case 0x0F: if(bIgnoreIllegalOps) break; MRW_Ab(ASO); break;
+ case 0x13: if(bIgnoreIllegalOps) break; MRW_Iy(ASO); break;
+ case 0x17: if(bIgnoreIllegalOps) break; MRW_Zx(ASO); break;
+ case 0x1B: if(bIgnoreIllegalOps) break; MRW_Ay(ASO); break;
+ case 0x1F: if(bIgnoreIllegalOps) break; MRW_Ax(ASO); break;
+
+ /* RLA */
+ case 0x23: if(bIgnoreIllegalOps) break; MRW_Ix(RLA); break;
+ case 0x27: if(bIgnoreIllegalOps) break; MRW_Zp(RLA); break;
+ case 0x2F: if(bIgnoreIllegalOps) break; MRW_Ab(RLA); break;
+ case 0x33: if(bIgnoreIllegalOps) break; MRW_Iy(RLA); break;
+ case 0x37: if(bIgnoreIllegalOps) break; MRW_Zx(RLA); break;
+ case 0x3B: if(bIgnoreIllegalOps) break; MRW_Ay(RLA); break;
+ case 0x3F: if(bIgnoreIllegalOps) break; MRW_Ax(RLA); break;
+
+ /* LSE */
+ case 0x43: if(bIgnoreIllegalOps) break; MRW_Ix(LSE); break;
+ case 0x47: if(bIgnoreIllegalOps) break; MRW_Zp(LSE); break;
+ case 0x4F: if(bIgnoreIllegalOps) break; MRW_Ab(LSE); break;
+ case 0x53: if(bIgnoreIllegalOps) break; MRW_Iy(LSE); break;
+ case 0x57: if(bIgnoreIllegalOps) break; MRW_Zx(LSE); break;
+ case 0x5B: if(bIgnoreIllegalOps) break; MRW_Ay(LSE); break;
+ case 0x5F: if(bIgnoreIllegalOps) break; MRW_Ax(LSE); break;
+
+ /* RRA */
+ case 0x63: if(bIgnoreIllegalOps) break; MRW_Ix(RRA); break;
+ case 0x67: if(bIgnoreIllegalOps) break; MRW_Zp(RRA); break;
+ case 0x6F: if(bIgnoreIllegalOps) break; MRW_Ab(RRA); break;
+ case 0x73: if(bIgnoreIllegalOps) break; MRW_Iy(RRA); break;
+ case 0x77: if(bIgnoreIllegalOps) break; MRW_Zx(RRA); break;
+ case 0x7B: if(bIgnoreIllegalOps) break; MRW_Ay(RRA); break;
+ case 0x7F: if(bIgnoreIllegalOps) break; MRW_Ax(RRA); break;
+
+ /* AXS */
+ case 0x83: if(bIgnoreIllegalOps) break; MRW_Ix(AXS); break;
+ case 0x87: if(bIgnoreIllegalOps) break; MRW_Zp(AXS); break;
+ case 0x8F: if(bIgnoreIllegalOps) break; MRW_Ab(AXS); break;
+ case 0x97: if(bIgnoreIllegalOps) break; MRW_Zy(AXS); break;
+
+ /* LAX */
+ case 0xA3: if(bIgnoreIllegalOps) break;
+ Ad_VlIx(); X = A = val; UpdateNZ(A); break;
+ case 0xA7: if(bIgnoreIllegalOps) break;
+ Ad_VlZp(); X = A = val; UpdateNZ(A); break;
+ case 0xAF: if(bIgnoreIllegalOps) break;
+ Ad_VlAb(); X = A = val; UpdateNZ(A); break;
+ case 0xB3: if(bIgnoreIllegalOps) break;
+ Ad_VlIy(); X = A = val; UpdateNZ(A); break;
+ case 0xB7: if(bIgnoreIllegalOps) break;
+ Ad_VlZy(); X = A = val; UpdateNZ(A); break;
+ case 0xBF: if(bIgnoreIllegalOps) break;
+ Ad_VlAy(); X = A = val; UpdateNZ(A); break;
+
+ /* DCM */
+ case 0xC3: if(bIgnoreIllegalOps) break; MRW_Ix(DCM); break;
+ case 0xC7: if(bIgnoreIllegalOps) break; MRW_Zp(DCM); break;
+ case 0xCF: if(bIgnoreIllegalOps) break; MRW_Ab(DCM); break;
+ case 0xD3: if(bIgnoreIllegalOps) break; MRW_Iy(DCM); break;
+ case 0xD7: if(bIgnoreIllegalOps) break; MRW_Zx(DCM); break;
+ case 0xDB: if(bIgnoreIllegalOps) break; MRW_Ay(DCM); break;
+ case 0xDF: if(bIgnoreIllegalOps) break; MRW_Ax(DCM); break;
+
+ /* INS */
+ case 0xE3: if(bIgnoreIllegalOps) break; MRW_Ix(INS); break;
+ case 0xE7: if(bIgnoreIllegalOps) break; MRW_Zp(INS); break;
+ case 0xEF: if(bIgnoreIllegalOps) break; MRW_Ab(INS); break;
+ case 0xF3: if(bIgnoreIllegalOps) break; MRW_Iy(INS); break;
+ case 0xF7: if(bIgnoreIllegalOps) break; MRW_Zx(INS); break;
+ case 0xFB: if(bIgnoreIllegalOps) break; MRW_Ay(INS); break;
+ case 0xFF: if(bIgnoreIllegalOps) break; MRW_Ax(INS); break;
+
+ /* ALR
+ AND Accumulator with memory and LSR the result */
+ case 0x4B: if(bIgnoreIllegalOps) break;
+ Ad_VlIm(); A &= val; LSR(A); break;
+
+ /* ARR
+ ANDs memory with the Accumulator and RORs the result */
+ case 0x6B: if(bIgnoreIllegalOps) break;
+ Ad_VlIm(); A &= val; ROR(A); break;
+
+ /* XAA
+ Transfers X -> A, then ANDs A with memory */
+ case 0x8B: if(bIgnoreIllegalOps) break;
+ Ad_VlIm(); A = X & val; UpdateNZ(A); break;
+
+ /* OAL
+ OR the Accumulator with #EE, AND Accumulator with Memory,
+ Transfer A -> X */
+ case 0xAB: if(bIgnoreIllegalOps) break;
+ Ad_VlIm(); X = (A &= (val | 0xEE));
+ UpdateNZ(A); break;
+
+ /* SAX
+ ANDs A and X registers (does not change A), subtracts memory
+ from result (CMP style, not SBC style) result is stored in X */
+ case 0xCB: if(bIgnoreIllegalOps) break;
+ Ad_VlIm(); tw.W = (X & A) - val; X = tw.B.l;
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) | NZTable[X] |
+ (tw.B.h ? C_FLAG : 0); break;
+ /* SKB
+ Skip Byte... or DOP - Double No-Op
+ These bytes do nothing, but take a parameter (which can be
+ ignored) */
+ case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64:
+ case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2:
+ case 0xF4:
+ if(bIgnoreIllegalOps) break;
+ PC.W++; /* skip unused byte */
+ break;
+
+ /* SKW
+ Swip Word... or TOP - Tripple No-Op
+ These bytes are the same as SKB, only they take a 2 byte parameter.
+ This can be ignored in some cases, but the read needs to be
+ performed in a some cases because an extra clock cycle may be used
+ in the process */
+ /* Absolute address... no need for operator */
+ case 0x0C:
+ if(bIgnoreIllegalOps) break;
+ PC.W += 2; break;
+ /* Absolute X address... may cross page, have to perform the read */
+ case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC:
+ if(bIgnoreIllegalOps) break;
+ Ad_VlAx(); break;
+
+ /* HLT / JAM
+ Jams up CPU operation */
+ case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52:
+ case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: case 0xF2:
+ /*it's not -really- jammed... only the NSF code has ended*/
+ if(PC.W == 0x5004) bCPUJammed = 2;
+ else
+ {
+ if(bIgnoreIllegalOps) break;
+ bCPUJammed = 1;
+ }
+ goto jammed;
+
+ /* TAS */
+ case 0x9B:
+ if(bIgnoreIllegalOps) break;
+ Ad_AdAy();
+ SP = A & X & (Rd(PC.W - 1) + 1);
+ Wr(final.W,SP);
+ break;
+
+ /* SAY */
+ case 0x9C:
+ if(bIgnoreIllegalOps) break;
+ Ad_AdAx();
+ Y &= (Rd(PC.W - 1) + 1);
+ Wr(final.W,Y);
+ break;
+
+ /* XAS */
+ case 0x9E:
+ if(bIgnoreIllegalOps) break;
+ Ad_AdAy();
+ X &= (Rd(PC.W - 1) + 1);
+ Wr(final.W,X);
+ break;
+
+ /* AXA */
+ case 0x93: if(bIgnoreIllegalOps) break; MRW_Iy(AXA); break;
+ case 0x9F: if(bIgnoreIllegalOps) break; MRW_Ay(AXA); break;
+
+ /* ANC */
+ case 0x0B: case 0x2B:
+ if(bIgnoreIllegalOps) break;
+ Ad_VlIm();
+ A &= val;
+ ST = (ST & ~(N_FLAG|Z_FLAG|C_FLAG)) |
+ NZTable[A] | ((A & 0x80) ? C_FLAG : 0);
+ break;
+
+ /* LAS */
+ case 0xBB:
+ if(bIgnoreIllegalOps) break;
+ Ad_VlAy();
+ X = A = (SP &= val);
+ UpdateNZ(A);
+ break;
+ }
+ }
+
+jammed:
+ regPC = PC.W;
+ regA = A;
+ regX = X;
+ regY = Y;
+ regSP = SP;
+ regP = ST;
+
+ EXIT_TIMER(cpu);
+
+ return (nCPUCycle - ret);
+}
+
+/****************** rockbox interface ******************/
+
+void set_codec_track(int t, int d) {
+ int track,fade,def=0;
+ SetTrack(t);
+
+ /* for REPEAT_ONE we disable track limits */
+ if (ci->global_settings->repeat_mode!=REPEAT_ONE) {
+ if (!bIsExtended || nTrackTime[t]==-1) {track=60*2*1000; def=1;}
+ else track=nTrackTime[t];
+ if (!bIsExtended || nTrackFade[t]==-1) fade=5*1000;
+ else fade=nTrackFade[t];
+ nSilenceTrackMS=5000;
+ SetFadeTime(track,track+fade, fNSFPlaybackSpeed,def);
+ }
+ ci->id3->elapsed=d*1000; /* d is track no to display */
+}
+
+/* this is the codec entry point */
+enum codec_status codec_main(void)
+{
+ int written;
+ uint8_t *buf;
+ size_t n;
+ int endofstream; /* end of stream flag */
+ int track;
+ int dontresettrack;
+ char last_path[MAX_PATH];
+ int usingplaylist;
+
+ /* we only render 16 bits */
+ ci->configure(DSP_SET_SAMPLE_DEPTH, (long *)16);
+
+ ci->configure(DSP_SET_FREQUENCY, (long*)44100);
+ ci->configure(DSP_SET_STEREO_MODE, (long *)STEREO_MONO);
+
+ RebuildOutputTables();
+
+ dontresettrack=0;
+ last_path[0]='\0';
+ track=0;
+
+next_track:
+ usingplaylist=0;
+ DEBUGF("NSF: next_track\n");
+ if (codec_init()) {
+ return CODEC_ERROR;
+ }
+ DEBUGF("NSF: after init\n");
+
+
+ /* wait for track info to load */
+ while (!*ci->taginfo_ready && !ci->stop_codec)
+ ci->sleep(1);
+
+ /* Read the entire file */
+ DEBUGF("NSF: request file\n");
+ ci->seek_buffer(0);
+ buf = ci->request_buffer(&n, ci->filesize);
+ if (!buf || n < (size_t)ci->filesize) {
+ DEBUGF("NSF: file load failed\n");
+ return CODEC_ERROR;
+ }
+
+init_nsf:
+ if(!NSFCore_Initialize()) {
+ DEBUGF("NSF: NSFCore_Initialize failed\n"); return CODEC_ERROR;}
+
+ if(LoadFile(buf,ci->filesize)) {
+ DEBUGF("NSF: LoadFile failed\n"); return CODEC_ERROR;}
+ if(!SetPlaybackOptions(44100)) {
+ DEBUGF("NSF: SetPlaybackOptions failed\n"); return CODEC_ERROR;}
+ if(!LoadNSF(nDataBufferSize)) {
+ DEBUGF("NSF: LoadNSF failed\n"); return CODEC_ERROR;}
+
+ ci->id3->title=szGameTitle;
+ ci->id3->artist=szArtist;
+ ci->id3->album=szCopyright;
+ if (usingplaylist) {
+ ci->id3->length=nPlaylistSize*1000;
+ } else {
+ ci->id3->length=nTrackCount*1000;
+ }
+
+ if (!dontresettrack||strcmp(ci->id3->path,last_path)) {
+ /* if this is the first time we're seeing this file, or if we haven't
+ been asked to preserve the track number, default to the proper
+ initial track */
+ if (bIsExtended &&
+ ci->global_settings->repeat_mode!=REPEAT_ONE && nPlaylistSize>0) {
+ /* decide to use the playlist */
+ usingplaylist=1;
+ track=0;
+ set_codec_track(nPlaylist[0],0);
+ } else {
+ /* simply use the initial track */
+ track=nInitialTrack;
+ set_codec_track(track,track);
+ }
+ } else {
+ /* if we've already been running this file assume track is set
+ already */
+ if (usingplaylist) set_codec_track(nPlaylist[track],track);
+ else set_codec_track(track,track);
+ }
+ strcpy(last_path,ci->id3->path);
+
+ /* The main decoder loop */
+
+ endofstream = 0;
+
+ reset_profile_timers();
+
+ while (!endofstream) {
+
+ ci->yield();
+ if (ci->stop_codec || ci->new_track) {
+ break;
+ }
+
+ if (ci->seek_time >0) {
+ track=ci->seek_time/1000;
+ if (usingplaylist) {
+ if (track>=nPlaylistSize) break;
+ } else {
+ if (track>=nTrackCount) break;
+ }
+ ci->seek_complete();
+ dontresettrack=1;
+ goto init_nsf;
+ }
+
+ ENTER_TIMER(total);
+ written=GetSamples((uint8_t*)samples,WAV_CHUNK_SIZE/2);
+ EXIT_TIMER(total);
+
+ if (!written || SongCompleted()) {
+ print_timers(last_path,track);
+ reset_profile_timers();
+
+ track++;
+ if (usingplaylist) {
+ if (track>=nPlaylistSize) break;
+ } else {
+ if (track>=nTrackCount) break;
+ }
+ dontresettrack=1;
+ goto init_nsf;
+ }
+
+ while (!ci->pcmbuf_insert((char *)samples, written))
+ ci->yield();
+
+ }
+
+ print_timers(last_path,track);
+
+ if (ci->request_next_track()) {
+ if (ci->global_settings->repeat_mode==REPEAT_ONE) {
+ /* in repeat one mode just advance to the next track */
+ track++;
+ if (track>=nTrackCount) track=0;
+ dontresettrack=1;
+ /* at this point we can't tell if another file has been selected */
+ goto next_track;
+ } else {
+ /* otherwise do a proper load of the next file */
+ dontresettrack=0;
+ last_path[0]='\0';
+ }
+ goto next_track; /* when we fall through here we'll reload the file */
+ }
+
+ return CODEC_OK;
+}