Turn your headphones down, or, maybe even off for this one:
So what was that terrible idea? I figured the only place to go from making tracker mods natively on an Amiga last month would be to code the next song in machine language, get it? Machine….language, anyway….
No one can write in machine language though, so I did the next best thing, and wrote some 6502 assembly targeting the NES.
As @antillese has explained in the context of much better NES songs, the NES has 5 audio channels. 2 pulse channels, a triangle channel, a noise channel, and a sample playback channel. The channels each have 4 bytes of memory mapped to the APU, which actually lives on the NES’s modified 6502 CPU, but it gives us discrete bits of things that we can control and it handles most of the hard stuff for us without having to code anything. Most NES games have songs written in some external tool like a tracker, and then a subroutine in the program rom loads in those songs into the APU on a per frame basis from rom, no direct fiddling with the APU required. But I ask you, where’s the fun in that?
We’re going to write a simple program to load in randomly generated garbage into the APU channels, and then we’re going to turn the channels on and off by increasing the APU flag counter at $4015 and then resetting it in a 16 beat cycle. This will create “music” in so far as it will be pitched notes in a repeated order, as music was defined by the Criminal Justice and Public Order Act 1994. Ready? Let’s jam!
First we’re going to set up the iNES header. We don’t need anything fancy so we’re setting it to the NROM mapper (or lack thereof) that SMB 1 uses.
.byte 02 ; progrom
.byte 01 ; charrom
.byte 00 ; nrom mapper
.byte 00, 00, 00, 00
.byte 00, 00, 00, 00, 00
Then we’re going to declare some variables in the zeropage, the first 256 bytes of memory that’s fastest to access
frame: .res 1
beat: .res 1
seed: .res 2 ; for randomness
Then we’re going to set up our program rom, most of this is boiler plate like setting up memory and waiting for the crt that we’re hypothetically plugged into to cycle twice so we know we’re ready to rock
sta $0000, x
sta $0100, x
sta $0300, x
sta $0400, x
sta $0500, x
sta $0600, x
sta $0700, x
sta $0200, x
sta $2000 ; nmi on vblank
sta $2001 ; turn on graphics
Now we’re going to initialize our counters and our random seed and get ready for our main loop
; initialize counters
; initialize the seed to non zero or else it will always be 0
; turn pulse on to start
; loop forever and wait for interupts
Now in our main loop we’re going to fill our APU with garbage and increase the channel on off flags in $4015 to 00010000 and then loop back to 00000001 for, uh “music”. The bits in the byte at $4015 turns on things as follows : nothing nothing nothing sample noise triangle pulse2 pulse1
jsr prng ; set the a register to a random value to store in everything
lda #$ff ; if we're at 30 prepare to roll over
; if we're at 30 increment the beat counter
inc $4015 ; change what's playing every beat
lda #$ff ; if we're at 16 prepare to roll over
; if we're at 16 reset the seed
Finally set up where the interrupts are and then where our non existent character rom lives:
Voila we have…..something that sounds awful. But hey! It’s audible!
If you want to fiddle around with NES programming, I learned how to do most of this from the truly exhaustive NES dev wiki here: https://www.nesdev.org/wiki/Nesdev_Wiki