#pragma once #include "BaseMapper.h" #include "MMC5Audio.h" #include "Vrc6Audio.h" #include "Vrc7Audio.h" #include "FdsAudio.h" #include "Namco163Audio.h" #include "Sunsoft5bAudio.h" enum class NsfIrqType { Init = 0, Stop = 1, Play = 2, None = 0xFF }; class NsfMapper : public BaseMapper { private: static NsfMapper *_instance; enum NsfSoundChips { VRC6 = 0x01, VRC7 = 0x02, FDS = 0x04, MMC5 = 0x08, Namco = 0x10, Sunsoft = 0x20 }; NesModel _model; NsfHeader _nsfHeader; MMC5Audio _mmc5Audio; Vrc6Audio _vrc6Audio; Vrc7Audio _vrc7Audio; FdsAudio _fdsAudio; Namco163Audio _namcoAudio; Sunsoft5bAudio _sunsoftAudio; bool _needInit = false; bool _irqEnabled = false; uint32_t _irqReloadValue = 0; uint32_t _irqCounter = 0; NsfIrqType _irqStatus = NsfIrqType::None; uint8_t _mmc5MultiplierValues[2]; int32_t _trackEndCounter; int32_t _trackFadeCounter; int32_t _fadeLength; uint32_t _silenceDetectDelay; bool _trackEnded; bool _allowSilenceDetection; bool _hasBankSwitching; uint16_t _ntscSpeed; uint16_t _palSpeed; uint16_t _dendySpeed; uint8_t _songNumber = 0; /***************************************** * NSF BIOS by Quietust, all credits to him! * Taken from the Nintendulator source code: * http://www.qmtpro.com/~nes/nintendulator/ * See below for assembly source code ******************************************/ uint8_t _nsfBios[256] = { 0xFF,0xFF,0xFF,0x78,0xA2,0xFF,0x8E,0x17,0x40,0xE8,0x20,0x30,0x3F,0x8E,0x00,0x20, 0x8E,0x01,0x20,0x8E,0x12,0x3E,0x58,0x4C,0x17,0x3F,0x48,0x8A,0x48,0x98,0x48,0xAE, 0x12,0x3E,0xF0,0x59,0xCA,0xF0,0xDC,0x20,0xF9,0x3F,0x68,0xA8,0x68,0xAA,0x68,0x40, 0x8E,0x15,0x40,0xAD,0x13,0x3E,0x4A,0x90,0x09,0x8E,0x02,0x90,0x8E,0x02,0xA0,0x8E, 0x02,0xB0,0x4A,0x90,0x0D,0xA0,0x20,0x8C,0x10,0x90,0x8E,0x30,0x90,0xC8,0xC0,0x26, 0xD0,0xF5,0x4A,0x90,0x0B,0xA0,0x80,0x8C,0x83,0x40,0x8C,0x87,0x40,0x8C,0x89,0x40, 0x4A,0x90,0x03,0x8E,0x15,0x50,0x4A,0x90,0x08,0xCA,0x8E,0x00,0xF8,0xE8,0x8E,0x00, 0x48,0x4A,0x90,0x08,0xA0,0x07,0x8C,0x00,0xC0,0x8C,0x00,0xE0,0x60,0x20,0x30,0x3F, 0x8A,0xCA,0x9A,0x8E,0xF7,0x5F,0xCA,0x8E,0xF6,0x5F,0xA2,0x7F,0x85,0x00,0x86,0x01, 0xA8,0xA2,0x27,0x91,0x00,0xC8,0xD0,0xFB,0xCA,0x30,0x0A,0xC6,0x01,0xE0,0x07,0xD0, 0xF2,0x86,0x01,0xF0,0xEE,0xA2,0x14,0xCA,0x9D,0x00,0x40,0xD0,0xFA,0xA2,0x07,0xBD, 0x08,0x3E,0x9D,0xF8,0x5F,0xCA,0x10,0xF7,0xA0,0x0F,0x8C,0x15,0x40,0xAD,0x13,0x3E, 0x29,0x04,0xF0,0x10,0xAD,0x0E,0x3E,0xF0,0x03,0x8D,0xF6,0x5F,0xAD,0x0F,0x3E,0xF0, 0x03,0x8D,0xF7,0x5F,0xAE,0x11,0x3E,0xBD,0x04,0x3E,0x8D,0x10,0x3E,0xBD,0x06,0x3E, 0x8D,0x11,0x3E,0x8C,0x12,0x3E,0xAD,0x12,0x3E,0x58,0xAD,0x10,0x3E,0x20,0xF6,0x3F, 0x8D,0x13,0x3E,0x4C,0x17,0x3F,0x6C,0x00,0x3E,0x6C,0x02,0x3E,0x03,0x3F,0x1A,0x3F }; private: void TriggerIrq(NsfIrqType type); void ClearIrq(); bool HasBankSwitching(); uint32_t GetClockRate(); void InternalSelectTrack(uint8_t trackNumber, bool requestReset = true); void ClockLengthAndFadeCounters(); protected: uint16_t GetPRGPageSize() override { return 0x1000; } uint16_t GetCHRPageSize() override { return 0x2000; } uint32_t GetWorkRamSize() override { return 0x4000; } uint32_t GetWorkRamPageSize() override { return 0x1000; } bool AllowRegisterRead() override { return true; } void InitMapper() override; void InitMapper(RomData& romData) override; void Reset(bool softReset) override; void GetMemoryRanges(MemoryRanges &ranges) override; void ProcessCpuClock() override; uint8_t ReadRegister(uint16_t addr) override; void WriteRegister(uint16_t addr, uint8_t value) override; public: NsfMapper(); ~NsfMapper(); static NsfMapper* GetInstance(); void SetNesModel(NesModel model) override; void SelectTrack(uint8_t trackNumber); uint8_t GetCurrentTrack(); NsfHeader GetNsfHeader(); }; /* ;BIOS Source Code ;NSF BIOS by Quietust, all credits to him! ;Taken from the Nintendulator source code: ;http://www.qmtpro.com/~nes/nintendulator/ ;NSF Mapper ;R $3E00-$3E01 - INIT address ;R $3E02-$3E03 - PLAY address ;R $3E04/$3E06 - NTSC speed value ;R $3E05/$3E07 - PAL speed value ;R $3E08-$3E0F - Initial bankswitch values ;R $3E10 - Song Number (start at 0) ;R $3E11 - NTSC/PAL setting (0 for NTSC, 1 for PAL) ;W $3E10-$3E11 - IRQ counter (for PLAY) ;R $3E12 - IRQ status (0=INIT, 1=STOP, 2+=PLAY) ;W $3E12 - IRQ enable (for PLAY) ;R $3E13 - Sound chip info ;W $3E13 - clear watchdog timer .org $3F00 .db $FF,$FF,$FF ;pad to 256 bytes reset:SEI LDX #$FF STX $4017 ;kill frame IRQs INX JSR silence ;silence all sound channels STX $2000 ;we don't rely on the presence of a PPU STX $2001 ;but we don't want it getting in the way STX $3E12 ;kill PLAY timer CLI loop: JMP loop irq: PHA TXA PHA TYA PHA LDX $3E12 ;check IRQ status BEQ init DEX BEQ reset ;"Proper" way to play a tune JSR song_play ;1) Call the play address of the music at periodic intervals determined by the speed words. PLA TAY PLA TAX PLA RTI .module silence silence: ;X=0 coming in, must also be coming out STX $4015 ;silence all sound channels LDA $3E13 LSR A BCC _1 STX $9002 STX $A002 STX $B002 ;stop VRC6 _1: LSR A BCC _2 LDY #$20 __2: STY $9010 STX $9030 ;stop VRC7 INY CPY #$26 BNE __2 _2: LSR A BCC _3 LDY #$80 STY $4083 STY $4087 STY $4089 ;stop FDS _3: LSR A BCC _4 STX $5015 ;stop MMC5 _4: LSR A BCC _5 DEX STX $F800 INX STX $4800 ;stop Namco-163 _5: LSR A BCC _6 LDY #$07 STY $C000 STY $E000 ;stop SUN5 _6: RTS .module init ;"Proper" way to init a tune init: JSR silence TXA ;(X=0) DEX TXS ;clear the stack STX $5FF7 ;1.5) Map RAM to $6000-$7FFF DEX STX $5FF6 ;(banks FE/FF are treated as RAM) LDX #$7F STA $00 STX $01 TAY LDX #$27 _1: STA ($00),Y ;1) Clear all RAM at $0000-$07FF. INY ;2) Clear all RAM at $6000-$7FFF. BNE _1 DEX BMI _2 DEC $01 CPX #$07 BNE _1 STX $01 BEQ _1 _2: LDX #$14 _3: DEX STA $4000,X ;3) Init the sound registers by writing $00 to $4000-$4013 BNE _3 LDX #$07 _4: LDA $3E08,X ;5) If this is a banked tune, load the bank values from the header into $5FF8-$5FFF. STA $5FF8,X ;For this player, *all* tunes are considered banked (the loader will fill in appropriate values) DEX BPL _4 LDY #$0F ;4) Set volume register $4015 to $0F. STY $4015 LDA $3E13 ;For FDS games, banks E/F also get mapped to 6/7 AND #$04 ;For non-FDS games, we don't want to do this BEQ _6 LDA $3E0E ;if these are zero, let's leave them as plain RAM BEQ _5 STA $5FF6 _5: LDA $3E0F ;TODO - need to allow FDS NSF data to be writeable BEQ _6 STA $5FF7 _6: LDX $3E11 ;4.5) Set up the PLAY timer. Which word to use is determined by which mode you are in - PAL or NTSC. LDA $3E04,X ;X is 0 for NTSC and 1 for PAL, as needed for #6 below STA $3E10 LDA $3E06,X STA $3E11 STY $3E12 LDA $3E12 ;if we have a pending IRQ here, we need to cancel it NOW CLI ;re-enable interrupts now - this way, we can allow the init routine to not return (ex: for raw PCM) LDA $3E10 ;6) Set the accumulator and X registers for the desired song. JSR song_init ;7) Call the music init routine. STA $3E13 ;kill the watchdog timer (and fire a 'Play' interrupt after 1 frame; otherwise, it'll wait 4 frames) JMP loop ;we don't actually RTI from here; this way, the init code is faster .module song song_init: JMP ($3E00) song_play: JMP ($3E02) .dw reset,irq ;no NMI vector .end */