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


by on (#118761)
EDIT: this was originally a "help" post. The first response fixed where I was confused, so now it you can just consider it a "here's how I built an NSF file using MUSE and CA65".

So I was trying to make an NSF, given the original source to a specific game I helped with earlier. And I discovered there must be some subtlety to the unbanked NSF format that I'm completely misunderstanding.

I made a .cfg that looks like:
    # First, the parts that are specified in the image, in the order they're needed:

    # NSF Cartridge Header
    HEADER: start = $0, size = $80, file = %O, fill = yes;

    ROM0: start = $8000, size = $8000, file = %O, define = yes;

    # Then, RAM definitions:
    ZP: start = $0, size = $100, type = rw, define = yes;

    # standard 2k SRAM (-zeropage)
    SRAM: start = $0200, size = $0600, define = yes;

    HEADER:   load = HEADER,          type = ro;
    CODE:     load = ROM0,            type = ro,  define = yes, align=256;
    MUSE:     load = ROM0,            type = ro,  define = yes, align=256;
    BSS:      load = SRAM,            type = bss, define = yes;
    ZEROPAGE: load = ZP,              type = zp;

And a little tiny asm source that looks like
;;; cl65 -d -vm -l nsf.lst -g -t nes -C nsf.cfg -m -Ln nsf.lbl -o driar.nsf nsf.asm

.segment "HEADER"
   .import __ROM0_START__
   .macro pad32 str
    .if (.strlen(str) > 31)
     .error "pad32 given too long input"
    .byte str,0
    .res 31-.strlen(str)
   .byte "NESM",$1a
   .byte 1     ; version
   .byte 10    ; number of songs
   .byte 1     ; starting song
   .word __ROM0_START__ ;;; This is where I was wrong. loadaddr should be the start of the ROM0 segment
   .word initaddr
   .word playaddr
   ;; ....0123456789a123456789b123456789c1
   pad32 "Driar Soundtrack"
   pad32 "David Eriksson"
   pad32 "©2012 David Eriksson"
   .word 16639 ; Real NTSC rate
   .byte 0,0,0,0,0,0,0,0 ; disable bankswitching
   .word 19997   ; Real PAL rate
   .byte 3   ; Prefer PAL, compatible with both
   .byte 0   ; no expansion audio
   .word 0,0

MUSE_RAM: .res 256
savedA: .byte 0
savedX: .byte 0

.segment "CODE"
.proc initaddr
   stx savedX
   sta savedA

   lda #<musedata
   ldx #>musedata
   jsr MUSE_init
   lda #15
   jsr MUSE_setVolume
   ;; X=1 if PAL, 0 if NTSC
   lda savedX
   eor #1
   ;; now A=0 if PAL, 16 if NTSC
   jsr MUSE_setFlags
   ;; A=desired song number
   lda savedA
   jsr MUSE_startMusic

.proc playaddr
   jsr MUSE_update
.segment "CODE"
.include ""

.segment "MUSE"
   .align 256
   .include ""

So there are two major things I don't understand:
1- What's the difference between loadaddr and initaddr in an unbanked NSF? I originally thought I should put MUSE_init in loadaddr, and MUSE_startMusic in initaddr, but then it doesn't work at all. What works is what I have above, where loadaddr is moot and initaddr does everything. Why would that be the case?
2- If I reorder the above segments, such that or or both come before my little stub code, and thus the address of loadaddr, initaddr, and playaddr are no longer at $80xx, once again, it doesn't work at all. Why does this break things?
by on (#118764)
Well, I can help you out with at least one of these things.

The load address in the NSF file tells the NSF player the beginning of that section of memory where the NSF code is loaded. The load address is *not* used as an address to any subroutine. The init address, on the other hand, is the address to the init routine, which is executed only once (when a song begins playing). Ideally, everything that prepares a song to be played should be executed in the init routine. These two addresses may or may not be the same depending on where the init routine is located.

Let's say, for example, you have all your sound data and code at $8000-$bfff. Let's say you have a bunch of music data at $8000, and your init routine starts at $a000. In this particular case, the load address would be set to $8000 and the init address would be set to $a000.
by on (#118768)
And that explains everything. Letting loadaddr move around (rather than being fixed to $8000) was what was breaking everything. And now that I know what to look for, I found that in the wiki page on NSF

<sigh> Thanks!