Skip to content
Roger edited this page Dec 19, 2016 · 5 revisions

Introduction

  • There are four known versions of this uCode: the accelerator version, the DMA version, the light version and the SMS version.
  • The accelerator version uses the DSP's accelerator to read sound data, whereas the DMA version reads sound data by DMA transfers.
  • Most Zelda games use the accelerator version. Super Mario Galaxy uses the DMA version.
  • The light version doesn't use exceptions at all and has a different communication protocol which needs less mails from CPU. Luigi's Mansion, Pikmin and some other games use the light version.
  • The SMS version uses a different communication protocol which requires three sync mails every frame instead of two every 16 PBs. Super Mario Sunshine uses the SMS version (hence its name).

Details

Initialization Mails

These are all the mails that the game sends to the DSP upon initialization:

Command 0xE (AKA DsetDMABaseAddr)

00000002
8E000000
90000800 -> Bit0-30: DMA Base address for sound data transfers from main memory

Note that this command is available only in DMA version.

Command 0x1 (AKA DsetupTable)

00000005
81000040 -> Bit0-15: probably number of voices (number of PBs, too)
807B04E0 -> Bit0-30: Address of parameter blocks (PBs) used during mixing
8054CDA0 -> Bit0-30: Address of some ADPCM coef table (1280 bytes)
8054CD60 -> Bit0-30: Address of AFC coef table (64 bytes)
807B64E0 -> Bit0-30: Address of 4 other PBs, maybe used to create reverb?

Continuous Mails

These are all the mails that the game continuously sends to the DSP.

Command 0x2 (AKA DsyncFrame)

00000005
82074000 -> Bit16-23: number of buffers in main memory (each buffer is 160 bytes long)
807AEAA0 -> Bit0-30: Start address of right buffers in main memory
807AEF00 -> Bit0-30: Start address of left buffers in main memory
00000000 -> Unused
00000000 -> Unused

The Legend of Zelda: Wind Waker keeps sending these forever, alternating between three different sets of buffers.

Synchronization Mails

00000000
00000000

00000000
00010000

00000000
00020000

00000000
00030000

Ending Mails

CDD10003 Sent by the CPU after DsyncFrame command has completed (and sent 0xDCD10005 to the CPU).

Notes

The first command sent is 0xE. In the real Zelda ucode, this command is dummy. In the ucode used by SMG, it isn't the case. Here is one of the differences between these two ucodes.

For each command:

  • The CPU sends one mail whose high part is zero and whose low part represents the number of mails that will follow (non-light versions only).
  • The CPU sends one mail whose bits 24 to 27 represent the command.
  • The CPU sends the remaining mails which are the parameters for the command

Notice the last 8 mails. They don't define commands.
After studying the ucode a bit, I noticed that these mails are tied by pairs.
A mail that has its low part empty means that another mail will follow, with a number in its high part.
The number can probably vary from 0 to 15, though here it's only varying from 0 to 3.
When receiving these mails, the ucode seems to store some values from the first mail into memory. These values are used only by command 0x2 (SyncFrame).
The purpose of that whole system is probably to keep mixing synchronous with the game's main loop.

How the Zelda ucode interprets mails

The mails are interpreted by the exception 7 handler. This means that exception 7 is probably triggered when a new mail was received (by DSP).

The first mail received determines how the following ones will be interpreted: 0xXXXXGGGG

If G is zero

The whole mail is likely zero. The following mail will be formatted as follows: 0x000HIIII

The following operation will then be executed:

MEM[0x034E] = ((H+1) << 4);
MEM[0x04FC+H] = I;

If G is non-zero

G determines how much mails will follow.
The first following mail defines the command number, the next ones define additional parameters depending on the command.

Then the mails are stored to memory and will be processed by the message loop.

How to play sound using the Zelda ucode (non-light versions)

First of all, keep in mind that the Zelda uCode is under Nintendo's copyright. We don't recommend releasing apps that would use that uCode. However you're free to play around with it to see how powerful it is.

Initializing the ucode

If you're using the DMA version, send a DsetDMABaseAddr command, by sending the following mails to the DSP:

00000002
8E000000
GGGGGGGG

where G is the DMA base address.

When reading sound data from main memory, the base address G will be added to the reading address.

For no fancy stuff, we'd recommend setting the DMA base address to the beginning of the ARAM.

Finally, you need to send a DsetupTable command:

00000005
8100GGGG
HHHHHHHH
IIIIIIII
JJJJJJJJ
KKKKKKKK

where:

  • G is the number of voices
  • H is the address of the voice parameter blocks (PBs). See below for more info about them.
  • I is the address of a 1280-byte data table. Its purpose isn't known yet.
  • J is the address of the AFC coefficient table (64 bytes). You probably don't need it if you don't use AFC sounds.
  • K is the address of 4 small parameter blocks. Their purpose isn't known yet. They seem to be intended for stuff like reverb.

Playing sound

The DsyncFrame command is responsible for mixing, so if you want to hear any sound, you need to send DsyncFrame commands regularly:

00000005
82GGHHHH
IIIIIIII
JJJJJJJJ
00000000
00000000

where:

  • G is the number of frames
  • H is some unknown number
  • I is the address of right output buffers
  • J is the address of left output buffers

For each frame to render, there is a right buffer of 80 samples, and a left buffer of same capacity.

After you sent a DsyncFrame, you need, for each frame, to send sync mails:

00000000
000GHHHH

The uCode then processes next voices until the current voice number becomes equal to ((G+1) * 16). Then you need to send other sync mails with a higher G. H seems to be a mask so that "useless" voices can be discarded. But we're unsure about how it actually works.

Once all the voices have been processed, the uCode transfers final sound to the output buffers associated with the current frame. Then it sends two mails: 0xDCD10004 and 0xF355FFXX, to indicate the current frame has just been finished, and also triggers an interrupt. Then it processes the next frame.

When the uCode finished processing all the frames, it sends a 0xDCD10005 mail and triggers an interrupt. Then you must send a last mail:

CDD1000G

where G is the task to execute, as follows:

  • 0: Halt
  • 1: Dump
  • 2: Do something and halt
  • 3: Do nothing

This thing seems to be meant for debugging... We recommend that you set G to 3 if you want to continue using the uCode. You can set G to 0, 1 or 2 if you want to debug. Note that G=1 requires that you send 10 other mails. We don't know yet what they are. BEWARE! Do NOT set G above 3. The uCode doesn't check if G is within range 0-3 and setting G above 3 will crash it. (yeah, Nintendo coders never take care about cases like that...)

Phew. Now that your DsyncFrame successfully completed, you have to send another one, and another one...

The parameter blocks

Like the well-known AX ucode, the Zelda ucode uses one parameter block (PB) per voice. (The number of voices is set by DsetupTable command). It also uses 4 much smaller PBs which seem to be used for something like reverb...

But let's work on the most important PBs, the voice ones. They can be found at the first address passed by DsetupTable command. Each block is 384 bytes long (i.e. 192 words).

Here is a table of what has been figured out inside these PBs (some parts of it may be wrong):

Read/Write Part

Offset (words) Size (words) Description
0x00 1 Status (0=stop, 1=play)
0x01 1 Key off (1=stop)
0x02 1 Sampling ratio integer part
0x03 1 Unknown/Unused
0x04 1 Reset flag (1=counters need to be reset)
0x05 1 Set to 1 when end was just reached
0x06 1 Blank flag (1=voice is blank)
0x07 1 Unknown
0x08 1 Sound type? (0x0D00=music, 0x4861=sfx)
0x09 1 Left volume 1 (for volume mode 0)
0x0A 1 Left volume 2 (for volume mode 0)
0x0B 2 Unknown
0x0D 1 Right volume 1 (for volume mode 0)
0x0E 1 Right volume 2 (for volume mode 0)
0x28 1 Volume coefficient indexes (for volume mode 1)
0x29 1 Some volume factor (for volume mode 1)
0x2A 1 Volume factor 1 (for volume mode 1)
0x2B 1 Volume factor 2 (for volume mode 1)
0x2C 1 Volume mode (0=direct, 1=uses table)
0x30 1 Sample position fractional part
0x31 1 Unknown/Unused
0x32 1 Current block? (used by AFC decoder)
0x33 1 Fixed sample for blank voices
0x34 2 Loopstart offset (in samples)
0x36 2 Unknown
0x38 2 Read offset (in bytes)
0x3A 2 Remaining length (in samples)
0x3C 42 Unknown
0x66 1 YN2 (for AFC sounds)
0x67 1 YN1 (for AFC sounds)
0x68 24 Unknown

Read-only Part

Offset (words) Size (words) Description
0x80 1 Sound format
0x81 1 Repeat mode (0=one-shot, nonzero=loop)
0x82 1 YN1 reload value (for looping AFC sounds)
0x83 1 YN2 reload value (for looping AFC sounds)
0x84 4 Unknown
0x88 2 Original loopstart offset (in samples)
0x8A 2 Length (in samples)
0x8C 2 Start offset (in bytes)
0x8E 2 Unused
0x90 48 Padding

Notes:

  • The last 64 words (128 bytes) are read-only. The ucode reads them from memory but never writes them back.
  • The read offset and start offset are exprimed in bytes. They're offsets in ARAM, except in SMG ucode where they're offsets in main memory (minus DMA base set by command 0xE).
  • The lengths and loop start positions are exprimed in samples.
  • When processing blank voices the ucode fills the temporary buffer with the value at offset 0x33 in PB.

List of available sound formats:

  • 0x0000: Square wave
  • 0x0001: Saw wave
  • 0x0002: Square+saw?
  • 0x0003: Rectangle wave
  • 0x0004: compressed / unknown
  • 0x0005: Low bitrate AFC ADPCM
  • 0x0006: Fills a 80 byte buffer with PB.RatioInt
  • 0x0007: compressed / unknown
  • 0x0008: PCM8
  • 0x0009: Normal bitrate AFC ADPCM
  • 0x000A: compressed / unknown
  • 0x000B: compressed / unknown
  • 0x000C: compressed / unknown
  • 0x0010: PCM16
  • 0x0020: Raw PCM16 from RAM (not resampled)
  • 0x0021: Raw PCM16 from RAM

Unknown Registers?

The Zelda ucode accesses a memory region at 0xFF8X. At glance you may think it's accessing unknown DSP registers, but this is not the case. This region is always accessed by lrs/srs opcodes, while CR is set to 0x0004. Sometimes the ucode uses the same opcodes to access hardware regs, but those times CR is set to 0x00FF.

Actually, for lrs/srs opcodes, the top byte of the address isn't obtained by sign-extension as we used to believe. but from the bottom byte of CR.

This explanation makes sense. The ucode doesn't read from 0xFF8X but from 0x048X. It also accesses various other addresses using lrs/srs, while CR is set to 0x0004 too. The ucode is actually using these opcodes to access the current voice PB (which is stored at 0x0400). This is meant as an optimization of space, and possibly speed as well, since lrs/srs opcodes occupy only one word, while opcodes reading from an absolute address (such as lr/sr) occupy two words. It might be possible that lrs/srs are faster, as well.