Playing sound samples

From NeoGeo Development Wiki
Jump to navigation Jump to search

ADPCM-A

Definitions of ADPCM-A registers:

;ADPCM-A register defines (YM2610 port B: address $06, data $07)
PA_CTRL:	EQU $00		;bit 7: 0 = key on, 1 = dump (key off). Bits 0-5: channel mask
PA_MVOL:	EQU $01		;master volume, 0-$3F ($3F = loudest)
PA_CVOL:	EQU $08		;+channel: bit 7 = left, bit 6 = right, bits 0-4 = volume 0-$1F
PA_STARTL:	EQU $10		;+channel: start address bits 8-15
PA_STARTH:	EQU $18		;+channel: start address bits 16-23
PA_ENDL:	EQU $20		;+channel: end address bits 8-15
PA_ENDH:	EQU $28		;+channel: end address bits 16-23

(Full list of defines here)

Registers marked "+channel" exist once per channel: add the channel number (0-5) to the base address. For example, the start address low register of CH3 is PA_STARTL+2 = $12. In PA_CTRL, bit 0 is CH1 and bit 5 is CH6, so several channels can be keyed on or off with a single write.

Writing to the YM2610

The real YM2610 requires a minimum delay between writes, see Z80/YM2610 interface.

	ORG	$0018
	jp	WritePortB		;RST $18 vector (only 8 bytes available here, so jump)

;Write E to YM2610 port B register D (ADPCM-A, FM channels 3-4)
;Destroys: nothing
WritePortB:
	push	af
WPB_wait1:
	in	a,($04)			;status 0
	rlca				;busy flag -> carry
	jr	c,WPB_wait1
	ld	a,d
	out	($06),a			;register number
WPB_wait2:
	in	a,($04)
	rlca
	jr	c,WPB_wait2
	ld	a,e
	out	($07),a			;value
	pop	af
	ret

;Write E to YM2610 port A register D (SSG, ADPCM-B, FM channels 1-2, flag control)
WritePortA:
	push	af
WPA_wait1:
	in	a,($04)
	rlca
	jr	c,WPA_wait1
	ld	a,d
	out	($04),a
WPA_wait2:
	in	a,($04)
	rlca
	jr	c,WPA_wait2
	ld	a,e
	out	($05),a
	pop	af
	ret

Playing a sample

Example: play V ROM $000000-$00FFFF on CH1, centered, at maximum volume. The start and end addresses must be written before the channel is keyed on. The master volume is global, so it usually only needs to be set once, at driver initialization.

;playback parameters
CH:		EQU 0			;channel 0-5 (0 = CH1)
MVOL:		EQU $3F			;master volume
CVOL:		EQU $C0+$1F		;left + right, channel volume $1F
START_ADDR:	EQU $000000		;first byte of the sample in V ROM
END_ADDR:	EQU $00FFFF		;last byte of the sample (inclusive)

PlayADPCMA:
	ld	de,(PA_MVOL<<8)+MVOL
	rst	$18
	ld	de,((PA_CVOL+CH)<<8)+CVOL
	rst	$18
	ld	de,((PA_STARTL+CH)<<8)+((START_ADDR>>8)&$FF)
	rst	$18
	ld	de,((PA_STARTH+CH)<<8)+((START_ADDR>>16)&$FF)
	rst	$18
	ld	de,((PA_ENDL+CH)<<8)+((END_ADDR>>8)&$FF)
	rst	$18
	ld	de,((PA_ENDH+CH)<<8)+((END_ADDR>>16)&$FF)
	rst	$18
	ld	de,(PA_CTRL<<8)+(1<<CH)	;key on
	rst	$18
	ret

Keying on a channel that is already playing restarts it from its start address. When the channel reaches its end address it stops on its own and sets its end flag; there is no looping.

Stopping a sample

Writing PA_CTRL with bit 7 set stops (dumps) the channels selected in bits 0-5:

StopADPCMA:
	ld	de,(PA_CTRL<<8)+$80+(1<<CH)	;dump CH
	rst	$18
	ret

Writing $BF stops all six channels.

End flags

The end flags of the ADPCM-A channels can be read in bits 0-5 of status 1 (port $06); bit 7 is the ADPCM-B end flag. A flag stays set until it is cleared through the flag control register $1C on port A. Writing 1 to a bit clears and masks that flag, and writing 0 unmasks it again so it can be set by the next sample end.

;Z flag clear on return if CH has finished playing
CheckADPCMAEnd:
	in	a,($06)			;status 1
	and	1<<CH
	ret

;Clear the end flag of CH
ClearADPCMAEnd:
	ld	de,($1C<<8)+(1<<CH)	;clear and mask
	call	WritePortA
	ld	de,($1C<<8)+$00		;unmask
	call	WritePortA
	ret

ADPCM-B

The YM2610 has one ADPCM-B channel. It plays 4-bit ADPCM samples at a variable rate, from about 1.85 kHz to 55.5 kHz, and can loop. This makes it suited to music, longer ambience and voice. Like ADPCM-A, samples are addressed in 256-byte units with an inclusive end address. Depending on the cartridge board, ADPCM-B samples are read from the same V ROM as ADPCM-A or from a separate ROM.

The ADPCM-B registers are on YM2610 port A: the Z80 writes the register number to port $04 and the value to port $05 (see WritePortA above).

Definitions of ADPCM-B registers:

;ADPCM-B register defines (YM2610 port A: address $04, data $05)
PB_CTRL1:	EQU $10		;bit 7 = start, bit 4 = repeat, bit 0 = reset
PB_CTRL2:	EQU $11		;bit 7 = left, bit 6 = right
PB_STARTL:	EQU $12		;start address bits 8-15
PB_STARTH:	EQU $13		;start address bits 16-23
PB_ENDL:	EQU $14		;end address bits 8-15
PB_ENDH:	EQU $15		;end address bits 16-23
PB_DELTAL:	EQU $19		;Delta-N (playback rate) low byte
PB_DELTAH:	EQU $1A		;Delta-N high byte
PB_VOL:		EQU $1B		;volume, 0-$FF ($FF = loudest)

Playback rate

The playback rate is set by the 16-bit Delta-N value:

rate = 55.5 kHz × Delta-N / 65536
Delta-N = rate × 65536 / 55555

For example, a sample recorded at 22050 Hz needs Delta-N = 26011 ($659B), and one at 11025 Hz needs 13006 ($32CE). Changing Delta-N while the sample plays changes its pitch, which can be used to play one sample at different notes.

Playing a sample

Example: play V ROM $100000-$13FFFF on ADPCM-B at 22050 Hz, centred, at maximum volume. All parameters must be written before the start bit is set. The pan bits in PB_CTRL2 must be set, otherwise nothing is heard.

;playback parameters
B_START:	EQU $100000		;first byte of the sample in V ROM
B_END:		EQU $13FFFF		;last byte of the sample (inclusive)
B_DELTA:	EQU $659B		;22050 Hz
B_VOL:		EQU $FF
B_PAN:		EQU $C0			;left + right

PlayADPCMB:
	ld	de,(PB_CTRL1<<8)+$01	;reset, in case a sample is playing
	call	WritePortA
	ld	de,(PB_CTRL1<<8)+$00
	call	WritePortA
	ld	de,(PB_CTRL2<<8)+B_PAN
	call	WritePortA
	ld	de,(PB_STARTL<<8)+((B_START>>8)&$FF)
	call	WritePortA
	ld	de,(PB_STARTH<<8)+((B_START>>16)&$FF)
	call	WritePortA
	ld	de,(PB_ENDL<<8)+((B_END>>8)&$FF)
	call	WritePortA
	ld	de,(PB_ENDH<<8)+((B_END>>16)&$FF)
	call	WritePortA
	ld	de,(PB_DELTAL<<8)+(B_DELTA&$FF)
	call	WritePortA
	ld	de,(PB_DELTAH<<8)+(B_DELTA>>8)
	call	WritePortA
	ld	de,(PB_VOL<<8)+B_VOL
	call	WritePortA
	ld	de,(PB_CTRL1<<8)+$80	;start ($90 = start with repeat)
	call	WritePortA
	ret

With the repeat bit set ($90), the sample loops from its end address back to its start address until it is stopped. Only the whole start-to-end range can loop; there is no separate loop point.

Stopping a sample

StopADPCMB:
	ld	de,(PB_CTRL1<<8)+$01	;reset
	call	WritePortA
	ld	de,(PB_CTRL1<<8)+$00
	call	WritePortA
	ret

End flag

The ADPCM-B end flag is bit 7 of status 1 (port $06), and it is cleared through bit 7 of the flag control register $1C on port A, the same way as the ADPCM-A flags:

;Z flag clear on return if ADPCM-B has finished playing
CheckADPCMBEnd:
	in	a,($06)			;status 1
	and	$80
	ret

ClearADPCMBEnd:
	ld	de,($1C<<8)+$80		;clear and mask
	call	WritePortA
	ld	de,($1C<<8)+$00		;unmask
	call	WritePortA
	ret

ADPCM-A demo

An interactive demo with source files can be found here.