divider
8bitpeoples.com


Nullsleep on Twitter Nullsleep on Facebook
////////////////////////////////////////////////////////
//// NSF PLAYBACK CARTRIDGE GUIDE //////////////////////
//// by nullsleep //////////////////////////////////////
//// version 1.0 /////////// product of 8bitpeoples ////
//////////////////////////// Research & Development ////
////////////////////////////////////////////////////////
Download NSF Playback Cartridge Guide v1.0 (.TXT)
*******************************************************************************
** OBJECTIVE ******************************************************************
*******************************************************************************

This document is aimed at providing all the information necessary to playback 
NSF files on an actual NES, rather than through the use of an emulator. First, 
the assembly code for a simple NSF player engine will be covered and explained.
Following this, there will be an overview outlining the light hardware hacking 
aspect involved. Specifically, the modification of an existing NES cartridge 
in order to accomodate the NSF playback code.

Thanks: Bananmos, Memblers, Andy Gunn, Cory Arcangel, Brad Taylor, Kevin Horton


*******************************************************************************
** SOFTWARE: NSF PLAYBACK CODE ************************************************
*******************************************************************************

The code below was written for x816 assembler, which can be found with a quick 
web search or simply by going to nesdev.parodius.com and downloading it there. 
If you are more comfortable using a different assembler, you should not have 
much trouble converting the code over to fit its requirements.

This first group of settings is required by x816 assembler:

; *** X816 SETTINGS ***
	.mem 8			; 8-bit memory mode
	.index 8		; 8-bit index mode
	.opt on			; address optimize

Next the code is initialized to start at the load address MINUS $80. The load 
address can be found in the NSF header at offset 0008 and is 2 bytes long. The 
reason why $80 is subtracted from the load address is because the NSF header 
itself is 128 (or $80 in hex) bytes long. If $80 was not subtracted the music 
data would actually be loaded at an address 128 bytes after the correct load 
address. So for example, if the load address specified at offset 0008 in the 
NSF header was 8080, then it would be appropriate to .org $8000 thereby putting 
the actual music data at $8080. Once the code is initialized to start at the 
correct address, simply include the NSF file. Note that since x816 is a DOS 
program, the filename for the NSF should be a maximum of 8 characters (not 
including the file extension).

	.org $----		; replace dashes with load address MINUS $80
	.incbin "chip.nsf"	; include NSF tune 

Initializing the NES is the next step, this portion of code defines what will 
happen when the NES is reset and is simply standard practice. The decimal flag 
is cleared (cld) and interrupts are disabled (sei).  The next two instructions 
disable vblank interrupts, specifically, the most significant bit of $2000 is 
used to either enable(1) or disable(0) these interrupts. Address $2000 is the 
first of two PPU (picture processing unit) control registers, mostly used when 
dealing with graphics, however its being used here since vblank interrupts will 
be providingthe correct timing for our music playback.

Reset_Routine:
	cld			; clear decimal flag
	sei			; disable interrupts
	lda #%00000000		; disable vblank interrupts by clearing
	sta $2000		; the most significant bit of $2000

The next few lines of code give the PPU (pixel processing unit) of the NES some
time to initialize. Although this code segment may not be required when using 
an emulator, it is integral to getting the replay routine running correctly on 
the real hardware. Additionally, if graphics are to be displayed, waiting for 
vblank becomes even more important. This is because vblank is the period of 
time in between frames, and this is the only time during which graphics should 
be loaded to be displayed. Without this precaution, graphics on the screen may 
become corrupted.

; *** WAIT 2 VBLANKS ***
WaitV1:	
	lda $2002		; give the PPU a little time to initialize
	bpl WaitV1		; by waiting for a vblank
WaitV2:	
	lda $2002		; wait for a second vblank to be safe
	bpl WaitV2		; and now the PPU should be initialized

The following chunk of code is responsible for clearing all the sound registers
of the NES. Generally, this may not actually be necessary because it should 
already be taken care of by the code in the NSF itself. However, it cannot 
really do any harm, so look at it as good practice. Registers $4000-$4003 are 
associated with the first pulse wave channel, $4004-$4007 with the second pulse
wave channel, $4008-$400B with the triangle wave channel, and $400C-$400F with 
the noise channel. The Clear_Sound loop below simply steps through each of 
these registers and sets them to $00. First the accumulator and x are loaded 
with $00, then the accumulator value ($00) is stored in address $4000 at the 
offset of the x value (currently $00), then inx increments x, and cpx #$0F 
compares the x value to the value $0F, finally the bne statement branches back 
to the Clear_Sound label if these values are not equal. Once $4000-$400F have 
been cleared program execution continues downward. The remaining registers from
$4010 to $4013 are all associated with the delta modulation channel (DMC) and 
are initialized according to values found in the NSF specification by Kevin 
Horton. Check Brad Taylor's DMC documentation for details on what each of these
registers control.

; *** CLEAR SOUND REGISTERS ***
	lda #$00		; clear all the sound registers by setting
	ldx #$00		; everything to 0 in the Clear_Sound loop
Clear_Sound:
	sta $4000,x		; store accumulator at $4000 offset by x
	inx			; increment x
	cpx #$0F		; compare x to $0F
	bne Clear_Sound		; branch back to Clear_Sound if x != $0F

	lda #$10		; load accumulator with $10
	sta $4010		; store accumulator in $4010
	lda #$00		; load accumulator with 0
	sta $4011		; clear these 3 registers that are 
	sta $4012		; associated with the delta modulation
	sta $4013		; channel of the NES

The following instructions are responsible for enabling the sound channels of 
the NES. Address $4015 acts as a channel enable register when being written to,
with the bit 0 corresponding to the first square channel, bit 1 the next square
channel, bit 2 the triangle wave channel, bit 3 the noise channel, and bit 4 is
used to control DMC operation. The remaining bits 5 through 7 are unused (in 
terms of $4015 as a write register). So, setting the 4 least significant bits 
to 1, thereby enables all sound channels of the NES except the delta modulation
channel. The DMC is disabled to avoid the possibility of sample playback before
the song begins playing.

; *** ENABLE SOUND CHANNELS ***
	lda #%00001111		; enable all sound channels except
	sta $4015		; the delta modulation channel

Anytime a write to $4017 occurs, the frame counter and clock divider are reset.
The following lines of code are necessary for synchronizing the sound playback 
routine to the internal timing of the NES sound hardware.

; *** RESET FRAME COUNTER AND CLOCK DIVIDER ***
	lda #$C0		; synchronize the sound playback routine 
	sta $4017		; to the internal timing of the NES

The next three lines of code carry out some of the final steps of the playback 
setup. First, the accumulator is loaded with the song number to be played back.
The numbering starts at 00, so for a single song NSF file, this first line 
should be set to lda #$00. Next set x to $00 for NTSC or $01 for PAL. Finally, 
jsr to the init address. This is found at offset 000A in the NSF header and is 
2 bytes long. Keep in mind that because it is 2 bytes, when looking at it in a 
hex editor, the low byte will be followed by the high byte. So for example, if 
the 2 bytes at offset 000A in the hex editor are 16 8A, the 16-bit address that 
this represents for jsr to jump to is $8A16.

; *** SET SONG # & PAL/NTSC SETTING ***
	lda #$--		; replace dashes with song number
	ldx #$--		; replace with $00 for NTSC or $01 for PAL
	jsr $----		; replace dashes with init address

Next, vblank interrupts are enabled by setting the most significant bit of 
address $2000 and program execution drops into an infinite jmp loop. The rest 
of the program code will be located in the NMI routine.

; *** ENABLE VBLANK NMI ***
	lda #%10000000		; enable vblank interrupts by setting the 
	sta $2000		; most significant bit of $2000

Loop:
	jmp Loop		; loop loop loop loop ...

The NMI routine is where program execution jumps to whenever a non-maskable 
interrupt occurs. By enabling the vblank interrupts above, this routine will be
called at either 60Hz (NTSC) or 50Hz (PAL), making it useful for maintaining 
the correct timing in playing back the NSF. First $2002 is read, this is the 
PPU status register, and reading it resets the vblank flag. Next $2000, the 
first PPU control register is cleared, and then the most significant bit of it 
is again set to enable vblank interrupts. Now the NSF playback is achieved by
setting jsr to jump to the play address found at offset 000C in the NSF header. 
Remember that, like the load and init addresses, this is a 16-bit address and 
so, when looking at it in the hex editor the low byte will be followed by the 
high byte. Finding a value of 84 80 in the editor means jsr should point to 
$8084. Finally, rti returns from the interrupt routine.

NMI_Routine:
	lda $2002		; read $2002 to reset the vblank flag
	lda #%00000000		; clear the first PPU control register  
	sta $2000		; writing 0 to it
	lda #%10000000		; reenable vblank interrupts by setting
	sta $2000		; the most significant bit of $2000
	jsr $----		; replace dashes with play address
	rti			; return from interrupt routine

The remainder of the code is simply necessary for all NES programs. The IRQ 
routine contains a single statement, rti, because it is not necessary to have 
anything occur during an IRQ since we are using the NMI routine for playback. 
Finally, we pad the code out to location $FFFA and setup the interrupt vectors.

IRQ_Routine:
	rti			; return from interrupt routine

.pad $FFFA
	.dw	NMI_Routine	; setup the NMI vector at $FFFA
	.dw	Reset_Routine	; setup the Reset vector at $FFFC
	.dw	IRQ_Routine	; setup the IRQ vector at $FFFE

That finishes the software section of this NSF playback guide. While the amount
of information above might be overwhelming if you have never done any assembly 
programming (or NES programming) before, it is really not very complex code at 
all. If you are having trouble understanding certain parts of it, grab some 
additional documents from nesdev.parodius.com and give it another read. I hope 
that this guide gives a nice start for some beginners but keep in mind that the
real fun in NES development is the joy of discovering things for yourself.


*******************************************************************************
** HARDWARE: MODIFYING THE CARTRIDGE ******************************************
*******************************************************************************

The hardware modification aspect of creating the NSF playback cartridge is very
straightforward. However, it does require a bit of equipment that the average 
person may not just have laying around. The required equipment is as follows:

NES game cartridge (mapper 0)
Screwdriver (may require special Nintendo screw bit)
Small Cutters (for clipping the pins on the PRG ROM)
Soldering Iron & Solder
De-Soldering Braid (or other implement for De-Soldering)
28-pin EPROM socket (low-profile ZIF socket recommended)
28-pin EPROM (32k or larger recommended)
28-pin EPROM programmer
EPROM eraser (you will probably want one of these too)

First off, the NES cartridge you modify should be a mapper 0 cartridge. This is
basically the simplest cartridge format, it contains no memory mapping hardware
and is essentially just 2 ROM chips stuck to a board. The game cartridge that 
I would recommend is Super Mario Bros. (NOT the Super Mario Bros./Duck Hunt
dual game cartridges) since it tends to be easy to find and available cheap. 
Some other possibilities are Arkanoid, Elevator Action, Excitebike, Ice Hockey, 
and Joust among others. The rest of the equipment on the list should be readily
available from most electronics component suppliers.

1. Open the Cartridge
Begin by opening up the game cartridge. Inside there will be a small circuit 
board, much smaller than the game casing itself. It should look something like 
what you see below:

                ________________________________________
               |                                        |
              |' __                                     '|
             |' |  |  |``````````````|  |``````````````| |
             |  |  |  |   CHR ROM    |  |   PRG ROM    | |
             |  |  |  |______________|  |______________| |
             |  '--'   ``````````````    ``````````````  |
             |_                                         _|
              |                                         |
              |||||||||||||||||||||||||||||||||||||||||||


2. Remove the PRG ROM
The CHR ROM on the left contains graphics data and the PRG ROM on the right 
contains all the program code. It is the PRG ROM that will need to be removed 
and replaced with an EPROM. Begin by clipping all the pins of the PRG ROM and 
removing the chip. Next use the de-soldering braid and the soldering iron to 
soak up the solder around the remaining lengths of pin sticking through the 
board. Once most of the solder around the pins has been removed, it should be 
possible to push/pull/knock out the pins so there are holes in the board that 
are clear and ready to receive the socket.

3. Solder the Socket
Place the pins of the socket into the place left by the PRG ROM. Next, solder 
the socket into place. This is basically the extent of modification necessary 
to the cartridge. Mapper 0 cartridges do not require any pin reconfigurations, 
since the mapping for the removed ROM and replacement EPROM is identical.

4. Write Data to the EPROM
The final step is to use the EPROM programmer to first blank check the EPROM 
and then write to it with the compiled program code. If everything was done 
properly, the EPROM can then be placed into the socket, the cartridge inserted 
into the NES, and the lovely sounds of the NSF will fill the air!

- EOF -------------------------------------------------------------------------

../love_always/nullsleep
../www.8bitpeoples.com
../www.nullsleep.com
../www.2A03.org

If you find any errors in this document or have any suggestions or requests for
additions, please contact me: nullsleep@8bitpeoples.com

Related Links:
http://nesdev.parodius.com			; a wealth of NES docs
http://www.2A03.org				; NES scene music archive
http://research.8bitpeoples.com 		; NES and other 8bit love
http://www.vorc.org				; relevant vgm & chiptune news