Programming the MSU1

From The Pile
Jump to: navigation, search


What the MSU1 is

The MSU1 is an expansion chip, not entirely unlike the SuperFX, SDD-1, CX4 or the DSP series used in games such as Yoshi’s Island, Star Ocean, Mega Man X 2 and most games with scenes in Mode 7. Consequently, it is a part of the game cartridge. A common misconception is that supporting the MSU1 in hardware would require modifying the main SNES unit, and that it therefore does not “count”. Unfortunately for those who would make this mistake and stick to it, that would disqualify the expansion chips listed above as well. Perhaps the misunderstanding occurs because in emulation, it is the emulator itself that needs to be modified to support the MSU1 – just like it’s the emulator itself that brings support for the SuperFX et al.

A little history

A lot of people don't seem to get the point of the MSU1 expansion chip byuu designed. A common complaint is that SNES games with CD-quality background music doesn't go together at all.

On one side, there was the Sega Mega Drive, also known as the Sega Genesis. This was, like the SNES, a cartridge-based game console. The Mega CD peripheral added increased storage size and CD-Audio capabilities to it, keeping the rest of the system mostly the same. A similar thing happened with the TurboGrafx-16/PC-Engine, which also received a CD-ROM attachment, to pretty much the same effect.

Did you know that Sony was contracted to produce a CD-ROM attachment for the SNES, having previously supplied the system's sound hardware? It didn't work out. But consider this: if Sony didn't try to claim complete control over all titles on the SNES CD, the SNES would have a larger storage size and CD-Audio capabilities -- just like the Genesis and PC-Engine had.

MSU1 basically delivers what Sony was supposed to.

What the MSU1 can do

The MSU1 expansion chip offers up to four gigabytes of data storage in a single stream, which can be used to store, for example, full motion video data. More popularly, it offers CD-quality music playback, leaving the SNES' own SPC700 sound chip free to do better sound effects and jingles. Another neat trick is to store program data on it, copy this data into the SNES' main RAM and execute it from there.

As a practical example, the MSU1 can be used to run full motion video games such as The 7th Guest[1], which was at one point supposed to be released on the SNES CDROM (specifically, the attempt that would later become the Philips CD-i system -- Sony wasn't the only player here).

It can not be used as RAM. That is, it can't be used to store four gigabytes of saved games. It also can't be used to play more than one audio track at a time -- while playing background music on the MSU1, sound effects and jingles must remain on the SPC700. On the other hand it could be turned around, playing (sequenced) background music on the SPC700 and a voice track on the MSU1 during a cutscene. Or you could cross-fade between the background music on the MSU1 and another piece of music on the SPC700 as your RPG character approaches a performer on the street.

Register reference

The MSU1 registers range from $2000 to $2007. Their functions are different between reading and writing.[2][3]

Address Reading Writing
$2000 Status port (MSU_STATUS) Data seek port (<tt>MSU_SEEK)
$2001 Data read port (MSU_READ)
$2002 Identification (MSU_ID)
$2004 Audio track (MSU_TRACK)
$2006 Audio volume (MSU_VOLUME)
$2007 Audio state control (MSU_CONTROL)

An explanation of all the registers and their dual functions is given below.

Status port ($2000, MSU_STATUS)

The status port has the following bit layout:

7 6 5 4 3 2 1 0
Data busy Audio busy Audio repeat Audio playing Track missing Revision

The revision part will be equal to 1, unless a new version of the MSU1 specification is released, which may add more features. A common usage for the status port is to lock the SNES CPU after setting an audio track or specifying a seek target on the MSU1 Data file. This is a rather important step to include if you're aiming to support the MSU1 in hardware. The "track missing" bit is set if, after seeking to a given audio track, that track is found to be unavailable. This allows selectively falling back to SPC700 sound.

Data read port ($2001, MSU_READ)

Reading from the data read port returns the next byte of data from the MSU1 Data file and increments the stream port's address automatically. Reads have no effect if the "data busy" bit is set on the status port.

Identification ($2002 to $2007, MSU_ID)

Reading from $2002 to $2007<tt> in order returns the string "<tt>S-MSU1" in ASCII. This allows the program to detect availability and as such offer compatibility fallbacks.

Data seek ports ($2000 to $2003, MSU_SEEK)

These four registers combine to one 32-bit address, with $2000 being the least significant byte, up to $2003. Writing to $2003 will trigger the seek, setting the "data busy" bit on the status port until done. Notice: on version 1 implementations, the system must be primed by seeking to 0.

Audio track ($2004 and $2005, MSU_TRACK)

These two registers combine to one 16-bit address, with $2004 being the least significant byte. Writing to $2005 will trigger the track change, setting the "audio busy" bit on the status port until done. The currently playing track is stopped.

Audio volume ($2006, MSU_VOLUME)

Sets the audio playback volume on a linear scale. A value of 0 is 0% (completely silent), 127 is 50% and 255 is 100% (full volume).

Audio state control ($2007, MSU_CONTROL)

The audio state port has the following bit layout:

7 6 5 4 3 2 1 0
Unused Repeat Playing

Writing to the audio state port has no effect if the "audio busy" bit is set on the status port. If the play bit is not set, the music is effectively paused. The upper six bits are reserved for future use -- they must be set to zero to ensure proper operation.


An MSU1-enabled SNES ROM requires the following things:

As a regular ROM

  • The main ROM image, gamename.sfc
  • A data file, gamename.msu
  • Optionally, one or more gamename-#.pcm files with the audio tracks
  • An XML mapping file to specify that the emulated game cartridge contains an MSU1 unit in the first place, gamename.xml

As a cartridge folder

  • The main ROM image, program.rom
  • A data file, msu1.rom
  • One or more track-#.pcm files
  • An XML mapping file, manifest.xml

The audio tracks are 44.1 kilohertz, 16-bit stereo uncompressed unsigned PCM files in little-endian order, left channel first, with a simple eight-byte header. The first four bytes spell out "MSU1" in ASCII. This is followed by a 32-bit unsigned integer used as the loop point, measured in samples (a sample being four bytes) -- if the repeat bit is set in the audio state register, this value is used to determine where to seek the audio track to[4].

Creation of audio tracks can be done easily by using the wav2msu tool.

Example manifest

<?xml version="1.0" encoding="UTF-8"?>
<cartridge region="NTSC">
<!-- This part depends on the ROM. This specifically is a typical LoROM game with 8 KiB of SRAM. -->
<map mode="linear" address="00-7f:8000-ffff"/>
<map mode="linear" address="80-ff:8000-ffff"/>
<ram size="0x2000">
<map mode="linear" address="20-3f:6000-7fff"/>
<map mode="linear" address="a0-bf:6000-7fff"/>
<map mode="linear" address="70-7f:0000-ffff"/>
<map mode="linear" address="f0-ff:0000-ffff"/>
<!-- This is the important bit -->
<map address="00-3f:2000-2007"/>
<map address="80-bf:2000-2007"/>


Register definitions


.define MSU_STATUS	$2000
.define MSU_READ $2001
.define MSU_ID $2002
.define MSU_SEEK $2000
.define MSU_TRACK $2004
.define MSU_VOLUME $2006
.define MSU_CONTROL $2007


MSU_STATUS	=	$2000
MSU_READ = $2001
MSU_ID = $2002
MSU_SEEK = $2000
MSU_TRACK = $2004
MSU_VOLUME = $2006


#define MSU1SEEK	*(unsigned long*)0x2000
#define MSU1TRACK *(unsigned short*)0x2004
#define MSU1VOLUME *(unsigned char*)0x2006
#define MSU1PLAY *(unsigned char*)0x2007
#define MSU1STATUS *(unsigned char*)0x2000
#define MSU1DATA *(unsigned char*)0x2001
//A.N.: I don't even remember why I did it this way.
#define MSU1IDENT0 *(char*)0x2002
#define MSU1IDENT1 *(char*)0x2003
#define MSU1IDENT2 *(char*)0x2004
#define MSU1IDENT3 *(char*)0x2005
#define MSU1IDENT4 *(char*)0x2006
#define MSU1IDENT5 *(char*)0x2007

Checking for MSU1 availability


lda MSU_ID
cmp #$53 ; 'S'
bne NoMSU ; Stop checking if it's wrong
lda MSU_ID+1
cmp #$2D ; '-'
bne NoMSU
lda MSU_ID+2
cmp #$4D ; 'M'
bne NoMSU
lda MSU_ID+3
cmp #$53 ; 'S'
bne NoMSU
lda MSU_ID+4
cmp #$55 ; 'U'
bne NoMSU
lda MSU_ID+5
cmp #$31 ; '1'
beq NoMSU
; Do something with this fact here.
; Do something with this fact here.


int msu1available()
if( MSU1IDENT0 == 'S' &&
MSU1IDENT1 == '-' &&
MSU1IDENT2 == 'M' &&
MSU1IDENT3 == 'S' &&
MSU1IDENT4 == 'U' &&
MSU1IDENT5 == '1' )
return 1;
return 0;

Playing a track


lda #$FF
ldx #$0001 ; Writing a 16-bit value will automatically
stx MSU_TRACK ; set $2005 as well, so this is easy.
lda #$01 ; Set audio state to play, no repeat.
; The MSU1 will now start playing.
; Use lda #$03 to play a song repeatedly.


void msu1play(unsigned int track)
MSU1TRACK = track;
/* TODO: edit SMO-ckery to load a message from somewhere not 0x0000
* and as such have a proper busy loop. Confirm if the below would
* do.

/* POSSIBILITY: for version 2 systems, let this function return bool.
* If so, switch the results around and have true mean "it worked"?

//{ /* busy loop */ }

Seek to a specific point in the data track


; Seeks to $0000:0410, just as an example.
ldx #$0410
ldx #$0000
stx MSU_SEEK+2
- bit MSU_STATUS ; Wait for the Data Busy bit to clear.
bmi -
; We can use "Branch if Minus" for cheap because the Data Busy
; bit happens to be the same as the sign bit in a Two's
; Complement number:'s_complement


void msu1seek(unsigned long offset)
MSU1SEEK = offset;
//{ /* busy loop */ }

Load a tile map


; Loads a tile map of $800 bytes in size to offset $4000.
; Assumes the data stream is already at the correct seek position.
ldx #$4000 ; Set starting point
ldy #$0000 ; Initialize counter
- lda MSU_DATA ; Grab tile byte
lda MSU_DATA ; Grab attribute byte
cpy #$0800
bne -
; Loading character data is basically the same, but without
; tile/attribute distinction. Note that the first few bits of
; the attribute byte are more tile indices, but this would only
; complicate the descriptions.

Load a color palette


; Loads a single color palette strip (16 colors) to the second palette index.
; Assumes the data stream is already at the correct seek position.
lda #$10 ; Set starting point - color #16
ldy #$0000 ; Initialize counter
- lda MSU_DATA
cpy #$20 ; Colors are 16-bit values, so we must copy 32 bytes, not 16.
bne -

Loading some data


Include code for a simple video player? I’m guessing it’d be like… load a frame count, then continuously load bitmap and palette data into the appropriate parts of VRAM until you’re out of frames?

More fun times: a SeekToID routine that, given a table of pointers at the start of the data file, seeks to a specific entry?


Personal tools