As I've continued my dive down the rabbit hole that is assembly, I've found a buoy to grab onto: NES programming.
The NES was my very first video game console, the remodeled version from the early 90s. Super Mario 3 was my absolute favorite, even though I couldn't really get past World 3, the water world, without using a warp whistle. In short, the NES was a huge part of my childhood.
It's worth mentioning that I wanted to make games basically since I saw Crash Bandicoot for the first time. Since I found out making games was even an option. The idea that I can program my first console is almost dreamlike.
Weapon of Choice
Anyway, remember when I said in the last post that assembly is highly system specific? Well, that ain't the half of it, buddy!
Assembly is like one piece of the puzzle when it comes to 80s era systems. I mean, with x86, you still have to program for the OS, but there's really only a few common APIs: Windows, Linux, macOS...I mean, if you need an executable for BSD, sure, it's an issue. But with 6502, my chosen language, you have to program for a specific computer model. Like the Apple II and Commodore 64 have entirely different APIs – shit, the Apple II and Apple II GS have entirely different APIs!
My point is, you need to pick a system, I've found, if you want to learn these languages. I'm going with the NES and 6502 for now. Before I dive in, big shout out to wiki.nesdev.com, which has been an invaluable resource in my exploration.
The Lay of the Land, or, How the Memory is Mapped
Spoiler alert: everything is memory-mapped. The I/O, the picture processing unit (PPU), the audio processing unit (APU)...every time you need to communicate with another part of the system, you're probably reading or writing memory.
This is so much cooler than using Yet Another Library to do I/O, or audio, or video – everything you need is right at your fingertips, a PEEK or a POKE away (or, since we're using assembly, LDA or STA).
This integrated system is just the complete opposite of what I'm used to. From everything I've read about other 80s computers like the Apple II and Commodore 64, they're similar in nature. In other words, systems of that era were "batteries included". You see similar principles in languages like Python or Racket, but not at the same scale. Not on a system-wide level.
Exploring the Territory
To be honest, I'm still exploring what the NES can do. The one unit I've dug into so far is the APU, for generating music and sound effects, and playing samples (of a sort – it's delta modulated PCM, I think, but that's another article).
The APU, if you've ever played Mario, Zelda, or Metroid on the OG NES, can be quite powerful (although limited in some ways). For example, it generates lively multi-instrument melodies on Super Mario 3, but you'll notice if you jump on certain levels or have a star power-up, it'll skip certain notes in the music because that channel's being used for the jump or the star. I've played this game a lot, if you couldn't tell, but I'm not sure if they used a custom sequencer or if this is straight up APU usage...
Anyway, I'm trying to learn how to make this chip sing (literally). There's two square wave generators available, and a triangle wave, along with a noise channel (which makes me wonder if that's how speech sounds are generated in NES games). With the square wave generators, you can set up envelopes, even! Oh, and fun fact: since there's no easy
printf command for debugging, devs commonly use sound effects to denote points in the program, sections of code being executed. How cool is that?!
Obviously, I'm still exploring the capabilities of this chip and how exactly I can use it. I think the first step here is to get a beep out of an emulator, to get a program running visibly (or audibly) on some form of the NES. To do this, I need to 1. figure out how data and code are formatted in a 6502 assembly file for the NES, 2. use cc65 to cross-compile this code, and 3. figure out how to load the result on an NES emulator.