This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

NSF player in an emulator

NSF player in an emulator
by on (#41319)
- I'm writing a NSF player in 6502, but since it's my first time inside programming, I don't know about polling the joypad buttons state. I saw the joypad code from CrashTest, but no clues about detecting button presses. Example... left/right to change the track, Start to (re)play the track, Select to stop.

- Plus, any problem of putting my code at CPU $4020? I don't know exactly about bankswitched NSFs, the wiki page looks unclear to me. Anyway, is the procedure of loading/initing/playing a tune the same when the code runs on an emulator?

by on (#41329)
A key has been pressed if it is down this frame but not down last frame. So you need to store information on what keys were pressed last frame.

The controller reading code in recent versions of Tetramino (src/pads.s) shows how to handle this for both controllers and work around the DPCM glitch.

by on (#41337)
I can't figure some of your syntax. You use "lda 1", at same time when "lda #1" or "lda #$1" appear too. Could you clarify it? ^_^;;

by on (#41338)
sounds like standard 6502 to me

with a # symbol is immediate mode
without a # symbol is absolute (or zero page) mode.

"lda 1" is lda zero page, from address 1 ($0001)

by on (#41339)
- Here's a good example of what I mean.
Code:
read_pads_once:
  lda #1         ; ** value 0x01
  sta 0          ; write to RAM[0x00]
  sta 1
  sta JOY1
  lda #0
  sta JOY1
  loop:
    lda JOY1
    and #$03     ; ** value 0x03, but the syntax has changed here.
    cmp #1
    rol 0
    lda JOY2
    and #$03
    cmp #1
    rol 1
    bcc loop
  rts


- Looks like an hybrid syntax case, not uniform... but it's drifting off-topic.

EDIT- Oh, unless you're using the $ symbol only for hexadecimal numbers? Like, lda 1 is "1" decimal; lda $1 is "1" hexadecimal..? :)

by on (#41342)
Yeah, always use hex, never decimal. Exept maybe for litteral constants, such as in "lda #134" where it may piss you off to convert the decimal 134 into hex, but I still do it 90% of the time so that I have an uniform syntax.

by on (#41343)
My 6502 coding style uses the first 16 bytes of zero page as local variables. But you're right that I ought to add more comments to the next version.

EDIT: Is this any clearer?
Code:
JOY1      = $4016
JOY2      = $4017

; The NES has two wired game controllers, each with eight buttons.
; This subroutine reads both controllers and puts the current state
; in cur_keys[]. It also puts newly pressed keys (down this frame,
; up last frame) in new_keys[].
;
; On the NES, standard plug-in controllers show up on D0.
; But on the Famicom, the hardwired controllers show up on D0,
; and plug-in controllers show up on D1.  This function considers
; a button to be pressed if it's pressed on D0 or D1 or both.
;
; The NES CPU's I/O has a glitch.  If the DPCM playback hardware
; fetches a sample at the exact time that the program is reading
; the controller, the serial port sends an extra clock, which causes
; the program to miss one button.  But if the buttons read out the
; same way both times, this means the DPCM glitch didn't happen,
; so it's OK to update the current keys.  Worst case, we'll get
; an occasional 1-frame control lag while a key is being held.
; And unlike commercial games that loop until the buttons match
; on two successive reads, this method takes near constant time.

.export read_pads
.importzp cur_keys, new_keys
.proc read_pads

  ; Uses $0000-$0005 for local variables
  raw_keys = 0
  first_read_keys = 2
  last_keys = 4

  ; Store the current keypress state to detect key-down later
  lda cur_keys
  sta last_keys
  lda cur_keys+1
  sta last_keys+1

  ; Read the controllers twice
  jsr read_pads_once
  lda raw_keys
  sta first_read_keys
  lda raw_keys+1
  sta first_read_keys+1
  jsr read_pads_once

  ldx #1  ; For each player (1 then 0):
@fixupKeys:

    ; Compare the two reads and update only if they match
    lda raw_keys,x
    cmp first_read_keys,x
    bne @dontUpdateGlitch
      sta cur_keys,x
    @dontUpdateGlitch:

    ; Calculate newly pressed keys
    lda last_keys,x   ; A = keys that were down last frame
    eor #$FF  ; A = keys that were up last frame
    and cur_keys,x  ; A = keys down now and up last frame
    sta new_keys,x
    dex
    bpl @fixupKeys
  rts

read_pads_once:

  ; A combination data register and loop counter.
  ; When 1 is shifted left eight times, the carry will go from 0 to 1,
  ; and we're done with the loop.
  lda #$01
  sta raw_keys
  sta raw_keys+1

  ; To tell both controllers to start sending bits, send a 1
  ; then a 0 on JOY1.
  sta JOY1
  lda #$00
  sta JOY1
  loop:
    lda JOY1  ; A = port D0-D4 content of expansion port
    and #$03  ; ignore all devices except standard controllers
    cmp #$01  ; Carry = 0 if not pressed, 1 if either is pressed
    rol raw_keys
    lda JOY2
    and #$03
    cmp #$01
    rol raw_keys+1
    ; the last 'rol' instruction clocked the loop counter too
    bcc loop
  rts

.endproc

by on (#41344)
Fx3 wrote:
Oh, unless you're using the $ symbol only for hexadecimal numbers? Like, lda 1 is "1" decimal; lda $1 is "1" hexadecimal..? :)


Yes. The $ symbol just means the following number is hex. It doesn't mean anything else.

LDA 100
LDA $64
LDA %01100100

All those 3 mean the exact same thing, just using different number bases. Any assembler will assemble them the same way.

Quote:
Yeah, always use hex, never decimal.


I don't agree. I see nothing wrong with "lda 0" and "lda 1" in something like the code sample Fx3 pasted. It's obvious they're temp vars so a longer name or hexadecimal prefix is unnecessary.

Using hex for numbers less than 10 doesn't make a lot of difference anyway, since they'll look exactly the same.

Quote:
Exept maybe for litteral constants, such as in "lda #134" where it may piss you off to convert the decimal 134 into hex, but I still do it 90% of the time so that I have an uniform syntax.


Literal constants (that aren't hidden behind a define) should be in whatever base makes the most sense. Hex isn't always the most clear. Binary makes more sense for bitfields, decimal makes more sense for something like in-game money or life, and maybe even for ID numbers to things like enemies and stuff (but those should probably be hidden behind defines anyway).

Code:
lda #<1000
sta mycash
lda #>1000
sta mycash+1

;; is TONS more clear than

lda #$E8
sta mycash
lda #$03
sta mycash+$01

by on (#41347)
Quote:
I don't agree. I see nothing wrong with "lda 0" and "lda 1" in something like the code sample Fx3 pasted. It's obvious they're temp vars so a longer name or hexadecimal prefix is unnecessary.

Using hex for numbers less than 10 doesn't make a lot of difference anyway, since they'll look exactly the same.

Yes but the syntax is uniform (and closer to the output binary that tracers/debuggers shows in Nintendulator and FCEUXP).

by on (#41348)
- Yup... awesome... :) but could we go back to the topic? ^_^;;
Re: NSF player in an emulator
by on (#41349)
Fx3 wrote:
- Plus, any problem of putting my code at CPU $4020? I don't know exactly about bankswitched NSFs, the wiki page looks unclear to me.


FDS has some readable regs around that area. So yeah that could be a problem.

I say use the $3xxx page and simply don't map it to PPU regs for NSFs. No NSF should ever read/write there.

Quote:
Anyway, is the procedure of loading/initing/playing a tune the same when the code runs on an emulator?


Yup
Re: NSF player in an emulator
by on (#41351)
Disch wrote:
I say use the $3xxx page and simply don't map it to PPU regs for NSFs. No NSF should ever read/write there.


- Yeah, but since I want to use the PPU to display the current track... perhaps not too. A "manual" JSR PLAY_ADDR *must* be placed somewhere, right? :| Well, I got the NSF play idea from here.

by on (#41353)
If your emulator decodes $3000-$3FFF to your NSF player's program and $2000-$2007 to the PPU, it can still display the current track.

by on (#41359)
If you had a readable copy of the sound registers, you could display a lot more than the current track:
http://www.youtube.com/watch?v=vC5JqM2kEKk

by on (#41361)
- Could someone explain me the bankswitch thing, plz? >_>

Memblers wrote:
If you had a readable copy of the sound registers, you could display a lot more than the current track:
http://www.youtube.com/watch?v=vC5JqM2kEKk


Cute. :) Well, that's my goal.

by on (#41362)
Fx3 wrote:
- Could someone explain me the bankswitch thing, plz? >_>


If you're familiar with the format I used in my mapper docs:

Code:
  $8000   $9000   $A000   $B000   $C000   $D000   $E000   $F000
+-------+-------+-------+-------+-------+-------+-------+-------+
| $5FF8 | $5FF9 | $5FFA | $5FFB | $5FFC | $5FFD | $5FFE | $5FFF |
+-------+-------+-------+-------+-------+-------+-------+-------+


If any of the bank bytes in the header (0x0070-0x0077) are nonzero, then you copy the bytes in the header to $5FF8-$5FFF (swapping in the desired banks) on song init -- every time you select a new song.

Otherwise, if all of the bank bytes in the header are zero, you don't do any bankswitching, and you just load the NSF into memory as the load address suggests.

by on (#41365)
1. Is possible to be bankswitched and the LOAD_ADDR & 0FFFh is non-zero?

2. As far as I can tell you, the lowest read register is $4040, used by FDS tunes. Other than that, the $4018-$403F looks free.

by on (#41366)
Fx3 wrote:
1. Is possible to be bankswitched and the LOAD_ADDR & 0FFFh is non-zero?


Yes.

given the following situation:

LOAD_ADDR=$8800
file_size_with_header=0x1980

Then you have 3 banks:

bank 0, $x000-$x7FF = nothing (most players default to 00s)
bank 0, $x800-$xFFF = NSF offset 0x0080-0x087F
bank 1, $x000-$xFFF = NSF offset 0x0880-0x187F
bank 2, $x000-$x0FF = NSF offset 0x1880-0x197F
bank 2, $x100-$xFFF = nothing (00s)

Quote:
2. As far as I can tell you, the lowest read register is $4040, used by FDS tunes. Other than that, the $4018-$403F looks free.


You're probably right. But all of $3xxx is always unused by every game/nsf. But whatever, use what you prefer -- whichever works best for you.

by on (#41368)
- The $3xxx region is used by PPU, and it'll be enable to display the track number, as example.
- Next, is 5FF8-5FFFh read only, write only or read+write for this address range?

EDIT: what means "Load the data into a RAM area, starting at (start_address AND 0fffh)"? What "RAM"?

by on (#41393)
Fx3 wrote:
- The $3xxx region is used by PPU, and it'll be enable to display the track number, as example.


You're splitting hairs. It's mirrored, that doesn't mean it's used. All games and demos use $2xxx and never touch $3xxx. (afaik, anyway -- in any event, NSFs will certainly never touch $3xxx)

But okay -- whatever :P

Quote:
- Next, is 5FF8-5FFFh read only, write only or read+write for this address range?


The format doesn't specify, so do whichever you like. It obviously has to be writable though, otherwise bankswapping would be impossible (so it can't be read-only).

I think I usually went with write-only (except for MMC5 tunes, since that range also falls into ExRAM and thus would need to be readable and writable)

Quote:
EDIT: what means "Load the data into a RAM area, starting at (start_address AND 0fffh)"? What "RAM"?


Addressing space. See the example in my previous post.

by on (#41476)
From this topic:
tepples wrote:
Petruza wrote:
Hi, is it possible to meassure time in the NES other than waiting for VBLANK?

The best answer depends on your answer to the following: Of what event are you trying to measure the time?


For NSF playback in an emulator, since it's unpratical to run a certain amount of cycles to call the play routine at 60Hz, or using a different number.

Well, for 60Hz, I though in checking the VBlank flag going 0->1->0 to count as 1 frame elapsed, using asm coding of course.

EDIT2: here my tested 6502 NSF player pseudo-code:
Code:
   jsr INIT_ADDR
_loop:
   jsr PLAY_ADDR
   lda #$00
-  bit $2002
   bpl -
   jmp _loop

by on (#41486)
If you're interested, I've uploaded the source for my powerpak nsf player.
powerpak_nsf_src.zip

by on (#41492)
- Almost a whole day of work and finding bugs or limitations... :D Well, it plays, but that (my) asm code isn't doing the task. The output is very "paused", slow. If I erase the "wait for vbl", it plays too fast.

- Suggestions?

by on (#41498)
Fx3 wrote:
here my tested 6502 NSF player pseudo-code:
Code:
   jsr INIT_ADDR
_loop:
   jsr PLAY_ADDR
   lda #$00
-  bit $2002
   bpl -
   jmp _loop

That'll wait two frames sometimes. You should install an NMI handler that increments a variable, then wait for this variable to change in your loop.

by on (#41500)
- I don't know how much freedom is allowed for running custom code in NSF playback. You know, the NMI vector is fetched from 0xFFFA - normally, it contains NSF data. Still, modifying a cpu register looks unpredictable, as A and X hold the track number and the NSF type (NTSC or PAL). The same would apply to the RAM - who knows if ram[0] or ram[1] are used or not?

- Regarding this topic, I have no clue how he managed the NMI handler or even the A and X values through the custom code. Of course, perhaps I can modify the registers or even create variables using the zero page... take my first-time programming as a grain of salt. :P

by on (#41503)
Fx3 wrote:
You know, the NMI vector is fetched from 0xFFFA - normally, it contains NSF data.

I think it can be modified.

Quote:
Still, modifying a cpu register looks unpredictable, as A and X hold the track number and the NSF type (NTSC or PAL).

Isn't the NSF supposed to store the song number in RAM?

by on (#41508)
I thought it was kind of weird that you're trying to do everything in 6502, here. Why not just have the emulator do it?

I don't know whether or not this will help, but this is how I do it in my emu:

- I only have a very tiny bit of 6502 to drive the NSF. It looks very similar to blargg's:

Code:
  JSR INIT
Loop:
  JSR PLAY
  JAM       ; 'illegal' opcode $F2
  JMP Loop


- This code sits at $3000 (read-only). The rest of $2xxx and $3xxx is removed and emulated as open bus.

- When a new track is selected by the user, I clear RAM, reset the bankswapping regs ($5FFx) to their header supplied values, set A and X to the trank number and region, and set the PC to $3000.

- Every frame, instead of triggering an NMI, I just unjam the CPU.

- In addition to removing PPU regs as stated above, I also remove $4014 and $4016. NSFs should need nothing apart from APU regs.

- I don't do any PPU emulation at all. Or generate any image to be drawn.

- I disallow all IRQs and NMIs. I know there are several NSFs out there that CLI and will break if you emulate IRQs.

- As for any visual display.. this is done by the emulator and not with some NSF driver in 6502. This lets you get around silly NES drawing restrictions, plus it prevents the NSF from messing with the display (some NSFs still access PPU regs when they probably shouldn't). My emulator calls a different drawing function if it's running an NSF, which blits the song name and stuff to the screen.

- Changing tracks, and start/stopping song playback is controlled through the UI of the emulator. I assign special keys for this like I assign keys for saving a state or taking a screenshot.

by on (#41510)
- I got NSFs playing. Yup, a few things are controlled by the emulator's UI, like changing tracks. There are a few details I had to code in C and interface it, much like a custom mapper. Of course, there are absolutely no conflicts. My custom code is loaded at 4020h, and there's 32 bytes of free space (4020-403F).

- Personally, I don't JAM the CPU, the invalid opcode is skipped OR the user warned about it.