SMPS/Song data

From Sega Retro

Back to: SMPS.

Regardless of what variant of SMPS is used, song data itself is almost nearly identical. The elementary unit of the song data is the byte; its value determines what the byte is:

  • value in range [$00, $7F]: Duration value
  • value in range [$80]: Rest (counts as a note value)
  • value in range [$81, $DF]: Note value
  • value in range [$E0, $FF]: Coordination flag

Duration values, rests, and note values are described in the Note Editing section; coordination flags in the Coordination Flags section.

Note Editing

Notation is SMPS usually follows the same general formula:

dc.b Note Byte, Duration Byte

However, if you already have a note byte, you can string multiple durations together to repeat notes:

dc.b Note Byte, Duration 1, Duration 2, ...

Every time a duration byte is read, the note is retriggered — that is, each duration value represents a new note play — unless the duration is prefixed by the $E7 (no attack) coordination flag. Do not put $E7 between a note byte and a duration byte! Likewise, if you already have a duration defined, you can string multiple notes together to make them use the same duration:

dc.b Note Byte, Duration Byte. Note 2, Note 3, ...

The rules for $E7 apply here too.

Here are some examples of valid note definitions:

dc.b $81, $01, $04, $03, $02

This particular combination of values will play note $81 at multiple durations--first 01, then 04, then 03, and then 02. As long as a note is defined, its value will stay in memory and be repeated on any subsequent duration parsed in the channel.

dc.b $82, $02, $81, $82, $83, $84, $86, $8F

This combination of values will play all of the defined notes with a duration of 02--first 82, then 81, then 82, then 83... so on and so forth. As previously stated, the first definition in a string of values must always be a note. When repeating notes instead of definitions to omit duplicate data, the string will be ended by placing a new duration value.

Note Equivalents

For tone channels (synthesis channels and the Sega CD PCM), notes represent actual music notes. In these cases, getting the note equivalent is simple: subtract $81 and divide by 12. The quotient gives you the octave number and the remainder gives you the note (0 = C, 1 = C#, 2 = D, etc.). For your convenience, however, here is a table.

Value Note Value Note Value Note Value Note
$81 C0 $99 C2 $B1 C4 $C9 C6
$82 C#0 $9A C#2 $B2 C#4 $CA C#6
$83 D0 $9B D2 $B3 D4 $CB D6
$84 D#0 $9C D#2 $B4 D#4 $CC D#6
$85 E0 $9D E2 $B5 E4 $CD E6
$86 F0 $9E F2 $B6 F4 $CE F6
$87 F#0 $9F F#2 $B7 F#4 $CF F#6
$88 G0 $A0 G2 $B8 G4 $D0 G6
$89 G#0 $A1 G#2 $B9 G#4 $D1 G#6
$8A A0 $A2 A2 $BA A4 $D2 A6
$8B A#0 $A3 A#2 $BB A#4 $D3 A#6
$8C B0 $A4 B2 $BC B4 $D4 B6
$8D C1 $A5 C3 $BD C5 $D5 C7
$8E C#1 $A6 C#3 $BE C#5 $D6 C#7
$8F D1 $A7 D3 $BF D5 $D7 D7
$90 D#1 $A8 D#3 $C0 D#5 $D8 D#7
$91 E1 $A9 E3 $C1 E5 $D9 E7
$92 F1 $AA F3 $C2 F5 $DA F7
$93 F#1 $AB F#3 $C3 F#5 $DB F#7
$94 G1 $AC G3 $C4 G5 $DC G7
$95 G#1 $AD G#3 $C5 G#5 $DD G#7
$96 A1 $AE A3 $C6 A5 $DE A7
$97 A#1 $AF A#3 $C7 A#5 $DF A#7
$98 B1 $B0 B3 $C8 B5

This base value is then added to a per-channel counter, the channel key displacement, which is then turned into a frequency value for the respective sound chip. Another special value, the channel frequency displacement, is added to this frequency value to define the final frequency of output sound.

For pure sample channels (the YM2612 and 32X), notes represent indexes into the DAC array; simply subtract $81 to get the (offset 0) index.

Coordination flags

Coordination flags take the form

dc.b Coordination Flag Byte
dc.b Coordination Flag Byte, Parameter Byte(s)

Mappings and definitions have changed between different SMPS versions; the differences are noted in the table:

Flag SMPS-68000 (and some SMPS-Z80s like Sonic 2) SMPS-Z80/SMPS-32X/SMS(??)/GG(??) SMPS-PCM
$E0xx Panning, AMS, FMS. xx takes the binary form lraaa0ff:
  • l - Left channel on
  • r - Right channel on
  • aaa - AMS (YM2612 only)
  • ff - FMS (YM2612 only)
$E1xx Set channel frequency displacement to signed xx
$E2xx Unknown Unknown, though $E2FF is fade-in to previous song (needed on DAC channel) Unknown
$E3 Return from subroutine Unknown
$E4 Fade-in to previous song (needed on DAC channel) Unknown No operation
$E5xx Change tempo divider to xx No operation; there is no parameter byte in this case
$E6xx Change channel volume by xx; xx is signed
$E7 Prevent next note from attacking Unknown, possibly also no attack
$E8xx Set note fill amount (??) to xx Unknown, possibly the same
$E9xx Add xx to channel key displacement; this value accumulates (that is, if the accumulated value was 4 and xx is 3, the new accumulated value is 7) Unknown No operation; there is no parameter byte in this case
$EAxx Set music tempo to xx Unknown, possibly the same
$EBxx Change Tempo Modifier to xx for ALL channels Unknown, possibly the same
$ECxx Change channel volume to xx; xx is signed(??) No operation; there is no parameter byte in this case
$ED Unknown No operation
$EE Something with Voice Selection No operation
$EFxx Change current voice (YM2612/SN76489)/sample (RF5C164) to the one at table index xx
  • 00 is the first voice in the bank. So in other words, subtract 01 from the value to get the voice in the bank you want.
$F0wwxxyyzz Modulation
  • ww - Wait for ww period of time before modulation starts
  • xx - Modulation Speed
  • yy - Modulation change per Mod. Step
  • zz - Number of steps in modulation
Unknown; there are no parameter bytes in this case
$F1 Turn on modulation Unknown; same as $F0
$F2 Stop the track Unknown; same as $F1
$F3xx Change current PSG noise to xx (For noise channel)
  • Applicable values are E0 - E7
  • Anything beyond E7 will wrap back to the E0 waveform
No operation; there is no parameter byte in this case
$F4xxxx Turn off modulation; there are no parameter bytes in this case Same as $F6xxxx
$F5xx Change current PSG tone to xx(??) No operation; there is no parameter byte in this case
$F6xxxx Jump to address xxxx
$F7xxyyzzzz Repeat section of music
  • xx - Loop index, for loops within loops without confusing the engine.
    • EXAMPLE: Some notes, then a section that is looped twice, then some more notes, and finally the whole thing is looped three times.
      The "inner" loop (the section that is looped twice) would have an xx of 01, looking something along the lines of F70102zzzz, whereas the "outside" loop (the whole thing loop) would have an xx of 00, looking something like F70003zzzz.
  • yy - Number of times to repeat
    • NOTE: This includes the initial encounter of the F7 flag, not number of times to repeat AFTER hitting the flag.
  • zzzz - Position to loop back to
$F8yyyy Call subroutine at address yyyy
$F9 Unknown Return from subroutine
$FAxx Unknown, it is unknown whether or not there are parameter bytes in this case Unknown
$FBxx Unknown Add xx to channel key displacement; this value accumulates (that is, if the accumulated value was 4 and xx is 3, the new accumulated value is 7)
$FCyyyy Unknown, it is unknown whether or not there are parameter bytes in this case Loop Sound Effect from pointer yyyy. Unknown, possibly loop SFX
$FD Unknown No operation
$FE Unknown Invalid
$FF Unknown Invalid

Notes on $E0

On the Mega Drive, only YM2612 channels (both FM synthesis and DAC) can be panned. On the Master System, SN76489 channels cannot be panned; on the Game Gear, they can. Both 32X PWM and RF5C164 PCM channels can be panned.

OutRun on Mega Drive stores DAC panning setup in its DAC table, therefore its DAC channel cannot be panned and you will need to reconfigure the panning when importing from OutRun.

Note on $F3

On the Mega Drive and 32X, the $F3 flag determines whether the composer wishes to use up to three tones or up to two tones and one noise on the SN76489. You may freely use a channel's tones until $F3 is issued, at which point the channel is changed to a noise channel — this cannot be undone unless the song is started over or a new song is played. On the Master System and Game Gear, the third channel is always tone and the fourth channel is always noise.

References