Using the SSG sound channels
SSG
The SSG part of the YM2610 is compatible with the AY-3-8910 (without its I/O ports). It has 3 square-wave tone channels (A, B, C), one noise generator that can be mixed into any channel, and one envelope generator (EG) shared by all channels. Each channel has a 4-bit volume, or can take its volume from the EG instead.
The SSG registers are on YM2610 port A: the Z80 writes the register number to port $04 and the value to port $05.
Definitions of SSG registers:
;SSG register defines (YM2610 port A: address $04, data $05)
SSG_AFINE: EQU $00 ;channel A tone period, bits 0-7
SSG_ACOARSE: EQU $01 ;channel A tone period, bits 8-11
SSG_BFINE: EQU $02 ;channel B
SSG_BCOARSE: EQU $03
SSG_CFINE: EQU $04 ;channel C
SSG_CCOARSE: EQU $05
SSG_NOISE: EQU $06 ;noise period, 5 bits (0-$1F)
SSG_MIXER: EQU $07 ;bits 0-2: tone A/B/C off, bits 3-5: noise A/B/C off (1 = off)
SSG_AVOL: EQU $08 ;bits 0-3: volume 0-$F, bit 4: use EG instead
SSG_BVOL: EQU $09
SSG_CVOL: EQU $0A
SSG_EGFINE: EQU $0B ;EG period, bits 0-7
SSG_EGCOARSE: EQU $0C ;EG period, bits 8-15
SSG_EGSHAPE: EQU $0D ;bits 0-3: hold, alternate, attack, continue
(Full list of defines here)
Frequencies
With the YM2610's 8 MHz clock:
Tone: f = 125000 / TP TP = 12-bit tone period (1-$FFF) Noise: f = 125000 / NP NP = 5-bit noise period (1-$1F) EG: f = 7812.5 / EP EP = 16-bit EG period; f is the repeat rate of one full ramp
For example, TP = $040 gives about 1953 Hz, and A4 (440 Hz) is TP = 284 ($11C). Going up one octave halves TP; going down one octave doubles it.
Mixer register
In register $07 a bit set to 1 turns the source off. Bits 0-2 control the tone of channels A, B and C, and bits 3-5 control the noise on channels A, B and C. So $3F silences all tone and noise, $3E enables only the tone of channel A, and $37 enables only noise on channel A.
The mixer controls all three channels at once. A driver that plays several channels independently should keep a copy of the register in RAM and change only its own bits (see the example below).
Write routine
The examples below use RST $08 to write register D with value E on port A. An RST slot is only 8 bytes long, and the write routine does not fit in it together with a RET, so the slot jumps to the routine instead. Polling the busy flag (bit 7 of port $04) before each write is the safe choice on real hardware.
.org $0008
jp WritePortA ;RST $08: set YM2610 port A register D to value E
;Write E to YM2610 port A register D
;Destroys: nothing
WritePortA:
push af
WPA_wait1:
in a,($04) ;status 0
rlca ;busy flag -> carry
jr c,WPA_wait1
ld a,d
out ($04),a ;register number
WPA_wait2:
in a,($04)
rlca
jr c,WPA_wait2
ld a,e
out ($05),a ;value
pop af
ret
Beep using the envelope generator
Plays a 2 kHz tone on channel A whose volume repeatedly ramps down.
FastBeep:
ld de,$0040 ;Channel A tone period: $040 (about 2 kHz)
rst $08
ld de,$0100
rst $08
ld de,$0B0F ;EG period: $050F (about 6 Hz)
rst $08
ld de,$0C05
rst $08
ld de,$0810 ;Channel A volume is taken from the EG
rst $08
ld de,$0D08 ;EG shape: repeating ramp down
rst $08
ld de,$073E ;Only the tone of channel A is on
rst $08
ret
Note: the mixer value is $3E, not $0E. $0E leaves the noise enabled on channels B and C.
Writing the EG shape register restarts the envelope, so write it last, when the sound should start.
EG shapes
| Value | Shape |
|---|---|
| $00-$03, $09 | Single ramp down, then stay at 0 |
| $04-$07, $0F | Single ramp up, then drop to 0 and stay there |
| $08 | Repeating ramp down (sawtooth) |
| $0A | Repeating down-up (triangle) |
| $0B | Single ramp down, then stay at maximum |
| $0C | Repeating ramp up (sawtooth) |
| $0D | Single ramp up, then stay at maximum |
| $0E | Repeating up-down (triangle) |
Playing a note
Sets channel C (0 = A, 1 = B, 2 = C) to note A (0-11 = C to B, octave 4). The table holds TP values; the fine and coarse registers of channel n are 2n and 2n+1.
;In: A = note 0-11, C = channel 0-2
SSG_SetNote:
push hl
ld hl,SSG_NoteTable
add a,a ;2 bytes per entry
add a,l
ld l,a
jr nc,SSG_SN_nc
inc h
SSG_SN_nc:
ld a,c
add a,a
ld d,a ;fine tune register
ld e,(hl)
rst $08
inc d ;coarse tune register
inc hl
ld e,(hl)
rst $08
pop hl
ret
SSG_NoteTable: ;octave 4, TP = 125000 / f
dw 478,451,426,402,379,358,338,319,301,284,268,253
; C C# D D# E F F# G G# A A# B
For other octaves, shift the TP right (higher) or left (lower) once per octave before writing it.
Enabling one channel without affecting the others
Keeps a copy of the mixer register in RAM, so each channel can be switched on or off independently.
MixerCopy: EQU $F800 ;1 byte of Z80 RAM, initialised to $3F
;Enable the tone of channel C (0-2)
SSG_ToneOn:
ld a,c
call SSG_ChannelBit ;A = 1 << channel
cpl
ld b,a
ld a,(MixerCopy)
and b ;clear bit = tone on
jr SSG_WriteMixer
;Disable the tone of channel C (0-2)
SSG_ToneOff:
ld a,c
call SSG_ChannelBit
ld b,a
ld a,(MixerCopy)
or b ;set bit = tone off
SSG_WriteMixer:
ld (MixerCopy),a
ld d,SSG_MIXER
ld e,a
rst $08
ret
;A = channel 0-2 -> A = 1 << channel
SSG_ChannelBit:
ld b,a
ld a,1
inc b
SSG_CB_loop:
dec b
ret z
add a,a
jr SSG_CB_loop
The same approach works for noise, using bits 3-5 (shift the channel bit left 3 times).
Noise hit
A short noise burst on channel C that decays by itself, usable as a snare or explosion.
NoiseHit:
ld de,$0608 ;Noise period: 8
rst $08
ld de,$0B00 ;EG period: $0800 (about 4 Hz: a decay of about 0.26 s)
rst $08
ld de,$0C08
rst $08
ld de,$0A10 ;Channel C volume is taken from the EG
rst $08
ld de,$071F ;Noise on channel C only (tone off everywhere)
rst $08
ld de,$0D00 ;EG shape: single ramp down, then silent
rst $08
ret
A lower noise period gives a brighter, hissier sound; a higher one sounds rougher.
Silence
SSG_Silence:
ld de,$0800 ;Channel A volume = 0
rst $08
ld de,$0900 ;Channel B volume = 0
rst $08
ld de,$0A00 ;Channel C volume = 0
rst $08
ld de,$073F ;Disable tone and noise on all channels
rst $08
ret
Note: the mixer value is $3F, not $0F. $0F only disables the three tones and the noise on channel A; noise on channels B and C stays enabled.