Using the SSG sound channels

From NeoGeo Development Wiki
Jump to navigation Jump to search

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.