NSF2: Difference between revisions
Rainwarrior (talk | contribs) m (cats) |
(Imported spec from http://forums.nesdev.com/viewtopic.php?f=6&t=7304 ; hopefully someone can make it readable) |
||
Line 5: | Line 5: | ||
It is currently an unfinished proposal. Currently Nintendulator partially supports it. See link below for reference. | It is currently an unfinished proposal. Currently Nintendulator partially supports it. See link below for reference. | ||
<pre> | |||
The goals are: | |||
* IRQ supprt | |||
* "no return" init addresses | |||
* information block | |||
For IRQ support, I figured allowing the use of frame IRQs and DPCM IRQs just like on the NES, and then a 16 bit IRQ timer which would be connected to the CPU in the usual way (via its IRQ input). | |||
This would allow a real NES to play 2.0 NSFs using say a powerpak or similar. | |||
So my proposal for IRQ hardware: | |||
* a 16 bit IRQ timer that sits at 0x4018-0x401a | |||
* 4018 = lower 8 bits of the 16 bit timer reload value | |||
* 4019 = upper 8 bits of the 16 bit timer reload value | |||
* 401a = control register. | |||
* bit 0 - 0 = timer off, reset. 1 = timer is running | |||
* all registers are readable and writable. on a real NES, these | |||
addresses are fully readable/writable to the cartridge. | |||
* allowing the use of DPCM and frame IRQs. | |||
* these will work like on a real NES. you MUST write to 0x4017 | |||
to enable the external (timer) IRQ, and to reset the frame IRQ, | |||
just like a regular NES. | |||
* IRQ vector. | |||
* write the vector to FFFE and FFFF. These two locations hold the | |||
IRQ vector like usual, but are writable. When read by the CPU, | |||
these two locations must return the two bytes written there. | |||
* when written, you DO NOT write to the underlying NSF data bank. | |||
* in effect, FFFE/FFFF become two bytes of RAM which are separate | |||
from the rest of NSF space. | |||
And the proposal for no return on the init address: | |||
* Allow for the init address to never return. | |||
this basically means: | |||
* init becomes the reset vector | |||
* play becomes the NMI vector | |||
* and IRQ has its vector at FFFE/FFFF | |||
Proposed header changes: | |||
0005 1 BYTE Version number (currently 01h) | |||
this will be bumped to 02h for version 2.0 | |||
007c 4 ---- 4 extra bytes for expansion (must be 00h) | |||
these will be used as follows: | |||
007c 1 BYTE NSF 2.0 feature enables | |||
bit: | |||
0 - when set, enables the IRQ features. when clear, disables them | |||
1 - when set, allows for a non-returning init address. | |||
2 - when set, allows play calling to be disabled | |||
3-6 - maintain 0 | |||
7 - an extended info block follows nsf data. (see below) | |||
007d 3 WORD length of NSF data block, in bytes. LSB first (little endian) | |||
Extended data block: | |||
* an extended block of data that is optional to include. | |||
* it has the following features: | |||
* stores a unicode? title up to N characters long | |||
* stores the same for copyright, author, and ripper | |||
* allow for separate author/copyright/title on each track? | |||
* lengths of tracks | |||
* any other possible ancillary data? | |||
* the reason for placing it at the end, is so that 1.0 players can still | |||
use these NSFs. they will append the extra data into NSF space, and | |||
it should not affect the playback if it doesn't use any of the other | |||
features (IRQs, non-return init addresses) | |||
* You MUST still populate the original author/copyright/title strings | |||
in the original header for backwards compatibility. | |||
So that's basically it. I think it adds all the features that can be added and still function properly on an NES with a powerpak or similar player cartridge. | |||
I'm open to suggestions or feedback on it. If people like it, I will formalize it and update the existing NSF document, and modify my FPGA synth to conform to the document for testing. | |||
</pre> | |||
== IRQ timer == | |||
The timer would decrement at the CPU clock rate, whether it be NTSC or PAL rate (so 1.79MHz or 1.66MHz or so). | |||
The counter is a modulus N counter and has the following behaviour: | |||
When the counter is off (whenever 401a bit 0 is clear) it is constantly being reloaded with the values in 4018/4019, and the counter IRQ flag is cleared. | |||
When the counter is on (401a bit 0 is set), it will decrement once per CPU cycle. When it hits 0, it is reloaded from 4018/4019, the IRQ flag is set and an IRQ is asserted. | |||
This means an IRQ will be generated every N+1 clock cycles, where N is the value loaded into the counter. (it is N+1 because 0 is counted too). | |||
To clear the IRQ flag, you read 401a. I should probably put the IRQ flag at bit 7 (read only) to allow easy testing of IRQ source. | |||
Code might look like this: | |||
<pre> | |||
timervalue: .equ 01fffh ;desired # of cpu cycles minus 1 | |||
starttimer: LDA #000h | |||
STA 0401ah ;reset and shut off timer in case it was on | |||
LDA #<irqvector | |||
STA 0fffeh ;store interrupt vector low of our handler | |||
LDA #>irqvector | |||
STA 0ffffh ;store interrupt vector high | |||
LDA #<timervalue | |||
STA 04018h ;low byte of timer value | |||
LDA #>timervalue | |||
STA 04019h ;high byte of timer value | |||
LDA #0c0h | |||
STA 04017h ;turn off frame IRQs | |||
LDA 04015h ;ack any pending DPCM IRQ if, it exists | |||
CLI ;enable IRQs | |||
LDA #01h | |||
STA 0401ah ;turn the timer on | |||
RTS | |||
stoptimer: SEI ;turn off IRQs | |||
LDA #000h | |||
STA 0401ah ;turn timer off | |||
RTS | |||
irqvector: <perform our interrupt code here> | |||
.... | |||
.... | |||
LDA 0401ah ;reading 401a resets IRQ flag | |||
RTI ;return from interrupt | |||
alternatively, if you wish to determine WHAT caused the IRQ (if you're using more than one source) you'd do something like this... | |||
irqvector: BIT 0401ah ;bit 7 indicates we have a timer IRQ waiting | |||
BPL + | |||
JSR timer ;if bit 7 was set, call timer subroutine | |||
+ BIT 04015h | |||
BPL + | |||
PHP ;save flags if we have DPCM int. | |||
JSR dpcm ;if bit 7 was set, call DPCM sub | |||
PLP | |||
+ BVC + | |||
JSR frame ;bit 6 of 4015 = frame IRQ | |||
+ RTI ;exit interrupt | |||
timer: <do stuff here> | |||
RTS | |||
dpcm: <do stuff here> | |||
RTS | |||
frame: <do stuff here> | |||
RTS | |||
</pre> | |||
There's a lot of ways to skin this cat, but this is one particular method. The idea is to read the status regs to figure out which source caused the interrupt, then run code to service it, then go back and check the other sources just in case one of them also needs servicing. | |||
Like the APU period timers and the MMC3 scanline timer, the NSF2 cycle timer includes both the 0 and the reload value in its sequence of states. | |||
When it hits 0, the NEXT clock performs a reload. | |||
For example, when the reload value is $0010, the counter would have a period of 17 CPU cycles: | |||
10, 10, 10, [Enable now] 0F, 0E, 0D, 0C, ..., 02, 01, 00, 10*, 0F, 0E, 0D, 0C, ... | |||
where * denotes the IRQ flag becoming true. | |||
== Track information == | |||
<pre> | |||
my approach would be to have records that contain track # and then information fields... something like this: | |||
record: | |||
offset, # of bytes, type, description | |||
---------------------------------------- | |||
0 1 BYTE record type | |||
1 2 WORD record length | |||
3 1 BYTE track # | |||
4 N --- record data | |||
Records would be one after another, and a record of 4 00h bytes would signify the end of the data. | |||
the type would be something like: | |||
0 - last record | |||
1 - title | |||
2 - composer | |||
3 - copyright | |||
etc. | |||
Track # 0ffh could be reserved, and used as a wildcard indicator, | |||
allowing for things like this: | |||
composer, track 0ffh, jimbob | |||
title, track 0, my first song | |||
title, track 1, my second song | |||
title, track 2, a song by someone else | |||
composer, track 2, billy | |||
title, track 3, another song | |||
this would populate all the "composer" entries on the tracks which do not have a specific composer (i.e. everything but track 2 in the above example). | |||
This would allow removal of most duplicated material and allow for individual track fields to be populated with different info if required. | |||
Also, it'd be pretty trivial to process this type of data on a real NES or other hardware player. | |||
Possible fields could be (with tentative ID's): | |||
01 Title followed by ASCII or unicode? data. A max length should be specified. | |||
02 Composer (Same as above) | |||
03 Copyright (Same as above) | |||
04 Ripper (Same as above) | |||
05 length (in seconds? milliseconds? NMI counts?) | |||
06 type (maybe? i.e. SFX, BGM, title tune, etc) | |||
07 ancillary data (i.e. compo entry #? "this is a cover of xyz") | |||
Any other fields that would be useful? As usual, all fields are optional, and you only use the ones you need/want. | |||
This would make it pretty easy to read via a real NES, vs. some feel-good text format. As such, it'd probably be a decent idea to only allow ASCII in the fields since an NES cannot read unicode too easily. (Also, a simple converter could be written to "compile" a desired text format into the binary format for stuffing onto the end of the NSF.) | |||
As for a pure text format, I ran into this issue with .SAP files. I had to write a somewhat annoying complicated parser for these files, because they have a human-generated header with the binary data just appended on. Because of this, you have to account for all sorts of weird cases; tabs, spaces, CRLF, LF only, etc. | |||
It's kind of a nightmare to handle in 6502 asm. | |||
</pre> | |||
The data would be encoded in UTF-8, with a warning if the player cannot display a particular character. | |||
== Reference == | == Reference == | ||
* [http://forums.nesdev.org/viewtopic.php?f=6&t=7304 NSF 2.0 forum discussion] | * [http://forums.nesdev.org/viewtopic.php?f=6&t=7304 NSF 2.0 forum discussion] |
Revision as of 01:46, 1 February 2016
NSF 2 is a proposed extension to the NSF file format. It is intended to be backward compatible with the original NSF, provide IRQ timer functionality, and allow track times and other metadata to be contained.
It is currently an unfinished proposal. Currently Nintendulator partially supports it. See link below for reference.
The goals are: * IRQ supprt * "no return" init addresses * information block For IRQ support, I figured allowing the use of frame IRQs and DPCM IRQs just like on the NES, and then a 16 bit IRQ timer which would be connected to the CPU in the usual way (via its IRQ input). This would allow a real NES to play 2.0 NSFs using say a powerpak or similar. So my proposal for IRQ hardware: * a 16 bit IRQ timer that sits at 0x4018-0x401a * 4018 = lower 8 bits of the 16 bit timer reload value * 4019 = upper 8 bits of the 16 bit timer reload value * 401a = control register. * bit 0 - 0 = timer off, reset. 1 = timer is running * all registers are readable and writable. on a real NES, these addresses are fully readable/writable to the cartridge. * allowing the use of DPCM and frame IRQs. * these will work like on a real NES. you MUST write to 0x4017 to enable the external (timer) IRQ, and to reset the frame IRQ, just like a regular NES. * IRQ vector. * write the vector to FFFE and FFFF. These two locations hold the IRQ vector like usual, but are writable. When read by the CPU, these two locations must return the two bytes written there. * when written, you DO NOT write to the underlying NSF data bank. * in effect, FFFE/FFFF become two bytes of RAM which are separate from the rest of NSF space. And the proposal for no return on the init address: * Allow for the init address to never return. this basically means: * init becomes the reset vector * play becomes the NMI vector * and IRQ has its vector at FFFE/FFFF Proposed header changes: 0005 1 BYTE Version number (currently 01h) this will be bumped to 02h for version 2.0 007c 4 ---- 4 extra bytes for expansion (must be 00h) these will be used as follows: 007c 1 BYTE NSF 2.0 feature enables bit: 0 - when set, enables the IRQ features. when clear, disables them 1 - when set, allows for a non-returning init address. 2 - when set, allows play calling to be disabled 3-6 - maintain 0 7 - an extended info block follows nsf data. (see below) 007d 3 WORD length of NSF data block, in bytes. LSB first (little endian) Extended data block: * an extended block of data that is optional to include. * it has the following features: * stores a unicode? title up to N characters long * stores the same for copyright, author, and ripper * allow for separate author/copyright/title on each track? * lengths of tracks * any other possible ancillary data? * the reason for placing it at the end, is so that 1.0 players can still use these NSFs. they will append the extra data into NSF space, and it should not affect the playback if it doesn't use any of the other features (IRQs, non-return init addresses) * You MUST still populate the original author/copyright/title strings in the original header for backwards compatibility. So that's basically it. I think it adds all the features that can be added and still function properly on an NES with a powerpak or similar player cartridge. I'm open to suggestions or feedback on it. If people like it, I will formalize it and update the existing NSF document, and modify my FPGA synth to conform to the document for testing.
IRQ timer
The timer would decrement at the CPU clock rate, whether it be NTSC or PAL rate (so 1.79MHz or 1.66MHz or so).
The counter is a modulus N counter and has the following behaviour:
When the counter is off (whenever 401a bit 0 is clear) it is constantly being reloaded with the values in 4018/4019, and the counter IRQ flag is cleared.
When the counter is on (401a bit 0 is set), it will decrement once per CPU cycle. When it hits 0, it is reloaded from 4018/4019, the IRQ flag is set and an IRQ is asserted.
This means an IRQ will be generated every N+1 clock cycles, where N is the value loaded into the counter. (it is N+1 because 0 is counted too).
To clear the IRQ flag, you read 401a. I should probably put the IRQ flag at bit 7 (read only) to allow easy testing of IRQ source.
Code might look like this:
timervalue: .equ 01fffh ;desired # of cpu cycles minus 1 starttimer: LDA #000h STA 0401ah ;reset and shut off timer in case it was on LDA #<irqvector STA 0fffeh ;store interrupt vector low of our handler LDA #>irqvector STA 0ffffh ;store interrupt vector high LDA #<timervalue STA 04018h ;low byte of timer value LDA #>timervalue STA 04019h ;high byte of timer value LDA #0c0h STA 04017h ;turn off frame IRQs LDA 04015h ;ack any pending DPCM IRQ if, it exists CLI ;enable IRQs LDA #01h STA 0401ah ;turn the timer on RTS stoptimer: SEI ;turn off IRQs LDA #000h STA 0401ah ;turn timer off RTS irqvector: <perform our interrupt code here> .... .... LDA 0401ah ;reading 401a resets IRQ flag RTI ;return from interrupt alternatively, if you wish to determine WHAT caused the IRQ (if you're using more than one source) you'd do something like this... irqvector: BIT 0401ah ;bit 7 indicates we have a timer IRQ waiting BPL + JSR timer ;if bit 7 was set, call timer subroutine + BIT 04015h BPL + PHP ;save flags if we have DPCM int. JSR dpcm ;if bit 7 was set, call DPCM sub PLP + BVC + JSR frame ;bit 6 of 4015 = frame IRQ + RTI ;exit interrupt timer: <do stuff here> RTS dpcm: <do stuff here> RTS frame: <do stuff here> RTS
There's a lot of ways to skin this cat, but this is one particular method. The idea is to read the status regs to figure out which source caused the interrupt, then run code to service it, then go back and check the other sources just in case one of them also needs servicing.
Like the APU period timers and the MMC3 scanline timer, the NSF2 cycle timer includes both the 0 and the reload value in its sequence of states. When it hits 0, the NEXT clock performs a reload.
For example, when the reload value is $0010, the counter would have a period of 17 CPU cycles:
10, 10, 10, [Enable now] 0F, 0E, 0D, 0C, ..., 02, 01, 00, 10*, 0F, 0E, 0D, 0C, ...
where * denotes the IRQ flag becoming true.
Track information
my approach would be to have records that contain track # and then information fields... something like this: record: offset, # of bytes, type, description ---------------------------------------- 0 1 BYTE record type 1 2 WORD record length 3 1 BYTE track # 4 N --- record data Records would be one after another, and a record of 4 00h bytes would signify the end of the data. the type would be something like: 0 - last record 1 - title 2 - composer 3 - copyright etc. Track # 0ffh could be reserved, and used as a wildcard indicator, allowing for things like this: composer, track 0ffh, jimbob title, track 0, my first song title, track 1, my second song title, track 2, a song by someone else composer, track 2, billy title, track 3, another song this would populate all the "composer" entries on the tracks which do not have a specific composer (i.e. everything but track 2 in the above example). This would allow removal of most duplicated material and allow for individual track fields to be populated with different info if required. Also, it'd be pretty trivial to process this type of data on a real NES or other hardware player. Possible fields could be (with tentative ID's): 01 Title followed by ASCII or unicode? data. A max length should be specified. 02 Composer (Same as above) 03 Copyright (Same as above) 04 Ripper (Same as above) 05 length (in seconds? milliseconds? NMI counts?) 06 type (maybe? i.e. SFX, BGM, title tune, etc) 07 ancillary data (i.e. compo entry #? "this is a cover of xyz") Any other fields that would be useful? As usual, all fields are optional, and you only use the ones you need/want. This would make it pretty easy to read via a real NES, vs. some feel-good text format. As such, it'd probably be a decent idea to only allow ASCII in the fields since an NES cannot read unicode too easily. (Also, a simple converter could be written to "compile" a desired text format into the binary format for stuffing onto the end of the NSF.) As for a pure text format, I ran into this issue with .SAP files. I had to write a somewhat annoying complicated parser for these files, because they have a human-generated header with the binary data just appended on. Because of this, you have to account for all sorts of weird cases; tabs, spaces, CRLF, LF only, etc. It's kind of a nightmare to handle in 6502 asm.
The data would be encoded in UTF-8, with a warning if the player cannot display a particular character.