Playing VGMs on an STM8S

Monday, 14th December 2009

Following the STM8S tutorial in my previous post, I've tried to put the chip to some practical use. My initial experiments into producing a video signal proved unsuccessful; I managed a static image using hard-coded delay loops, but when trying to use interrupts to trigger the generation of scanlines the timing was all wrong and without an oscilloscope or a working simulator I couldn't find out what was wrong. I decided to turn my attention from picture to sound.

Photo of VGM player

VGM files store game music by logging the data written to the sound chips inside the console or computer directly along with the delay between writes. This results in reasonably small files that are capable of producing excellent sound quality, depending on the way the sound chips are emulated (or, in some cases, not emulated).

I've chosen to focus on the SN76489, a simple sound chip found in a variety of machines including the Sega Master System and BBC Micro. Three of its four sound channels are simple square wave tones, implemented as a 12-bit decrementing counter that flips the state of its output every time it underflows and is reset. Changing the value that is preloaded into the counter when it is reset changes the period of the output square wave, resulting in a change of pitch.

Square waves

The fourth channel proves rather more of a challenge. It uses a shift register (15- or 16-bit depending on the particular version of the chip) instead of a simple tone counter, and has two modes. When generating periodic noise a single bit shuttles around the shift register, generating a 1/15th or 1/16th duty cycle square wave. This has effect of producing a lower pitch with a distinctive "buzzy" timbre. The other mode is white noise, which uses a feedback system to generate pseudo-random noise.

The emulated SN76489, or PSG, has been implemented in two parts. The first is an interrupt handler written in STM8S assembly for speed. This is executed approximately 44,100 times a second (44.1kHz is the internal time step used in VGM files) and is used to update the internal PSG counters and shift register and generate the output level for that particular sample. Two output levels are generated as I've implemented the Game Gear's stereo extension to the PSG (this simply lets you switch individual channels on or off for each ear). These levels are loaded into capture compare registers for TIM2, which is used in PWM mode to generate the analogue output signals.

The rest of the code is written in C. This includes the second part of the emulated PSG, which handles bytes written to the PSG and updates its internal registers as appropriate.

VGM player circuit

Due to a 16KB limitation with the free version of the Cosmic compiler (and the 32K physical limitation of the microcontroller itself) the VGM file is stored on external EEPROMs which are accessed over the I2C bus via the microcontroller's I2C peripheral. As I don't have any large single EEPROMs, I've used two 32KB EEPROMs, one at address 0xA0 and the other at 0xA2. When the read pointer overflows one EEPROM it automatically steps to the next EEPROM. In theory any size could be supported using this code, but I've used 16-bit variables for all of the file pointers introducing a 64KB limit – this should be easily fixable, but I don't own enough memory to test the code myself, so I've left it as it is for the moment.

// The program I use to split VGM files into 32KB chunks.
// Bear in mind that most VGMs are compressed (VGZ): you'll need to decompress them first.
// You can use 7-zip to do so.
using System.IO;
class Program {
    static void Main(string[] args) {
        var SourceFile = @"D:\Documents\Documents\VGM\StrykersRun-title";
        using (var r = new BinaryReader(File.OpenRead(SourceFile))) {
            for (var i = 0; i < int.MaxValue; ++i) {
                var data = r.ReadBytes(32 * 1024);
                if (data.Length == 0) break;
                File.WriteAllBytes(string.Format("{0} [{1}].bin", Path.GetFileNameWithoutExtension(SourceFile), i), data);

To take advantage of the delay between PSG accesses I've implemented a very simple buffering system that queues up a few bytes in advance from the EEPROM. This works well for music, but sampled audio (which involves updating the PSG very rapidly) doesn't work as the code spends too much time waiting for data to be transferred from the EEPROMs.

I've included some recordings of the output below.

The source code can be downloaded from here. If you do try to run it you'll find that it tends to hang when trying to initialise the EEPROM; this is due to the I2C bus being left in an active state by forcefully terminating the program before debugging. I find it helps to program the board, disconnect then reconnect the power supply to the EEPROMs to reset them, then hitting continue in the debugger.

A productive weekend

Wednesday, 3rd May 2006

What with the weekend having an extra Monday tacked on for good measure (Labour Day), I felt the need to be productive.

I also felt the need to listen to VGM files converted to MIDI, so rustled up a VGM to MIDI converter. There already is one (available on the SMS Power! site), but I could never get it to work.
Having never really puzzled out the YM2413 ('OPLL', FM chip) I limited it to the square-wave generating PSG.

First of all, you need to be able to convert a tone register value (from 0 to 1023), the period of the output square wave, to a MIDI key value (0 to 127, where every 12 keys represent an octave).
This is easiest if you have a real frequency (in Hertz) to work with, so I have the formulae:

Frequency = ClockSpeed ÷ (32 × ToneReg)
Key = 12 × Log2|Frequency × Constant|
ClockSpeed is the clock speed of the PSG in Hertz. Constant is a precalculated constant used to scale the frequency to a range so that 440Hz ends up being played as key A, octave 5.

As it is unlikely you'll get a round number with this, I rely on adjusting the MIDI pitch wheel. Now it's a case of detecting attacks (when the volume of a channel increases) and releases (when the volume of a channel is set to 0) to create MIDI key press and release messages.
Percussion (white noise from the PSG) is handled the same way, except that instead of using mapping frequencies to keys and pitch wheels it plays one of 3 different drums corresponding to the 3 different pitches of noise.

It works fairly well, and you can download the software and source from the site here.
You can also listen to some samples:

The problem with sound-related apps is that they don't provide very interesting screenshots, so I took on a little side project that I hoped would. (After all, journals are a bit dull if they're plain text).

tween1.png tween2.png

tween3.png tween4.png

Yep, it's that 3MHz, 32-colour, 8KB powerhouse the Sega Game Gear again.
Looking on, there is only one Game Gear demo on there. If I haven't got it in me to complete a full-blown game, I can at least try and contribute something. rolleyes.gif After all, it's a relatively simple Z80 system to write programs for.

Have the typically poorly-shot video to see it in action:

(The odd flickering horizontal band that sometimes appears is a case of me using up the 8 sprite per scanline ration).

YM2413 (OPLL) Emulation

Monday, 30th January 2006

As (yet another) side project to all this Z80 work, I've also decided to have a stab at the Japanese Master System's FM chip. It's a YM2413, and the documentation on it is fairly tricky to get to grips with - not only thanks to it being in typical Japanese-manual English.

So far, I have this - the VGM player is a bit buggy and extremely primitive (unfinished), but should be enough to demonstrate the current state of the OPLL. You'll need an FM VGM to try it with - the Space Harrier demo from the BIOS sounds pretty good.

If anybody has had any experience with the YM2413, I'd be interested to hear if you had any helpful tips...

In other news, I've been adding multipage program support to Brass, and Latenite has had a lot of debugger integration work done on it. It's still a long slog before it's in a presentable state, sadly.

VGM Player now works!

Tuesday, 27th September 2005

Lots of fixes...
Download here! (New package includes loading music from Fire Track as a sample)

- Fixed reading of files. I'd used 'char' instead of 'unsigned char'. Files
  that played back as plain silence now play back fully, also play at correct
  speed. What a stupid mistake - it's amazing it worked at all!
  I ended up rewriting the entire PSG emulation thinking it was that that was
  to blame... evidently not!
- Halved frequency of noise channel by only switching output once on 2 clocks.
  It was an octave too high before!
- Fixed odd jumping/incorrect (after loop) seek bar.
- Changed [x] exit button to use 'x' character rather than the cross symbol -
  the cross appeared as a "Pt" sign under Windows 2000.
- Removed display of Japanese tags.
- VGM files that loop back to the exact start of the file are now looped rather
  than ignored.
- Added display of system name for noise mode and very stupid autodetection.
- Sound output is now averaged inside each 44100Hz sample to "antialias" the
  output. Sound output that relies on channels at 0 outputting fully now work.
  Sampled audio still sounds a bit noisy/weird. Sound is now output at 16-bit.
- Option to enable/disable sound channels individually.
- Accelerated seeking considerably.


Monday, 26th September 2005

scr_1.png scr_2.png scr_3.png

  • Fixed drunk pilots. New algorithm is about 20 lines shorter, 10 times faster, 5 times simpler. Reason for drunk pilots: drunk coder.
  • Implemented scoring, including:
    • Variable points for shooting enemies.
    • Variable points for shooting objects on the ground.
    • Display of score at top of screen.

  • Updated pause screen, all sprites now remain on-screen, background is less wavy.
  • Trimming of enemy attack times so they follow on from eachother nice and quickly.
  • Numerous minor bugfixes and tweaks.

The score might look nice there, but it really needs to be stuck on a background - any sprites trying to come in are cut off and look really odd popping into existance 8 pixels down from the top of the screen (8 sprite limit, and I have 8 digits in my score). For example, see this:

Look at the top - you can see the nose of a ship appearing, and it looks horribly wrong.

Drawing a black bar might be achieved by swapping name table addresses when I hit a certain scanline, but that's mucky and so far the best result has been 3 or 4 VRAM timing errors per frame and a jumping area of the screen that shows junk. Wonderful!

Download the new build and source.

I also did some more work on my VGM player. It now emulates the PSG correctly! There is one problem still - playback of VGM files that have a specific delay specified. The default timings (1/50th second, 1/60th second) both work beautifully... *sighs* The others play waaaay too fast.
Thankfully, this affects very few VGM files I've tried it with. I also ripped out the tacky visualisations (it's now just a boring console app), improved the seeking code to be time rather than file position based (more accurate) and added support for VGZ files (GZipped-up VGMs) using ZLIB.
If you're in the mood for excellent chiptunes, download the above and the VGM pack to a classic Sega game from SMS Power! - I'm particularly fond of the music from "The Flash". (Ristar's music is also excellent). smile.gif

And now for something completely different

Friday, 23rd September 2005

I felt like a different project for the evening, so started work on a console-based VGM player.

Click to download. [193KB] (Comes with a few sample tunes).

There are a number of (mainly minor) bugs listed in the readme.txt file, but it's not too shabby. It uses SDL to produce the sound (directly writing to the primary buffer). I'd be interested to hear any feedback!

Oh, Fire Track... Well, there's a rip of the BBC Micro version's in-game music (just recorded directly from an emulator, hence the sfx over the top) in the above zip file. Give that a listen!

Subscribe to an RSS feed that only contains items with the VGM tag.

FirstLast RSSSearchBrowse by dateIndexTags