Sega Megadrive – 10: Sound Part I – The PSG Chip

Okay, despite my promises of regular updates I’ve fallen a little behind, and you can thank a short holiday, music festivals, my Real Job™, and many many videogames for that.

Welcome to the first article in the Sound series! Being a full-time audio engine programmer, game audio is a subject very close to me, so this should get quite tasty. One of the most nostalgic and fondly remembered parts of my childhood were the soundtracks accompanying my favourite games, many of which I still find technically impressive, especially considering the machine’s audio capabilities didn’t really weigh up to those of its competing consoles, requiring the sound designers to squeeze so much out of so little.

There are three chips which can create, or are involved in the creation of sound in the Megadrive:

The PSG chip

The Programmable Sound Generator, the same Texas Instruments SN76489 chip used in the Sega Master System. It’s a simple device, comprising 4 mono channels – 3 channels generate a square wave at varying frequencies, and the 4th is a white noise generator. Each channel can have its attenuation modified, and the frequency of the wave or density of the white noise.

It’s got a good trick up its sleeve – you can set the wave generators to stick at +ve, then switch the attenuation registers high and low very quickly to make it behave like a 1-bit DAC, meaning it’s capable of playing PCM data. It won’t win any sound quality awards, and with the available memory I doubt it could stream entire soundtracks, but it can pull of some short voice overs and sound effects which can’t be synthesised.

The FM chip

The Yamaha YM2612 – a stereo, 6 channel sound chip with lots to brag about. 6 FM voices with 4 operators each, Yamaha synthesizer patch compatibility, envelopes, a distortion oscillator, two built-in timers, and a DAC for playing 8-bit PCM data.

Each ‘operator’ of a channel has an input and output (from and to other operators, or the final output), a frequency and an envelope. These operators can be routed in various formations to create algorithms suited for different instrument types (harp, strings, flute, brass, xylophone, piano, organ, guitar and various percussion). It’s quite a complex beast and requires some serious effort to get anything other than basic beeps out of it, but I’ve found plenty of documentation and examples so hopefully I can get it singing.

The Z80 chip

The 8-bit Zilog Z80, which was the Sega Master System’s main CPU, made its way to the Genesis both for backwards compatibility and as a slave CPU to the 68000. Neither of the sound chips can be programmed and left to get on with it, they need their hands holding for the whole session, having data fed to them constantly. This would be quite a pain to manage on the 68k, and it would be impossible to keep up a feed of PCM data whilst trying to process an entire game at the same time, so the Z80 can be used as a secondary CPU for managing the audio. Unfortunately, it doesn’t have any knowledge of the sound chips or how to operate them by default – we need to write our own sound driver (a sequencer, and a program to feed PCM data) in Z80 assembler, generate a binary and load it on startup. This means learning another assembly language, but I’m certainly up for the challenge.

Programming the PSG

I won’t jump straight in the deep end and write a Z80 audio driver just yet, I need to get a handle on the basics. This article covers the simplest case – operating the PSG from the 68000.

The PSG has 4 channels3 square wave generators and 1 white noise generator. I’ll concentrate on just the wave generators to begin with. Each channel is controlled by 2 registers: one for the attenuation, and one for the wave generator’s counter reset. The attenuation registers are 4 bits in size, allowing 16 possible volume values, from 0x0 (no attenuation; full volume) to 0xF (full attenuation; no volume). The counter reset registers are 10 bits in size, and store the square wave’s time until the polarity of the output is flipped in clock ticks / 16 (essentially the “wave width / 2”).

So, assuming an NTSC setup with a clock frequency of 3579545 Hz, a register value of 0xFE would generate a square wave at a  frequency of 440.4 Hz (3579545  ÷ (2 x 16 x reg value)).

To program the PSG, we write to its control port at 0x00C00011, one byte at a time. The most significant bit is the latch, telling the chip that this is the first or only byte it’s expected to receive. Bits 6-5 mark the channel ID (0 – 3) that we’re about to modify. Bit 4 indicates that we’re writing either the attenuation value or wave/noise settings. That leaves 4 bits for the data, which may or may not be enough. If there wasn’t enough space, we send a second byte with the latch bit OFF, indicating it contains the remaining data and is not a new command, with the upper 6 bits of data in bits 5-0.

So, a quick recap:

First byte:
Bit 7    : Latch. ON indicates this is the first (or only) byte being written
Bits 6-5 : Channel ID (0-3)
Bit 4    : Data type. ON if data bits contain attenuation value, OFF if they contain the square wave counter reset
Bits 3-0 : The data. Either all 4 bits of the attenuation value, or the lower 4 bits of counter reset value

Second byte:
Bit 7    : Latch. OFF indicates this is the second byte, and will only contain the remainder of data
Bit 6    : Unused
Bits 5-0 : Upper 6 bits of data

So, let’s make channel 0 produce 440.4hz. The first byte needs the latch ON (to indicate it’s the first byte), the channel ID, the data type bit OFF (to indicate we’re writing a counter reset value), and the lower 4 bits of the value 254. The second byte needs the latch OFF (it’s the second byte), and the upper 6 bits of the value 254.

They’re written to the PSG control port at address 0x00C00011:

move.b #%10001110, 0x00C00011 ; Latch ON, channel 0, counter data type, lower 4 bits of data
move.b #%00001111, 0x00C00011 ; Latch OFF, upper 6 bits of data

The channel will have been initialised fully attenuated, so we need to turn the volume up to hear anything. The attenuation is specified in 4 bits, where 0 is fully attenuated and 16 is full volume, so we can fit the command and data in a single byte. Latch needs to be ON, channel ID is 0, data type bit ON to indicate attenuation data, followed by the 4-bit value:

move.b #%10010000, 0x00C00011 ; Latch OFF, channel 0, attenuation data type, 4 bits of data

Immediately after writing, the PSG will emit a constant tone of 440.4hz, at full volume.

A poor man’s sequencer

Even with just one feature of the PSG covered, it’s enough to make a basic tune. By defining an array of counter reset values, with sustain times, we can iterate over them and play the notes. Here’s some example data – it’s one crudely written line from Scott Joplin’s The Entertainer. Each “note” is 32 bits in size, the first word is the sustain time in vsync frames, and the second word is the counter reset value:

 chan0_notes:
   dc.w 0x0010, 0x02f8, 0x0010, 0x02cd, 0x0010, 0x02a5, 0x0010, 0x01aa, 0x0008, 0x0000 ; D3 D#3 E3 C4 .
   dc.w 0x0010, 0x02a5, 0x0010, 0x01aa, 0x0008, 0x0000 ; E3 C4 .
   dc.w 0x0010, 0x02a5, 0x0010, 0x01aa, 0x0010, 0x0000 ; E3 C4 .
   dc.w 0x0010, 0x01aa, 0x0010, 0x0193, 0x0010, 0x017c, 0x0010, 0x0152, 0x0010, 0x01aa, 0x0010, 0x017c, 0x0010, 0x0152, 0x0008, 0x0000 ; C4 C#4 D4 E4 C4 D4 E4 .
   dc.w 0x0010, 0x01c4, 0x0010, 0x017c, 0x0008, 0x0000 ; B3 D4 .
   dc.w 0x0010, 0x01aa ; C4
 chan0_notes_end
 chan0_notes_len equ chan0_notes_end-chan0_notes
 chan0_notes_count equ chan0_notes_len/4

In order to play it, we loop over the notes, apply the counter reset value to channel 0, and wait for the defined amount of frames. It’s pretty simple:

 move.b #%10010000, psg_control ; Channel 0 full volume

 lea chan0_notes, a0            ; Notes to a0
 move.l #chan0_notes_count, d1  ; Number of notes to d1
 subi.l #0x1, d1                ; -1 for counter

 @NextNote:

 move.w (a0)+, d0       ; Delay to d0 and inc. pointer
 move.w (a0)+, d2       ; Counter reset to d2 and inc. pointer
 move.b d2, d3          ; Lower byte of counter reset to d3
 and.b #%00001111, d3   ; Clear top nybble (leave lower 4 bits)
 or.b #%10000000, d3    ; Latch bit (7) on, chan 0 (6-5), tone data bit (4) off
 move.b d3, psg_control ; Write to PSG port

 move.w d2, d3          ; Counter reset to d3 again
 ror.w #0x4, d3         ; Shift right 4 bits
 and.b #%00111111, d3   ; Only need bits 5-0 (upper 6 bits of the 10 bit value)
 move.b d3, psg_control ; Write to PSG port

 move.l d1, -(sp)       ; Backup d1
 jsr WaitFrames         ; Delay frames is in d0
 move.l (sp)+, d1       ; Restore d1

 dbra d1, @NextNote     ; Branch back up to play the next note

 move.b #%10011111, psg_control ; Finished - silence channel 0

It’s a good start, but it will need many improvements. At the very least, it should support all three square wave channels, and a higher frequency timer. Also, each note should come with a “note on” time, so we don’t need to create “empty” notes for silence, and instead have arbitrary start times. To make it more advanced, we could implement software ADSR envelopes to bring some life to each note with a softer attack and a fade out.

I’ll be building on my sequencer with each new sound feature I learn, and I have plans to write an authoring tool to compose music on a PC, instead of looking up the frequency of each note, converting it to a counter reset value and manually typing it into a text editor! The sequencer will eventually need translating to Z80 assembly language, too.

Well, this was quite a short article, I’ll try and pick up the pace again soon. I’ve missed out the white noise generator, but to really show it off I need to add some finely tuned support to my sequencer for it, so perhaps it deserves – along with many sequencer improvements – a post of its own.

Matt.

Source

Assemble with:

asm68k.exe /p soundtest.asm,soundtest.bin

References

24 thoughts on “Sega Megadrive – 10: Sound Part I – The PSG Chip

    • Im also really enjoying these posts even though I can’t program. Does any one have any advice on what I can study if I want to be able to follow along better?
      Can you program a game for the Genesis all from the PC or do you need special hardware?

      • I’d recommend starting out with a higher level language and working your way down. I always recommend C# for beginners. As for courses, Staffordshire University have started offering some really good free online courses, I think they have some Computer Science ones planned. I was self taught in C++, and it really helped, but learning about computers at such a low low level couldn’t have been achieved without throwing myself into the deep end like this. I’ve learned more from the 12 months of writing this blog than in my entire games development career.

        So far I’ve managed to get all of this running with just the MESS emulator and its built-in debugger on a PC, and no official tools or hardware. I do have a MegaCD development kit (I’ll be posting about it soon!) which I’ll be using from now on, but it’s purely a luxury and not a requirement. I’m very confident you could create an entire Genesis game with just a PC.

  1. I’ve just discovered your blog! Great job 🙂

    I’ve only worked with emulated versions, but the Megadrive sounds chips are a lot of fun to work with. I’m guessing that when it came to the YM2612 most of the heavy lifting was done on a Yamaha synth and then the patch transferred across, but i wonder what tools the musicians of the time used to program the PSG? Have you encountered any information about contemporary progamming tools in the course of your research.

    • I haven’t yet found any tools for the PSG in any form, but I’m still looking. I have various official Sega disks and docs which don’t have much info, and the big manual I’ve recently acquired is for the MegaCD and naturally concentrates only on CD audio.

      I think the general routine for studios at the time was for the sound designers themselves (or a programmer under the sound team’s supervision) to write the sound driver and tools, tailored towards their games’ specific requirements. In my day job I’m fortunate enough to work with David Whittaker, one of the Commodore 64’s most prolific sound engineers, and he was quite famous for writing his own sound drivers, sequencers, and even specialised tools to get compositions from his own synthesizers into a binary file. I’m eager to get 5 minutes of his time when he’s not rushed off his feet, I have many questions.

      I’m in the middle of writing my own authoring tool to go with the sequencer in these articles, I’ll release it here when it’s done 🙂

  2. Hey bigevilcorp!
    It’s great to see you back at it.

    Have you ever tried to contact the Watermelon Team? The people behind Pier Solar?

    They have already made an excellent Genesis/ Mega Drive game and they’re working on another. If they’re willing to share info I know they have everything it takes.

    I found these details at their site:
    http://www.watermelon-corp.com/

    enquiries@watermelon-corp.com
    +1 (563) 571-0019
    216 Sycamore St.
    Suite 500
    Muscatine, Iowa
    USA

    This is their online store:
    http://www.magicalgamefactory.com/

    • Yes, I’m a big fan of them! One of the guys posted on the SpritesMind forum not so long ago, offering details of how to get Genesis cartridges manufactured. I’ll certainly get it touch when I have a full game running, would be nice to have a few real copies!

  3. Hi Matt! Love the article. I am a sound designer and currently trying to make some Mega Drive style music. I am not a programmer and a bit confused about the sound specs of the consol. I dont quite understand what the two sound chips combined bring to the table. Does the Mega Drive have a total of 10 channels (6 FM+4 from the PSG)? Most sites and articles claim that the consol has 6 channels in total? Probably a stupid question, but forgive me 🙂 Kind regards – Morten

    • Hi Morten,

      I’m afraid my expertise lies with only the PSG chip, not the FM chip, I’m still trying to get my head around the latter. It’s been a while since I’ve posted an article, been busy with the day job, I’ll try my best to get back into the habit soon.

      As for the FM chip, your questions are best directed at Nemesis over at the Sprites Mind forums, he’s written a very thorough post on the technical details of the FM chip .

      I’m assuming when people refer to “the Megadrive’s 6 channels” they are probably only referring to the FM chip. The PSG adds 4 channels of wave and noise only, and was the same chip brought across from the Master System.

    • Oh, and if you’re interested in making sounds rather than the technical jargon of the chip, then is a must-have tool. It’s sequencer which emulates most 16-bit sound chips, the Megadrive FM included.

  4. I’m afraid “Real Life” got in the way, I’ve been through several house moves and a new job, and it all got a bit on top of me. I’ll try my best to revive this thing once things have settled down a bit. I do think about it often, and it upsets me more than you that I couldn’t keep up the pace!

    • Hey hello! It’s great to see you’re still around. I know exactly what you mean about life getting in the way of some of the fun stuff. But it’s all good. Doesn’t matter if you take a break and return. It doesn’t change how cool what you’re doing is. No need to commit to regular posts just do them as you have time and feel like it.
      We just wanted you to know that we really do enjoy your site.

  5. Jesus Christ you fairly ramp it up on this post don’t you! All the posts up till now have been fairly informative yet easy going, interesting and understandable but all of a sudden I hit this one and suddenly it’s been kicked up to 11 haha! I’m sure it is just because this is probably your bread and butter with you being an audio engine programmer but I have no idea what half the stuff here means (and I’m a software dev professionally!).

    I’m not criticising, I just find it funny: ‘3 channels generate a square wave’ – A what now?, ‘4th is a white noise generator’ – isn’t that what an untunned telly makes? Why would I want that? ‘Each channel can have its attenuation modified’ – It’s what? And so on and so on! 😀 😀 😀

    Don’t answer these questions, I’m sure I can find another resource to explain the specifics of audio related development. I’m just pointing it out because it is great to see you talking about something you are clearly knowledgeable and excited about and the difference it has, likely unwittingly!, made to your writing style.

    If you find time please continue this series, it’s a great resource for those interested in the inner working of the Megadrive and, although I love it as well, much more accessible than the Darkdust.net / Marc’s Realm writings.

    Cheers

    • Ahah I guess I’m just in my element here. All of those terms are searchable and aren’t particularly difficult to grasp, let me know if you need explanations for anything.

      I would love to continue to series, but finding spare time is a real pain. I’ll try my best but can’t make any promises. All my time at the moment is spent writing my game, but I will be releasing the framework source and toolchain so hopefully I’ll continue to be of some use to other developers!

  6. Hi Matt, thanks for taking the time to write and upload these tutorial articles (I’m aware it was a few years ago now but still.) Very helpful 🙂

    While reading I noticed a couple of things that may be in error:

    – In the second paragraph under Programming the PSG you’ve said that the attenuation registers’ values are “from 0x0 (no attenuation; full volume) to 0xF (full attenuation; no volume)” but then later on you say “The attenuation is specified in 4 bits, where 0 is fully attenuated and 16 is full volume”, which seems contradictory. The latter case seems more intuitive but looking at your code it seems it’s the first description that’s correct.
    – The second code box has comment starting “Latch OFF” but I think you meant latch on

    And I also had a few thoughts/questions about the code:

    + I was wondering when you do two consecutive move.b instructions to the PSG control port, can this be done as a single move.w ? I think you can do this with some other parts of the system like the VDP control port iirc, probably others too.
    + Is there a technical reason why you’ve coded the delays (note sustains) before each note rather than after them?
    + What is the @ for in the @NextNote: label? I’ve coded a hello world for the mega drive in the past but used labels without @’s. I think I used snasm.

    Sorry to bother you :p

Leave a reply to TheSegaDude Cancel reply