Difference between revisions of "Sonic Jam/Sounds"
From Sonic Retro
m (→SWORLD.SND Map: Typo) |
(Added information on how Music and SFX are handled in Sonic World, and by extension, information on the Saturn's implementation of the MIDI standard. Minor edits to the Samples section.) |
||
Line 1: | Line 1: | ||
__NOTOC__ | __NOTOC__ | ||
{{SCHG SJ}} | {{SCHG SJ}} | ||
− | ==Samples== | + | ==PCM Samples== |
− | According to my research so far, most samples are in a signed 8-bit mono format, with a sample rate of 22050Hz. Each byte is a building block ( | + | According to my research so far, most PCM samples are in a signed 8-bit mono format, with a sample rate of 22050Hz. Each byte is a building block (one "sample") of the PCM sample as a whole. |
There are some exceptions, though. | There are some exceptions, though. | ||
− | Some samples are "stereo", in that the sample is in the left ear, while there's data/code in the right ear. The stereo format has every other sample starting with the first be in the left ear, and every other sample starting with the second be in the right ear. Due to every other sample being used, these have a halved sample rate; 11025Hz instead of 22050Hz. | + | Some PCM samples are "stereo", in that the sample is in the left ear, while there's data/code in the right ear. The stereo format has every other sample starting with the first be in the left ear, and every other sample starting with the second be in the right ear. Due to every other sample being used, these have a halved sample rate; 11025Hz instead of 22050Hz. |
In addition, some simply have different sample rates despite being mono. | In addition, some simply have different sample rates despite being mono. | ||
Line 12: | Line 12: | ||
==SWORLD.SND Map== | ==SWORLD.SND Map== | ||
− | Here's a map of samples that can be found in SWORLD.SND. Start and end addresses are inclusive; they're part of the sample. | + | Here's a map of PCM samples that can be found in SWORLD.SND. Start and end addresses are inclusive; they're part of the sample. |
NOTE: Sample start and end times are currently estimates based on looking at the waveform and patterns in the hex. Finding the exact start and end of each sample will likely require digging into the code. | NOTE: Sample start and end times are currently estimates based on looking at the waveform and patterns in the hex. Finding the exact start and end of each sample will likely require digging into the code. | ||
Line 100: | Line 100: | ||
| $4C721 || $4ED8D || Jump || | | $4C721 || $4ED8D || Jump || | ||
|} | |} | ||
+ | |||
+ | ==Music and SFX== | ||
+ | |||
+ | The music in Sonic World (and interestingly, the SFX in both Sonic World and the Genesis ports) is performed in real-time via a variation of the MIDI standard (as opposed to just playing back a prerecorded audio file). | ||
+ | |||
+ | A Saturn MIDI file (at least as Sonic Jam handles them) consists of a Header, and a series of Tracks, which consist of a Tempo Track, and Play Data. | ||
+ | |||
+ | ===MIDI Header=== | ||
+ | |||
+ | A MIDI Header is very simple, consisting of only two components: | ||
+ | {|class="prettytable" style="width:auto;" | ||
+ | ! Function || Size || Notes | ||
+ | |- | ||
+ | | Number of Tracks || word || | ||
+ | |- | ||
+ | | Offset of Track 1 || long || From the top of the header. | ||
+ | |- | ||
+ | | Offset of Track 2 || long || From the top of the header. | ||
+ | |- | ||
+ | | etc. || || | ||
+ | |} | ||
+ | |||
+ | ===Tempo Track=== | ||
+ | |||
+ | A Tempo Track defines the Resolution of a track, the Tempo, any Tempo Changes that occur, and when they happen. | ||
+ | {|class="prettytable" style="width:auto;" | ||
+ | ! Function || Size || Notes | ||
+ | |- | ||
+ | | Resolution || word || Number of MIDI ticks per quarter-note (typically $01E0). Holding all else equal, lowering the value will slow the apparent tempo. Must be between $0018 and $03C0, inclusive, otherwise the song won't play. | ||
+ | |- | ||
+ | | Number of Tempos || word || The number of Tempo Duration and Tempo pairs defined below. Must have at least 1 pair. | ||
+ | |- | ||
+ | | Length of Tempo Track || word || In bytes. Counting starts at MSB of Resolution. | ||
+ | |- | ||
+ | | Length of Tempo List || word || In bytes. Counting starts at MSB of first Tempo Duration. Must be a multiple of 8 (4 bytes for each Tempo Duration, 4 bytes for each Tempo). | ||
+ | |- | ||
+ | | Tempo 1 Duration || long || The amount of time (in MIDI ticks) to play the song at the following tempo for, before changing to the next tempo (when applicable; otherwise this is ignored). | ||
+ | |- | ||
+ | | Tempo 1 || long || Tempo to play the song at, in microseconds per quarter-note. | ||
+ | |- | ||
+ | | Tempo 2 Duration || long || | ||
+ | |- | ||
+ | | Tempo 2 || long || | ||
+ | |- | ||
+ | | etc. || || | ||
+ | |} | ||
+ | |||
+ | Any tempo changes done within a loop won't work as intended on subsequent loops. The progression of tempos only goes forward, won't jump backwards when a loop is triggered, and the last tempo will play forever rather than wrapping back to the start of the list. This looks to be intentional, reading the Saturn docs. | ||
+ | |||
+ | ===Play Data=== | ||
+ | |||
+ | The Play Data of a track consists of a continuous series of MIDI events. Each MIDI event consists of a Status byte, and a variable number of parameter bytes, ranging from 0 to 4. All possible MIDI events are defined below. | ||
+ | |||
+ | ====$00 - $7F: Note On==== | ||
+ | |||
+ | For Note On events, the upper nybble of Status is bit flags. | ||
+ | {|class="prettytable" style="width:auto;" | ||
+ | ! Bit Mask || Function | ||
+ | |- | ||
+ | | $10 || Mutes the note when set, essentially changing this event to a rest. | ||
+ | |- | ||
+ | | $20 || Extension bit for Delta Time parameter. | ||
+ | |- | ||
+ | | $40 || Extension bit for Length parameter. | ||
+ | |} | ||
+ | |||
+ | The lower nybble of Status is the channel to play the note on. | ||
+ | |||
+ | Note On events have 4 parameters. | ||
+ | {|class="prettytable" style="width:auto;" | ||
+ | ! Parameter || Function || Notes | ||
+ | |- | ||
+ | | $01 || Pitch || Middle C is $3C. | ||
+ | |- | ||
+ | | $02 || Volume || Clamped from $00 to $7F. | ||
+ | |- | ||
+ | | $03 || Length || How long the note plays, in MIDI ticks. | ||
+ | |- | ||
+ | | $04 || Delta Time || How long to wait before triggering this event, in MIDI ticks. | ||
+ | |} | ||
+ | |||
+ | ====$80 - $8F: Song Flow==== | ||
+ | |||
+ | These events are for manipulating the song's flow. | ||
+ | {|class="prettytable" style="width:auto;" | ||
+ | ! Status || Event || Parameters || Notes | ||
+ | |- | ||
+ | | $80 || Invalid || N/A || Abruptly terminates the song, cutting off any active notes. | ||
+ | |- | ||
+ | | $81 || Reference? || 3: Offset (High), Offset (Low), Count || Copies [Count] events, starting with the one at [Offset] from the start of the track. Needs confirmation. | ||
+ | |- | ||
+ | | $82 || Loop Start/End || 1: Delta Time || First call is where loop starts, second call is where the loop ends. Delta Time is in MIDI ticks. | ||
+ | |- | ||
+ | | $83 || End Song || 0 || Immediately ends the song, letting any active notes play out. | ||
+ | |- | ||
+ | | $84 - $87 || Invalid || N/A || Has the same behavior as event $80. | ||
+ | |- | ||
+ | | $88 - $8B || No-op? || 0 || Doesn't seem to actually do anything, as far as I can tell. Might be for spacing reasons? Could use more investigation. | ||
+ | |- | ||
+ | | $8C || One-eighth rest || 0 || Waits for the length of a one-eighth note before proceeding to the next event. | ||
+ | |- | ||
+ | | $8D || Quarter rest || 0 || Ditto, for a quarter note. | ||
+ | |- | ||
+ | | $8E || Whole rest || 0 || Ditto, for a whole note. | ||
+ | |- | ||
+ | | $8F || 2x Whole rest || 0 || Ditto, for two whole notes. | ||
+ | |} | ||
+ | |||
+ | ====$90 - $9F: Invalid==== | ||
+ | |||
+ | On testing, these all seem to behave the same as event $80. | ||
+ | |||
+ | ====$A0 - $AF: Unused==== | ||
+ | |||
+ | The MIDI Standard and the Saturn’s docs seem to imply these are supposed to be Polyphonic Key Pressure events; however, the Saturn docs state (inconsistently) that this is unimplemented. Maybe this is just a ghost of that? I certainly don’t notice any difference when I fiddle around with it. | ||
+ | |||
+ | For any who wish to play around with them, they have 3 parameters, the last of which is Delta Time. | ||
+ | |||
+ | ====$B0 - $BF: Control Change==== | ||
+ | |||
+ | For Control Change events, the lower nybble of Status is the channel to change the control for. | ||
+ | |||
+ | Control Change events have 3 parameters. | ||
+ | {|class="prettytable" style="width:auto;" | ||
+ | ! Parameter || Function || Notes | ||
+ | |- | ||
+ | | $01 || Control ID || The control to change. Could use some investigation and documenting. | ||
+ | |- | ||
+ | | $02 || Value || The value to set the control to. | ||
+ | |- | ||
+ | | $03 || Delta Time || How long to wait before triggering this event, in MIDI ticks. | ||
+ | |} | ||
+ | |||
+ | ====$C0 - $CF: Program Change==== | ||
+ | |||
+ | For Program Change events, the lower nybble of Status is the channel to change the program for. | ||
+ | |||
+ | Program Change events have 2 parameters. | ||
+ | {|class="prettytable" style="width:auto;" | ||
+ | ! Parameter || Function || Notes | ||
+ | |- | ||
+ | | $01 || Program || The program to change the channel to. Could use some investigation and documenting. | ||
+ | |- | ||
+ | | $02 || Delta Time || How long to wait before triggering this event, in MIDI ticks. | ||
+ | |} | ||
+ | |||
+ | ====$D0 - $DF: Unused==== | ||
+ | |||
+ | Similar to the $A_ events. Docs imply they are an unimplemented ghost of Channel Pressure. I can’t notice any changes when I fiddle with it. | ||
+ | |||
+ | 2 parameters, the last is Delta Time. | ||
+ | |||
+ | ====$E0 - $EF: Pitch Wheel==== | ||
+ | |||
+ | For Pitch Wheel events, the lower nybble of Status is the channel to apply the pitch wheel to. | ||
+ | |||
+ | Pitch Wheel events have 2 parameters. | ||
+ | {|class="prettytable" style="width:auto;" | ||
+ | ! Parameter || Function || Notes | ||
+ | |- | ||
+ | | $01 || Pitch || The pitch to set the wheel to. Could use some investigation and documenting. | ||
+ | |- | ||
+ | | $02 || Delta Time || How long to wait before triggering this event, in MIDI ticks. | ||
+ | |} | ||
+ | |||
+ | ====$F0 - $FF: System Events==== | ||
+ | |||
+ | I've yet to see any of these in use, and none of them seem particularly useful for ROM hacking purposes (assuming the docs are accurate). | ||
+ | |||
+ | If someone wants to document them, feel free. | ||
{{SCHGuides}} | {{SCHGuides}} | ||
[[Category:SCHG: Sonic Jam]] | [[Category:SCHG: Sonic Jam]] |
Revision as of 00:42, 23 April 2024
SCHG: Sonic Jam |
---|
Main Article |
Sonic World |
PCM Samples
According to my research so far, most PCM samples are in a signed 8-bit mono format, with a sample rate of 22050Hz. Each byte is a building block (one "sample") of the PCM sample as a whole.
There are some exceptions, though.
Some PCM samples are "stereo", in that the sample is in the left ear, while there's data/code in the right ear. The stereo format has every other sample starting with the first be in the left ear, and every other sample starting with the second be in the right ear. Due to every other sample being used, these have a halved sample rate; 11025Hz instead of 22050Hz.
In addition, some simply have different sample rates despite being mono.
SWORLD.SND Map
Here's a map of PCM samples that can be found in SWORLD.SND. Start and end addresses are inclusive; they're part of the sample.
NOTE: Sample start and end times are currently estimates based on looking at the waveform and patterns in the hex. Finding the exact start and end of each sample will likely require digging into the code.
Start Address | End Address | Description | Notes |
---|---|---|---|
$004A2 | $02D59 | Flute (Low) | |
$02D5A | $06A79 | Flute (High) | |
$06A79 | $073B1 | Snare Drum | |
$08D54 | $09677 | Bongo (Low) | |
$09678 | $09CDB | Bongo (High)? | |
$09CDC | $0B189 | Tom-Tom (Low)? | |
$0B18A | $0C4A2 | Maracas? | |
$0C4A3 | $0CFD1 | Tom-Tom (High)? | |
$0FF92 | $112B1 | Bass Guitar | Stereo. Sample's in left ear. |
$112B2 | $120F0 | Trumpet | |
$120F1 | $14D31 | Brass | |
$14D32 | $1512F | Sawtooth? | Unsure if sample(s). Stereo, sawtooth in left ear, but right ear's a pair of sawtooth samples that seem to be stereo too. |
$15130 | $16719 | Steel Drum | |
$1671A | $1CB3B | Bass Drum(?) | Stereo. Sample's in left ear. |
$1CB3C | $215D2 | Piano (Low) | |
$215D3 | $2611D | Piano (High) | |
$2611E | $2982E | Synth(?) "Doo" | |
$2982F | $2AD8F | Electric Piano | Has an almost bell sound to it. |
$2AD90 | $2EEC7 | Synth Pad | |
$2EEC8 | $2F287 | Saxophone? | Sample's too short to easily tell. |
$2F948 | $2FEDD | Museum Select | |
$2FEDE | $311FD | Museum Confirm | Not full sound, just the sample used to play it. |
$311FE | $3234D | Unknown | |
$3234E | $3294F | Unknown | |
$32950 | $32BAF | Mission Button | Played on press and release. |
$32BB0 | $358D1 | Big Ring Warp | |
$35B22 | $36548 | Landing | Played when Sonic lands on the ground. |
$36549 | $36E68 | Unknown | |
$36E69 | $37A11 | Spring | Mono, sample rate 11025Hz. |
$37A12 | $381FD | Unknown | Unsure if sample. Stereo, left ear is sine wave, right ear seems like noise. |
$381FE | $399F9 | Code Monitor | Played on interaction. |
$399FA | $3BE83 | Wind | Unused. |
$3BE84 | $3DCF3 | Honk | Unused. |
$3DCF4 | $3FBDE | Unknown | Sounds like rustling, but sounds different from the in-game trees/bushes. |
$3FBDF | $40020 | Unknown | |
$40021 | $423D1 | Electric Organ | Stereo, sample's in left, right is data/code. |
$423D2 | $44DD1 | Splash | |
$44DD2 | $46E65 | Waterfall | |
$46E66 | $4A863 | Ring Pick-up | |
$4A864 | $4C720 | Balloon Pop | |
$4C721 | $4ED8D | Jump |
Music and SFX
The music in Sonic World (and interestingly, the SFX in both Sonic World and the Genesis ports) is performed in real-time via a variation of the MIDI standard (as opposed to just playing back a prerecorded audio file).
A Saturn MIDI file (at least as Sonic Jam handles them) consists of a Header, and a series of Tracks, which consist of a Tempo Track, and Play Data.
MIDI Header
A MIDI Header is very simple, consisting of only two components:
Function | Size | Notes |
---|---|---|
Number of Tracks | word | |
Offset of Track 1 | long | From the top of the header. |
Offset of Track 2 | long | From the top of the header. |
etc. |
Tempo Track
A Tempo Track defines the Resolution of a track, the Tempo, any Tempo Changes that occur, and when they happen.
Function | Size | Notes |
---|---|---|
Resolution | word | Number of MIDI ticks per quarter-note (typically $01E0). Holding all else equal, lowering the value will slow the apparent tempo. Must be between $0018 and $03C0, inclusive, otherwise the song won't play. |
Number of Tempos | word | The number of Tempo Duration and Tempo pairs defined below. Must have at least 1 pair. |
Length of Tempo Track | word | In bytes. Counting starts at MSB of Resolution. |
Length of Tempo List | word | In bytes. Counting starts at MSB of first Tempo Duration. Must be a multiple of 8 (4 bytes for each Tempo Duration, 4 bytes for each Tempo). |
Tempo 1 Duration | long | The amount of time (in MIDI ticks) to play the song at the following tempo for, before changing to the next tempo (when applicable; otherwise this is ignored). |
Tempo 1 | long | Tempo to play the song at, in microseconds per quarter-note. |
Tempo 2 Duration | long | |
Tempo 2 | long | |
etc. |
Any tempo changes done within a loop won't work as intended on subsequent loops. The progression of tempos only goes forward, won't jump backwards when a loop is triggered, and the last tempo will play forever rather than wrapping back to the start of the list. This looks to be intentional, reading the Saturn docs.
Play Data
The Play Data of a track consists of a continuous series of MIDI events. Each MIDI event consists of a Status byte, and a variable number of parameter bytes, ranging from 0 to 4. All possible MIDI events are defined below.
$00 - $7F: Note On
For Note On events, the upper nybble of Status is bit flags.
Bit Mask | Function |
---|---|
$10 | Mutes the note when set, essentially changing this event to a rest. |
$20 | Extension bit for Delta Time parameter. |
$40 | Extension bit for Length parameter. |
The lower nybble of Status is the channel to play the note on.
Note On events have 4 parameters.
Parameter | Function | Notes |
---|---|---|
$01 | Pitch | Middle C is $3C. |
$02 | Volume | Clamped from $00 to $7F. |
$03 | Length | How long the note plays, in MIDI ticks. |
$04 | Delta Time | How long to wait before triggering this event, in MIDI ticks. |
$80 - $8F: Song Flow
These events are for manipulating the song's flow.
Status | Event | Parameters | Notes |
---|---|---|---|
$80 | Invalid | N/A | Abruptly terminates the song, cutting off any active notes. |
$81 | Reference? | 3: Offset (High), Offset (Low), Count | Copies [Count] events, starting with the one at [Offset] from the start of the track. Needs confirmation. |
$82 | Loop Start/End | 1: Delta Time | First call is where loop starts, second call is where the loop ends. Delta Time is in MIDI ticks. |
$83 | End Song | 0 | Immediately ends the song, letting any active notes play out. |
$84 - $87 | Invalid | N/A | Has the same behavior as event $80. |
$88 - $8B | No-op? | 0 | Doesn't seem to actually do anything, as far as I can tell. Might be for spacing reasons? Could use more investigation. |
$8C | One-eighth rest | 0 | Waits for the length of a one-eighth note before proceeding to the next event. |
$8D | Quarter rest | 0 | Ditto, for a quarter note. |
$8E | Whole rest | 0 | Ditto, for a whole note. |
$8F | 2x Whole rest | 0 | Ditto, for two whole notes. |
$90 - $9F: Invalid
On testing, these all seem to behave the same as event $80.
$A0 - $AF: Unused
The MIDI Standard and the Saturn’s docs seem to imply these are supposed to be Polyphonic Key Pressure events; however, the Saturn docs state (inconsistently) that this is unimplemented. Maybe this is just a ghost of that? I certainly don’t notice any difference when I fiddle around with it.
For any who wish to play around with them, they have 3 parameters, the last of which is Delta Time.
$B0 - $BF: Control Change
For Control Change events, the lower nybble of Status is the channel to change the control for.
Control Change events have 3 parameters.
Parameter | Function | Notes |
---|---|---|
$01 | Control ID | The control to change. Could use some investigation and documenting. |
$02 | Value | The value to set the control to. |
$03 | Delta Time | How long to wait before triggering this event, in MIDI ticks. |
$C0 - $CF: Program Change
For Program Change events, the lower nybble of Status is the channel to change the program for.
Program Change events have 2 parameters.
Parameter | Function | Notes |
---|---|---|
$01 | Program | The program to change the channel to. Could use some investigation and documenting. |
$02 | Delta Time | How long to wait before triggering this event, in MIDI ticks. |
$D0 - $DF: Unused
Similar to the $A_ events. Docs imply they are an unimplemented ghost of Channel Pressure. I can’t notice any changes when I fiddle with it.
2 parameters, the last is Delta Time.
$E0 - $EF: Pitch Wheel
For Pitch Wheel events, the lower nybble of Status is the channel to apply the pitch wheel to.
Pitch Wheel events have 2 parameters.
Parameter | Function | Notes |
---|---|---|
$01 | Pitch | The pitch to set the wheel to. Could use some investigation and documenting. |
$02 | Delta Time | How long to wait before triggering this event, in MIDI ticks. |
$F0 - $FF: System Events
I've yet to see any of these in use, and none of them seem particularly useful for ROM hacking purposes (assuming the docs are accurate).
If someone wants to document them, feel free.