Let's take a look at the structured format of sound information. Here's a relatively straightforward example demonstrating the setup of the Checkpoint PCM sample.
; Voice 1, Checkpoint
ROM:6F91 data_voice1: dw data_voice1_c ; Offset to channel setup below
The sample is played through two channels simultaneously, presumably to boost its volume.
; Voice 1, Channel Setup
ROM:6F99 data_voice1_c: db 2 ; Number of channels
ROM:6F9A dw data_voice1_c1 ; Address of Channel 1 Setup
ROM:6F9C dw data_voice1_c2 ; Address of Channel 2 Setup
This block represents the default setup for the 32 byte block mentioned in the previous post. It's not actually 32 bytes, but the remainder of the space is padding to zero by the program code. I've cut the second entry short in the interests of space as it's very similar.
; Voice 1, Checkpoint (PCM Samples: Channel 1) - Default 0x20 area setup
ROM:6F9E data_voice1_c1: db 80h ; Flags: Enable
ROM:6F9F db 1000110b ; Flags: Mute & Channel Index
ROM:6FA0 db 2 ; End Marker
ROM:6FA1 dw 0
ROM:6FA3 dw 1
ROM:6FA5 dw data_voice1_c1p; Address of commands
ROM:6FA7 db 0
ROM:6FA8 db 20h ; Offset for positioning information
ROM:6FA9 db 0
ROM:6FAA db 0
ROM:6FAB db 0
; Voice 1, Checkpoint (PCM Samples: Channel 2)
ROM:6FAC data_voice1_c2: db 80h
; Snip: Similar to the above block
ROM:6FB9 db 0
Here's where things gets a little interesting; what follows is a series of commands that correspond to a particular z80 routine, along with their arguments.
; Voice 1, Checkpoint (PCM Properties)
ROM:6FBA data_voice1_c1p:db 93 ; 93 = Command: PCM Set Pitch
ROM:6FBB db 48h ; value = pitch
ROM:6FBC db 82h ; 82 = Command: PCM Sample Volumes
ROM:6FBD db 17h ; value = left channel vol
ROM:6FBE db 2Eh ; value = right channel vol
ROM:6FBF db 0DCh ; DC = Command: Sample Index
ROM:6FC0 db 28h ; value = checkpoint
ROM:6FC1 db 99h ; 99 = Command: PCM Finalize
ROM:6FC2 data_voice1_c2p:db 93h ; 93 = Command: PCM Set Pitch
ROM:6FC3 db 48h ; value = pitch
ROM:6FC4 db 82h ; 82 = Command: PCM Sample Volumes
ROM:6FC5 db 2Eh ; value = left channel vol
ROM:6FC6 db 17h ; value = right channel vol
ROM:6FC7 db 0DCh ; DC = Command: Sample Index
ROM:6FC8 db 28h ; value = checkpoint
ROM:6FC9 db 99h ; 99 = Command: PCM Finalize
These commands index a table of routines which is as follows. Not all of these routines are used, as I imagine this area of the code is used across other Sega titles. I've highlighted in red the entries used by the above sample.
ROM:0B93 BigRoutineTable:
ROM:0B93 dw YM_Dec_Pos ; YM: Decrement Position In Sequence (80)
ROM:0B95 dw YM_SetEndMarker ; YM: Set End Marker.
ROM:0B97 dw PCM_SetVol ; PCM: Set Volume (Left & Right Channels) (82)
ROM:0B99 dw YM_Dec_Pos ; YM: Decrement Position In Sequence (80)
ROM:0B9B dw YM_Finalize ; YM: End (84)
ROM:0B9D dw YM_SetNoise ; YM: Enable Noise Channel (85)
ROM:0B9F dw loc_409 ; Unused?
ROM:0BA1 dw YM_SetModTab ; YM: Enable/Disable Modulation table
ROM:0BA3 dw WriteSeqAddr
ROM:0BA5 dw SetSeqAddr ; Set Next Sequence Address
ROM:0BA7 dw YM_GetLoopAdr ; de = new YM loop address
ROM:0BA9 dw YM_SetNoteOffset ; YM: Set Note/Octave Offset (8B)
ROM:0BAB dw YM_DoLoop ; YM: Loop Sequence Of Commands (8C)
ROM:0BAD dw loc_46B ; Unused?
ROM:0BAF dw loc_471 ; Unused?
ROM:0BB1 dw YM_Enable_Correspnd ; YM: (Unused) Enable corresponding channel (8F)
ROM:0BB3 dw YM_Disable_Correspnd ; YM: (Unused) Disable corresponding channel (90)
ROM:0BB5 dw YM_SetBlock ; YM: Set Block - Called First When Setting Up (91)
ROM:0BB7 dw YM_DisableNoise ; YM: Disable Noise Channel (92)
ROM:0BB9 dw PCM_SetPitch ; PCM: Set Pitch (93)
ROM:0BBB dw YM_MarkerData ; FM: End Marker - Do not calculate, use value from data (94)
ROM:0BBD dw YM_MarkerHigh ; FM: End Marker - Set High Byte From Data (95)
ROM:0BBF dw YM_ConnectRight ; FM: Connect Channel to Right Speaker (96)
ROM:0BC1 dw YM_ConnectLeft ; FM: Connect Channel to Left Speaker (97)
ROM:0BC3 dw YM_ConnectCentre ; FM: Connect Channel to Both Speaker (98)
ROM:0BC5 dw PCM_Finalize ; Write Commands to PCM Channel (99)
In case you were wondering the 0xDC command which is not shown in the table above triggers a separate piece of code, which also triggers drum samples and so forth in the music tracks. Anyway, let's take a look at a simple routine - setting the pitch of a PCM sample.
ROM:03C4 PCM_SetPitch:
ROM:03C4 bit 6, (ix+1) ; If channel is muted, don't set pitch
ROM:03C8 jp nz, set_pitch
ROM:03CB ld a, a
ROM:03CC ret
ROM:03CD set_pitch:
ROM:03CD ld a, (de) ; a = New pitch (read from setup table in rom)
ROM:03CE ld (ix+16h), a ; Set relevant area in 32 byte block that controls pitch
ROM:03D1 ret
The music tracks work in a similar way, but with a much longer and more complex series of commands.
Originally, I thought the Z80 might be used in a 'dumb' manner and simply stream preformatted audio data to the various chips. But its usage is much more sophisticated as demonstrated above.