Writing a NSF player

Discuss NSF files, FamiTracker, MML tools, or anything else related to NES music.

Moderator: Moderators

User avatar
SusiKette
Posts: 103
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: Writing a NSF player

Post by SusiKette » Tue Dec 11, 2018 2:55 am

So in other words if bank swapping is not used the loading part consists only of loading the actual file to memory so that the code can read it?
In one NSF I have exported the load address is $98D5. I'm assuming that this is the address in ROM where LOAD is located (but what is actually there and do I need to run that code at some point?). Also, is the 0x80 address in the NSF file that is the start of music and program data the start point of the "ROM" in it which I should start mapping from $8000 onward?

User avatar
cpow
NESICIDE developer
Posts: 1089
Joined: Mon Oct 13, 2008 7:55 pm
Location: Minneapolis, MN
Contact:

Re: Writing a NSF player

Post by cpow » Tue Dec 11, 2018 6:32 am

SusiKette wrote:Using .dll seemed to be useless effort.
In what sense?

User avatar
SusiKette
Posts: 103
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: Writing a NSF player

Post by SusiKette » Tue Dec 11, 2018 6:45 am

cpow wrote:
SusiKette wrote:Using .dll seemed to be useless effort.
In what sense?
the program didn't sort of know it is there. It did display in the project tree and it did recognize some information about it, but you couldn't use it for some reason. It just didn't recognize it when I tried to actually include it into a script. I'm not sure if it was a bug or if it wasn't fully compatible

User avatar
SusiKette
Posts: 103
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: Writing a NSF player

Post by SusiKette » Wed Dec 12, 2018 1:37 am

So here is a question about the carry flag. If I'm testing for overflow to set the carry flag, do I have to clear it if the condition to set it was not met (this might be relevant if carry was already set before the operation). Of course with ADC the carry flag would be used in the addition if it was set (is it cleared after?).

EDIT: Does the carry flag stay set if an overflow happens after adding the carry flag?

Code: Select all

//cv.A = Accumulator
//cv.A_temp = Value of accumulator before operation

    public void OverflowTest()
    {
        if(cv.A_temp > cv.A)
        {
            Set_C();
        }
        else    //Is this part necessary?
        {
            Clear_C();
        }
    }
Secondly, do I have to code interrupts to the player? If I have to, which interrupts do I include and how should they be handled?

User avatar
cpow
NESICIDE developer
Posts: 1089
Joined: Mon Oct 13, 2008 7:55 pm
Location: Minneapolis, MN
Contact:

Re: Writing a NSF player

Post by cpow » Wed Dec 12, 2018 5:17 am

SusiKette wrote:
cpow wrote:
SusiKette wrote:Using .dll seemed to be useless effort.
In what sense?
the program didn't sort of know it is there. It did display in the project tree and it did recognize some information about it, but you couldn't use it for some reason. It just didn't recognize it when I tried to actually include it into a script. I'm not sure if it was a bug or if it wasn't fully compatible
Yeah looks like I've some work to do since I only use it in C++ applications. :roll:

tepples
Posts: 21755
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Writing a NSF player

Post by tepples » Wed Dec 12, 2018 6:27 am

SusiKette wrote:If I'm testing for overflow to set the carry flag, do I have to clear it if the condition to set it was not met
Yes. Every instruction that changes a flag can set it to 1 or clear it to 0. There aren't really any instructions where the flag output from an instruction is OR'd with the existing flag value. But many instructions leave a flag unchanged: off the top of my head, only ADC, SBC, BIT, and PHP affect V. If an instruction does not modify a particular flag, such as AND and ORA not affecting C, then programs will expect its value to be preserved across the instruction.
SusiKette wrote:Does the carry flag stay set if an overflow happens after adding the carry flag?
Yes. This means that if carry is set, ADC #$FF or SBC #$00 leaves both A and carry unchanged.
SusiKette wrote:Secondly, do I have to code interrupts to the player?
Only for the experimental timer support in NSF version 2. Otherwise, the only thing resembling an interrupt is the one that calls the PLAY routine.

User avatar
SusiKette
Posts: 103
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: Writing a NSF player

Post by SusiKette » Wed Dec 12, 2018 6:48 am

tepples wrote: Only for the experimental timer support in NSF version 2. Otherwise, the only thing resembling an interrupt is the one that calls the PLAY routine.
Do I have to detect the ending of the PLAY (and INIT) routines somehow or is there a infinite loop at the end that waits until I call the PLAY routine again? (basically change the program counter there or is there some other method to do it?)

tepples
Posts: 21755
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Writing a NSF player

Post by tepples » Wed Dec 12, 2018 7:04 am

The INIT and PLAY routines end with an rts instruction. But because they may themselves call subroutines, you need to wait for an rts instruction to move the stack pointer into the area of the stack that you have reserved for the player.

User avatar
rainwarrior
Posts: 7680
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Writing a NSF player

Post by rainwarrior » Wed Dec 12, 2018 7:11 am

SusiKette wrote:So in other words if bank swapping is not used the loading part consists only of loading the actual file to memory so that the code can read it?
In one NSF I have exported the load address is $98D5. I'm assuming that this is the address in ROM where LOAD is located (but what is actually there and do I need to run that code at some point?). Also, is the 0x80 address in the NSF file that is the start of music and program data the start point of the "ROM" in it which I should start mapping from $8000 onward?
A LOAD of $98D5 means put the data from the NSF at $98D5. (If not bankswitching.)

Yes, the data begins at $80 in the file.

User avatar
SusiKette
Posts: 103
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: Writing a NSF player

Post by SusiKette » Wed Dec 12, 2018 8:09 am

tepples wrote:...you need to wait for an rts instruction to move the stack pointer into the area of the stack that you have reserved for the player.
Can you explain this a bit further?

tepples
Posts: 21755
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Writing a NSF player

Post by tepples » Wed Dec 12, 2018 8:51 am

An NSF player is allowed to reserve the end of the stack page for memory used by the NSF player itself as opposed to memory used by the NSF being played. This is done to allow for NSF players that run on an NES, such as the NSF player in the PowerPak, which need to keep track of state such as current song, controller presses, and/or elapsed time. Suppose for example that an NSF player reserves memory addresses $01F1 through $01FF for its own use. Then when calling the INIT or PLAY routine of the NSF being played, the NSF player will push a return address within the NSF player's code, which address is stored at $01F0 (high byte) and $01EF (low byte), leaving the stack pointer (register S) at $EE before the CPU jumps to the INIT or PLAY routine. When the INIT or PLAY routine executes the rts instruction, it pulls the program counter from $01EF and $01F0, leaving the stack pointer at $F0. This would cause an NES-based NSF player to enter a routine that performs tasks such as input reading and waiting for the next opportunity to run the PLAY routine. An emulator-based NSF player, such as the one you are writing, would instead detect the end of the INIT or PLAY routine in one of two ways: the stack pointer (register S) has entered the area reserved for the player's use or the PC has taken on a particular reserved value denoting completion. You might choose $4100 or the like for this value.

In the preceding, which was the first word you failed to understand?

User avatar
rainwarrior
Posts: 7680
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Writing a NSF player

Post by rainwarrior » Wed Dec 12, 2018 3:26 pm

You don't have to put anything on the stack for an NSF player emulator, maybe let's take one step back from this.

INIT and PLAY are both subroutines, that could normally be called via JSR. The subroutine runs, possibly calling its own subroutines, but eventually it finishes with an RTS like all subroutines do.

When a subroutines is finished, the stack register will be returned to the position it started by that RTS. Your NSF player could detect that the routine has finished just by checking the stack position, but there are other ways to do it as well.


Some NSF players, especially hardware ones, push a bit of extra stuff to the stack to help manage this. This is what tepples is talking about. Because according to the spec the NSF is allowed to use all RAM in the system the way it likes, except the stack region, the stack is the only safe place for a hardware NSF player to use for its own memory storage. Emulators don't have to do this, though they can. (It can be convenient for the implementation.)


One more thing: the PLAY routine does not necessarily have to return. Usually this is for NSFs that play recorded PCM samples over the DMC channel. Not all NSF players support this properly, but to support the NSF player should continue to produce sound in step with the CPU's actions, whether or not PLAY returns.

User avatar
SusiKette
Posts: 103
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: Writing a NSF player

Post by SusiKette » Thu Dec 13, 2018 12:02 pm

I think I got the basic idea on how this works now. I think what I'll do is that when I call INIT or PLAY I'll push a return address to the stack, change the program counter to the routine's address and enable the CPU. The return address points to a part of the memory the program counter normally doesn't have any business begin at and this is tested after each opcode is executed. If it is within that area, I'll disable the CPU until I need to call PLAY again.

User avatar
SusiKette
Posts: 103
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: Writing a NSF player

Post by SusiKette » Fri Dec 14, 2018 9:31 am

Can I run one frame's code (one PLAY routine) all at once or should I be waiting the appropriate amount of time between every instruction based on how long each instruction takes to process (559 ns per cycle iirc)? Since the player has to wait for the next call for PLAY routine anyway to continue even if it finished the routine sooner than what it would have if the wait between instructions was implemented. Could this cause any serious issues with the player?

tepples
Posts: 21755
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Writing a NSF player

Post by tepples » Fri Dec 14, 2018 10:48 am

Non-returning PLAY routines, particularly those that play raw PCM through writes to $4011, depend on each instruction taking as many cycles as it actually takes.

Post Reply