2013smscsvread, SMS Binary Music Modules and Music Player Documentation

12th April 2013

My very first bit of Z80 programming was the music player used in Gravity Beam: Master Gaiden. The source file calls it SEGA MASTER SYSTEM GAME AUDIO THING, but I usually call it 'SMS music'. There are a number of components that support the player: a user-friendly method for creating modules, a convertor that converts these user-friendly modules into a dense binary format, a player that reads the binary format and updates a state machine with the correct values using appropriate timing, and an instrument runtime that sets the parameters of the SN76489 sound chip in the SMS based on this state.


Creating Modules

I use Microsoft Excel to write my music modules. You could use LibreOffice Calc if you were a CRAZY PERSON.

Why? Because it's easy to use. It has an arbitrarily specifyable grid interface. Supports copy and paste. The actual conversion 'surface' the convertor expects is .csv, so you subsitute anything you like there, or write your own editor if you like.

Here's the 'plot_2' music from the Gravity Beam: Master Gaiden title screen. You can see that the layout is similar to that a MOD editor like OctaMED or OpenMPT. There are three tone channels and a noise channel, corresponding directly to the channels of the SN76489 in the SMS. Each channel is capable of playing a single tone of a single instrument channel at once.

The module player works as follows: Every PAL frame, a counter is incremented. When that counter reaches the value of TEMPO, the playback cursor plays the next row of the module. When a SMS binary music module is prepared for playback, the next call to music_tick will play the very first row immediately.

For each row, for each channel, there are five properties that can be set:

When a property is set, its value remains constant until it is explicitly re-set. The exception to this is the RNP, as an instrument program can set the RNP to OFF when the note age reaches some value. Only the property changes specified in the editor are reflected in the output file: use this to reduce the size of your binary modules by leaving cells blank when you can.

The percussion channel works like a tone channel, except the RNP controls which drum is used. The instrument program number is ignored. You'll notice in the top right of the screenshot I've written what notes reflect which percussion instrument. These are percussion programs I have written. Because the SMS only has a couple of noise pitch paramters, a lot of these sound very similar to each other. Use your imagination, together with the volume column, to create compelling drum effects!

The leftmost column is used for labels. These are song positions you can instruct the playback cursor to return to using a GOTO directive in the module editor (you can't access labels from assembly code as they no longer exist at that point). The label end is used to indicate the final row of module data to parse. Everything in the file after that point is ignored. The label must be present or the parser will enter an infinite loop and would need to be manually terminated.

There are a number of special directives that you can use. These go in the Directives column on the right, directly after the BEN column of the Percussion channel. To specify a directive, type its name in the cell, and its argument in the cell immediately following. To specify two directives on the same row, place the name of the second directive in the cell immediately to the right, and so on.

It sounds like a lot, but it's really easy! Let's take another look at the 'plot_2' music and I'll show you how it works.

The first row contains the label 'begin'. This is just my convention. My ingame music doesn't have an intro sequence that's played only once, so I would use GOTO begin to loop that song. Because the 'plot_2' music contains a short ascending 'dooooop!' at the beginning, I instead use GOTO looppoint.

You should use X to silence channels that you're not using at the beginning of a song. If you don't, the RNP and other parameters will be maintained from the previous song (safe values if you've audio_init immediately before, or garbage if you haven't).

For this song, INS 3 is a normal steady tone that fades out after a short amount of time. Think of it like a piano note. For the first eight lines, I increase the RNP and VOL while repeatedly setting the AGE to 0. This creates an ascending sliding legato note.

For the main body of the song, Channel 0 is the bass notes and Channel 1 is the main melody. Channel 2 contributes harmony during the second repeat of the first section. Notice that I've set the instrument to 3 on the channels explicitly again on this row. If you're going to give a row a label in preparation for GOTO, you should put INS and VOL values on it, otherwise the values from before the jump will be maintained.

The remainder of the song that's visible on this page doesn't use INS or VOL changes, which results in a very small file size!

Notice that at the end of the column for Channel 1 I change INS to 1. Instrument 1 is a note that doesn't fade out over time but instead alters its SN76489 tone value slightly over time to make a flute-like wobbling sound. If you read down the column of Channel 1, you can see the GBMG theme there 'dah---dah---da-dadadadah-dah--dah--dah-dah-dah-daaaaaaaaaaaaaaah'.

The percussion channel shows the drumrolls as C-0 and D#0 notes.

My SMS music format doesn't have crazy mixed up table or pattern support like Soundmon for the Amiga or Goattracker for the C64, which means that both source and binary versions of a song will contain repeated data, but on the plus side they're readable by humans. Plus, the player doesn't have a zillion levels of indirection to get to the actual data.

There's no way to preview the song in Excel. The simplest way is to export it, convert it and include the binary song into an SMS rom and emulate it. (The hardest way is to write a binary module player that emulates the SN76489, and contains reimplementations of all your asm instrument and percussion programs. I did that, but it's out of date now.)

Converting Modules

Binary versions of music modules are produced by 2013smscsvread.exe. This is a program used to convert plain-text music modules stored as .csv files into super-hyper compact .bin SMS music module files compatible with the SMS music module player included as part of Gravity Beam: Master Gaiden.

First, save your Excel file as a .csv from Excel. It'll complain about the loss of formatting, but that's okay, we don't need it.

Then convert it with 2013smscsvread.exe. It will produce a .bin which you can then incbin into your SMS program.

Format: 2013smscsvread.exe input.csv output.bin

If 2013smscsvread.exe hangs, it's probably because you didn't include a row labelled 'end'.

Playing Modules

First, call audio_init to reset the state of the audio system. You should do this as part of your very first initialisation code.

To select a song for playback, load the pointer to the start of your binary song into HL and call music_select_song. This resets the music playback system to the start of your song. At this point, the playback will be halted. To begin playback, call music_resume.

My player is not interrupt driver. To ensure smooth music playback, the following should be called every frame:

I call these things together at the end of a frame just before I halt for a vwait. The routines clobber the shadow registers, clobber the main registers, and mess around with out instructions. If you want to rewrite these things to be triggered on an interrupt, you'll have to work out how to do that safely yourself.

The complete interface is documented in the source file audio.z80asm. If you want to know how to do something, check how Gravity Beam: Master Gaiden does it!


Binary File Format

An SMS binary music module is a contiguous series of variable length instructions for the SMS music player. When the music_tick_counter timer reaches the current TEMPO value, the music player continually reads instructions from the binary file until it reaches a WAIT or STOP instruction.

An instruction is either a 'playback command' or a 'directive', depending on whether or not the high bit of the first byte is set. If the high bit is cleared, the command is a 'playback command', otherwise it is a directive.

Playback command:

  byte               

    0     1     2     3     4     5   

[ COMMD (RNP) (AGE) (INS) (VOL) (BEN) ]

Playback commands can be anywhere from between 2 to 6 bytes long. The command byte COMMD contains a bitmask indicating which channel parameters it contains as a payload.

The structure of COMMD is set according to the following bitmask:

The structure of the command byte is %DBVIAPCC

DBVIAP are bits indicating whether this channel event packet is followed by those parameters.

 7 D is directive

 6 B is BEND

 5 V is VOLUME

 4 I is INSTRUMENT

 3 A is AGE

 2 P is PITCH

10 CC is channel as a 2-bit integer.

Only those bytes which are specified in the bitmask are present. Elements that are present always appear in the order PITCH, AGE, INSTRUMENT, VOLUME and BEND. The elements do not have to be contiguous in the bitmask to be contiguous as payload bytes: it is possible to have a command which sets the PITCH and the INSTRUMENT but not the AGE. All parameters are unsigned 8-bit integers. A single byte playback command with no payload is degenerate.

Example:

  byte               

    0     1     2     3      

[ COMMD (RNP) (INS) (VOL) ]

[  $34   $10   $03   $05  ]

$34 is a playback command COMMD byte with bits %00110100: the channel is 0, the PITCH, INSTRUMENT and VOLUME are present in that order.

The PITCH is $10 (this corresponds to note 16, which is E-1). The INSTRUMENT is 3 and the VOLUME is 5. This is the first Note On event in the 'plot_2' music file; you can see its plain-text counterpart in the screenshot above.

  byte               

    0     1     2     3      

[ COMMD (RNP) (AGE) (VOL) ]

[  $2C   $11   $00   $06  ]

$2C is a playback command COMMD byte with bits %00101100: the channel is 0, the PITCH, AGE and VOLUME are present in that order.

The PITCH is $11 (this corresponds to note 17, which is F-1). The AGE is 0: specifying a PITCH and setting the AGE to 0 means that this is a 'legato mode' note! The VOLUME is 6. This is the second row in the 'plot_2' music file.

Directives:

  byte               

    0     1     2     3     4     5   

[ WAITv ]

A WAIT command is encoded as a single byte in the range $81 to $EF. The number of rows to wait is stored in WAITv AND $7F. This is the number of rows to wait before attempting to read any more music instructions (including the current row). When consecutive rows in the editor contain instructions, they are separated by $81, representing the time between the current row and the next. When the music playback cursor encounters WAIT, instruction reading is halted and music_tick will return. You shouldn't try to place a WAIT manually in the Excel editor as WAIT commands are automatically placed after every row containing instructions or directives. The convertor will read how many blank rows are between successive instructions and compress large blocks of long notes/silence into a single WAIT byte.

  byte               

    0     1     2     3     4     5   

[ GOTO  lower upper ]

[  $FD  lower upper ]

A GOTO command is encoded as a $FD byte, followed by an offset in bytes to jump to. lower and upper are combined to make a 16-bit unsigned value. The offset is relative to the beginning of the file. This means that the byte sequence $FD $00 $00 restarts the song from the very beginning. This command immediately resets the music instruction reading cursor, and instruction reading immediately resumes at the new address.

  byte               

    0     1     2     3     4     5   

[ TEMPO value ]

[  $FE  value ]

A TEMPO command is encoded as a $FE byte, followed by the TEMPO value to use as an unsigned 8-bit integer.

  byte               

    0     1     2     3     4     5   

[ STOP  ]

[  $FF  ]

A STOP command is encoded as a $FF byte by itself. When a STOP is encountered, the music playing flag is set to false and instruction reading halts.

If a binary music module does not end in either a GOTO or a STOP, either something is very wrong, or you're doing something very tricky like having one song lead into another. Be careful if you do this because the music song start base pointer will still be pointing at the earlier song (because you didn't call music_select_song) so GOTO commands will still think you're in the first song and will malfunction in the second.

Command bytes in the range $F0 to $FC are left unused currently.