| /*************************************************************************** |
| * __________ __ ___. |
| * 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[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 */ |
| int32_t nFrequencyLookupTable[8]; /* lookup tbl for freq conversions */ |
| |
| /* |
| * Individual channel stuff |
| */ |
| /* Wavelength / Frequency */ |
| union QUAD nFreqReg[8]; |
| int32_t nFreqTimer[8]; |
| int32_t nFreqCount[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[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[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[0x10][0x10] 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 IDATA_ATTR; |
| 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[mWave_N106.nVolume[i]] |
| [mWave_N106.nOutput[i]]; |
| |
| if(mWave_N106.nFreqTimer[i] < 0) |
| mWave_N106.nFreqTimer[i] = |
| (mWave_N106.nFrequencyLookupTable[mWave_N106.nActiveChannels] / |
| mWave_N106.nFreqReg[i].D); |
| if(mWave_N106.nFreqCount[i] > mWave_N106.nFreqTimer[i]) |
| mWave_N106.nFreqCount[i] = mWave_N106.nFreqTimer[i]; |
| |
| mWave_N106.nFreqCount[i] -= ticks << 8; |
| while(mWave_N106.nFreqCount[i] <= 0) |
| { |
| mWave_N106.nFreqCount[i] += mWave_N106.nFreqTimer[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]; |
| } |
| |
| } |
| } |
| } |
| } |
| /****************** VRC6 ******************/ |
| |
| #ifdef ICODE_INSTEAD_OF_INLINE |
| void Wave_VRC6_DoTicks(const int32_t ticks) ICODE_ATTR; |
| void Wave_VRC6_DoTicks(const int32_t ticks) |
| #else |
| inline void Wave_VRC6_DoTicks(const int32_t ticks); |
| inline void Wave_VRC6_DoTicks(const int32_t ticks) |
| #endif |
| { |
| register int32_t i; |
| |
| for(i = 0; i < 2; i++) { |
| |
| if(mWave_VRC6Pulse[i].bChannelEnabled) { |
| |
| mWave_VRC6Pulse[i].nFreqCount -= ticks; |
| |
| if(mWave_VRC6Pulse[i].nDutyCount <= |
| mWave_VRC6Pulse[i].nDutyCycle) |
| { |
| mWave_VRC6Pulse[i].nMixL = |
| VRC6Pulse_nOutputTable_L[mWave_VRC6Pulse[i].nVolume]; |
| } |
| else |
| mWave_VRC6Pulse[i].nMixL = 0; |
| |
| while(mWave_VRC6Pulse[i].nFreqCount <= 0) { |
| mWave_VRC6Pulse[i].nFreqCount += |
| mWave_VRC6Pulse[i].nFreqTimer.W + 1; |
| |
| if(!mWave_VRC6Pulse[i].bDigitized) |
| mWave_VRC6Pulse[i].nDutyCount = |
| (mWave_VRC6Pulse[i].nDutyCount + 1) & 0x0F; |
| } |
| } |
| } |
| |
| if(mWave_VRC6Saw.bChannelEnabled) { |
| |
| mWave_VRC6Saw.nFreqCount -= ticks; |
| |
| 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; |
| } |
| } |
| } |
| |
| /****************** 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 */ |
| |
| /* |
| * 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.nFreqTimer[ch] = -1; \ |
| break; \ |
| case r1: if(mWave_N106.nFreqReg[ch].B.h == v) break; \ |
| mWave_N106.nFreqReg[ch].B.h = v; \ |
| mWave_N106.nFreqTimer[ch] = -1; \ |
| break; \ |
| case r2: if(mWave_N106.nFreqReg[ch].B.w != (v & 3)){ \ |
| mWave_N106.nFreqReg[ch].B.w = v & 0x03; \ |
| mWave_N106.nFreqTimer[ch] = -1;} \ |
| 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.nFreqTimer[0] = -1; |
| mWave_N106.nFreqTimer[1] = -1; |
| mWave_N106.nFreqTimer[2] = -1; |
| mWave_N106.nFreqTimer[3] = -1; |
| mWave_N106.nFreqTimer[4] = -1; |
| mWave_N106.nFreqTimer[5] = -1; |
| mWave_N106.nFreqTimer[6] = -1; |
| mWave_N106.nFreqTimer[7] = -1; |
| 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; |
| int square_out1; |
| int square_out2; |
| |
| 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_out1 = mWave_Squares.nVolume[0]; |
| else |
| square_out1 = 0; |
| |
| if((mWave_Squares.nDutyCount[1] < mWave_Squares.nDutyCycle[1]) && |
| mWave_Squares.nLengthCount[1] && |
| !mWave_Squares.bSweepForceSilence[1]) |
| square_out2 = mWave_Squares.nVolume[1]; |
| else |
| square_out2 = 0; |
| |
| mWave_Squares.nMixL = Squares_nOutputTable_L[square_out1][square_out2]; |
| |
| 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) |
| Wave_VRC6_DoTicks(tick); |
| 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[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[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[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 */ |
| |
| /* |
| * 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.nFrequencyLookupTable[i] = |
| ((((i + 1) * 45 * 0x40000) / (float)NES_FREQUENCY) * |
| (float)NTSC_FREQUENCY) * 256.0; |
| |
| ZEROMEMORY(pRAM,0x800); |
| ZEROMEMORY(pSRAM,0x2000); |
| ZEROMEMORY(pExRAM,0x1000); |
| pStack = pRAM + 0x100; |
| return 1; |
| } |
| |
| /* |
| * LoadNSF |
| */ |
| |
| int LoadNSF(int32_t datasize) |
| { |
| if(!pDataBuffer) return 0; |
| |
| int32_t i; |
| |
| 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); |
| |
| 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; |
| |
| 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) |
| { |
| 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 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(j = 0; j < 0x10; j++) |
| { |
| for(i = 0; i < 0x10; i++) |
| { |
| temp = (int32_t)(l[0] * j); |
| temp += (int32_t)(l[1] * i); |
| |
| if(!temp) |
| Squares_nOutputTable_L[j][i] = 0; |
| else |
| Squares_nOutputTable_L[j][i] = 1438200 / ((2072640 / temp) + 100); |
| } |
| } |
| |
| /* VRC6 Pulse 1,2 */ |
| for(i = 0; i < 0x10; i++) |
| { |
| VRC6Pulse_nOutputTable_L[i] = |
| 1875 * i / 0x0F; |
| } |
| /* VRC6 Saw */ |
| for(i = 0; i < 0x20; i++) |
| { |
| VRC6Saw_nOutputTable_L[i] = 3750 * i / 0x1F; |
| } |
| |
| /* N106 channels */ |
| /* this amplitude is just a guess */ |
| |
| for(i = 0; i < 0x10; i++) |
| { |
| for(j = 0; j < 0x10; j++) |
| { |
| N106_nOutputTable_L[i][j] = (3000 * i * j) / 0xE1; |
| } |
| } |
| |
| /* FME-07 Square A,B,C */ |
| FME07_nOutputTable_L[15] = 3000; |
| FME07_nOutputTable_L[0] = 0; |
| for(i = 14; i > 0; i--) |
| { |
| FME07_nOutputTable_L[i] = FME07_nOutputTable_L[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. |
| */ |
| 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(bFade && (nTotalPlays >= nEndFade)) return 0; |
| |
| 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; |
| |
| 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, 16); |
| |
| ci->configure(DSP_SET_FREQUENCY, 44100); |
| ci->configure(DSP_SET_STEREO_MODE, 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); |
| |
| codec_set_replaygain(ci->id3); |
| |
| /* 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; |
| } |
| |
| ci->pcmbuf_insert(samples, NULL, written >> 1); |
| } |
| |
| 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; |
| } |