Sound in Assembly - YM2151 using ROM functions
It has been a while since I did any sound programming.In 2020 I wrote a couple of libraries using VERA PSG to add sound effects or music to games written in BASIC. I know few people used those libraries to also add sound effects to Assembly and C projects. They are still perfectly usable and convenient:
Simplest Sound Effects Library
Music Player Library for BASIC programs
I never really got into programming the other sound chip in Commander X16, the famous Yamaha YM2151. It is quite complex and it would require some significant investment in time that I simply did not have.
Flash forward 5 years and we now have many improvements and new features in Commander X16 ROM, including a set of Kernal calls dedicated to both PSG (VERA) and FM (YM2151) sound chips that make sound programming much easier and does not require learning all the quirks and details.
In this article we will explore the minimum code required to add sound effects to our projects which I will follow up with a simple Synthesizer using the same functions.
In order to be successful at the task ahead we will cover following areas:
- Look at ROM banking to get access to sound functions in ROM
- How to initialize YM chip
- Which functions to call to generate sound
ROM Banking
As we know, the 6502 family of processors have 16 bit address bus and can therefore only address 64K of memory (both ROM and RAM combined). To overcome this limitation we are using memory banking extensively. If we focus on ROM we currently have 16 overlapping sets of libraries that are dedicated to different areas that we might need. Descriptions of all 16 are available in the official Commander X16 documentation.
CPU really doesn’t know which one to use so we set that by writing the ROM number to memory address $01
For our use let’s just mention a couple. When the Commander X16 is started it is using BASIC ROM, and we verify that by checking the value at address 1
PRINT PEEK(1)
And we got 4, which is what we expected after reading through documentation. In the same document we also see that AUDIO rom has number 10 so in order to be able to call Audio functions we have to first set it to 10.
After we do that it is very important to keep track of which ROM is active unless we don’t plan to use any other ROM functions, we would want to change it back after our audio functions are called.
Alternatively we can use jsrfar which does the bank change for us and then return it back to the value before the call. I personally like to change ROMs only when necessary and do it “manually”.
In Assembly all we have to do is write value 10 ($0A) to memory address 1 ($01) and that’s it:
lda #10Make some noise
Now that we have access to Audio ROM we can generate a wide variety of sounds. Again a quick look at documentation (Chapter 11) shows that we have over 50 functions to choose from so what is the easiest way to make noise.
First of all, if we want to focus only on YM2151 we only look at functions that start with ym_ which narrows the number of functions quite a bit. What we need to do is to initialize the sound chip, define what sound we want and play it. I am lazy and don’t want to invent new sounds but prefer to choose from the ones that are predefined for us in ROM. For that we only need to call one function for each of the above steps:
To initialize the YM2151 we need to call ym_init function. It requires no parameters and returns with Carry bit cleared if successful.
To play a sound I typically choose between two functions ym_playnote or ym_playdrum. For either of them we need to first decide the type of sound we want to play. Again, ROM comes with preset "patches" that we can simply load and use, no complicated chip programming is required. As names of functions imply the ym_playnote is mostly supporting musical instruments and the ym_playdrum more percussions. In total we have 127 standard and 35 extended patches for ym_playnote and 62 patches for ym_playdrum. The difference between these two functions is that for ym_playnote we have to load patch first and then define the pitch and play and in case of ym_playdrum it also loads the patch and plays the sound, we can't change the pitch.
To load patch for ym_playnote we have to call ym_loadpatch and use following register to pass parameters to it. A register must contain the channel we are setting up. YM2151 has 8 channels so valid values for A are 0-7. If we decide to use one of the preset patches then register X must contain the patch ID. Very important is use of Carry bit. If we are using preset patches the Carry must be set to 1. If we want to use our own custom patches then Carry must be cleared and Registers X together with Y must point to the memory address where the custom patch is stored.
For example if we want to load jazz guitar sound to channel 0 we need to write the following code:
lda #0 ; Channel numberNote, that before we execute above code we must set the ROM to bank 10, otherwise the $C069 might be called in the wrong ROM and likely crash the computer.
Next step is to actually trigger the sound to start. Again this is very straight forward step, we just need set ups few registers and call ym_playnote. Register A is again the channel number, and register X and Y set the pitch (or frequency) predefined as musical notes. The main note is called KC and must stored to Register X and in top nibble contains Octave and lower nibble notes for C♯ = 0, D = 1, D♯ = 2, E = 4, F = 5, F♯ = 6, G = 8, G♯ = 9, A = 10, A♯ = 12, B = 13 and C = 14. Note that values 3,7,11 and 15 are not used. KF in register Y contains fractions for fine tunning the frequency.
To play middle C we need to set Octave to 3 and note to 14 or in hex $3E.
lda #0 ; Channel numberIn my recent game Crazy Solitaire I used two variations of ym_playdrum calls, one for card drop and the other for card placed to the Foundation and winning points. Below is a complete sound snippet from that game. It is even simpler than above example because we don't need to load patches separately and we don't need to set the note.
Using the above learnings I wrote a simple Synth Demo:
Sound in Assembly - Use YM2151 to write Synthesizer

Comments
Post a Comment