;======================================================================
;	SN76489 SOUND DRIVER
;
;	WRITTEN BY: DEAN NETHERTON
;======================================================================
;
; SN74489 PSG CHIP NEEDS AN INPUT CLOCK FREQUENCY OF
; NO MORE THAN 4 MHZ.  THE CLOSEST THING THERE IS TO A STANDARD
; IS THE MSX FREQ OF 3.579545 MHZ.
;
; TODO:
; 1. PROVIDE SUPPORT FOR NOISE CHANNEL
; 2. DOES THIS WORK FOR FASTER CPUS? ONLY BEEN TESTED ON A Z80 7MHZ UNIT
;
;======================================================================
; CONSTANTS
;======================================================================
;

	DEVECHO	"SN76489 MODE="
;
#IF (SNMODE == SNMODE_VGM)
SN76489_PORT_LEFT	.EQU	$C6	; PORTS FOR ACCESSING THE SN76489 CHIP (LEFT)
SN76489_PORT_RIGHT	.EQU	$C7	; PORTS FOR ACCESSING THE SN76489 CHIP (RIGHT)
	DEVECHO	"VGM"
#ENDIF
;
#IF (SNMODE == SNMODE_RC)
SN76489_PORT_LEFT	.EQU	$FF	; PORTS FOR ACCESSING THE SN76489 CHIP (LEFT)
SN76489_PORT_RIGHT	.EQU	$FB	; PORTS FOR ACCESSING THE SN76489 CHIP (RIGHT)
	DEVECHO	"RC"
#ENDIF
;
#IF (SNMODE == SNMODE_DUO)
SN76489_PORT_LEFT	.EQU	$BE	; PORTS FOR ACCESSING THE SN76489 CHIP (LEFT)
SN76489_PORT_RIGHT	.EQU	$BF	; PORTS FOR ACCESSING THE SN76489 CHIP (RIGHT)
	DEVECHO	"RC"
#ENDIF
;
	DEVECHO	", IO_LEFT="
	DEVECHO	SN76489_PORT_LEFT
	DEVECHO	", IO_RIGHT="
	DEVECHO	SN76489_PORT_RIGHT
	DEVECHO	", CLOCK="
	DEVECHO	SN7CLK
	DEVECHO	" HZ\n"
;
SN7_IDAT		.EQU	0
SN7_TONECNT		.EQU	3	; COUNT NUMBER OF TONE CHANNELS
SN7_NOISECNT		.EQU	1	; COUNT NUMBER OF NOISE CHANNELS
SN7_CHCNT		.EQU	SN7_TONECNT + SN7_NOISECNT
;
#INCLUDE "audio.inc"
;
; BLINDLY RESET THE PSG AS SOON AS WE CAN AFTER BOOT BECAUSE IT
; DOES NOT RESET ITSELF AT POWER ON AND MAKES UGLY NOISE.
;
SN76489_PREINIT:
	JR	SN7_RESET
;
SN76489_INIT:
	LD	IY, SN7_IDAT		; POINTER TO INSTANCE DATA
	LD	BC, SN7_FNTBL		; BC := FUNCTION TABLE ADDRESS
	LD	DE, SN7_IDAT		; DE := SN7 INSTANCE DATA PTR
	CALL	SND_ADDENT		; ADD ENTRY, A := UNIT ASSIGNED

	LD	DE,STR_MESSAGELT
	CALL	WRITESTR
	LD	A, SN76489_PORT_LEFT
	CALL	PRTHEXBYTE

	LD	DE,STR_MESSAGERT
	CALL	WRITESTR
	LD	A, SN76489_PORT_RIGHT
	CALL	PRTHEXBYTE

	CALL	SN7_RESET
	XOR	A			; SIGNAL SUCCESS
	RET
;
;======================================================================
;	INITIALIZE DEVICE
;======================================================================
;
SN7_INIT:
;
#IFDEF SBCV2004
	LD	A,(HB_RTCVAL)
	OR	%00001000		; SBC-V2-004+ CHANGE
	EZ80_IO
	OUT	(RTCIO),A		; TO HALF CLOCK SPEED
#ENDIF
;
	LD	HL,SN7_REG_INIT		; POINT TO REG INIT TBL
	LD	B,SN7_REG_CNT		; BYTES IN TABLE
SN7_INIT1
	LD	A,(HL)
	INC	HL			; BUMP FOR NEXT TIME
	EZ80_IO
	OUT	(SN76489_PORT_LEFT), A	; WRITE LEFT PORT
	EZ80_IO
	OUT	(SN76489_PORT_RIGHT), A	; WRITE RIGHT PORT
	DJNZ	SN7_INIT1		; LOOP TILL DONE
;
#IFDEF SBCV2004
	LD	A,(HB_RTCVAL)
	AND	%11110111		; SBC-V2-004+ CHANGE TO
	EZ80_IO
	OUT	(RTCIO),A		; NORMAL CLOCK SPEED
#ENDIF
;
	RET
;
SN7_REG_INIT:
	.DB	$80,$00			; TONE 1 FREQ (2 BYTES)
	.DB	$9F			; TONE 1 ATTENTUATION
	.DB	$A0,$00			; TONE 2 FREQ (2 BYTES)
	.DB	$BF			; TONE 2 ATTENTUATION
	.DB	$C0,$00			; TONE 3 FREQ (2 BYTES)
	.DB	$DF			; TONE 3 ATTENTUATION
	.DB	$E0			; NOISE CONTROL
	.DB	$FF			; NOISE ATTENTUATION
SN7_REG_CNT	.EQU	$ - SN7_REG_INIT
;
;======================================================================
; SN76489 DRIVER - SOUND ADAPTER (SND) FUNCTIONS
;======================================================================
;
SN7_RESET:
	AUDTRACE(SNT_INIT)
;
	CALL	SN7_INIT		; HARDWARE INIT
;
	; RESET DEFAULTS IN CASE OF AN IN-PLACE HBIOS RESTART
	LD	HL,0
	LD	(SN7_PENDING_PERIOD),HL
	LD	(SN7_PENDING_DURATION),HL
	XOR	A
	LD	(SN7_PENDING_VOLUME),A
;
	XOR	A
	RET

; BIT MAPPING
; SET TONE:
; 1 CC 0 PPPP (LOW)
; 0 0 PPPPPP (HIGH)

; 1 CC 1 VVVV

SN7_VOLUME:
	AUDTRACE(SNT_VOL)
	AUDTRACE_L
	AUDTRACE_CR
	LD	A, L
	LD	(SN7_PENDING_VOLUME), A

	XOR	A			; SIGNAL SUCCESS
	RET

SN7_NOTE:
	LD	DE, SN7NOTETBL
	CALL	AUD_NOTE		; RETURNS PERIOD IN HL, FALL THRU
;
SN7_PERIOD:
	AUDTRACE(SNT_PERIOD)
	AUDTRACE_HL
	AUDTRACE_CR
;
	LD	A,H			; IF ZERO - ERROR
	OR	L
	JR	Z,SN7_PERIOD1
;
	LD	A,H			; MAXIMUM TONE PERIOD IS 10-BITS
	AND	11111100B		; ALLOWED RANGE IS 0001-03FF (1023)
	JR	NZ,SN7_PERIOD1		; RETURN NZ IF NUMBER TOO LARGE
	LD	(SN7_PENDING_PERIOD),HL	; SAVE AND RETURN SUCCESSFUL
	XOR	A			; SET SUCCESS
	RET
;
SN7_PERIOD1:
	LD	HL,$FFFF		; REQUESTED PERIOD IS LARGER
	LD	(SN7_PENDING_PERIOD),HL	; THAN PSG CAN SUPPORT, SO
	OR	$FF			; SET PERIOD TO $FFFF
	RET				; AND RETURN FAILURE
;
SN7_PLAY:
	AUDTRACE(SNT_PLAY)
	AUDTRACE_D
	AUDTRACE_CR

	LD	A, (SN7_PENDING_PERIOD + 1)
	CP	$FF
	JR	Z, SN7_PLAY1		; PERIOD IS TOO LARGE, UNABLE TO PLAY
	CALL	SN7_APPLY_VOL
	CALL	SN7_APPLY_PRD

	XOR	A			; SIGNAL SUCCESS
	RET

SN7_PLAY1:				; TURN CHANNEL VOL TO OFF AND STOP PLAYING
	LD	A, (SN7_PENDING_VOLUME)
	PUSH	AF
	LD	A, 0
	LD	(SN7_PENDING_VOLUME), A
	CALL	SN7_APPLY_VOL
	POP	AF
	LD	(SN7_PENDING_VOLUME), A

	OR	$FF			; SIGNAL FAILURE
	RET

SN7_QUERY:
	LD	A, E
	CP	BF_SNDQ_CHCNT
	JR	Z, SN7_QUERY_CHCNT

	CP	BF_SNDQ_PERIOD
	JR	Z, SN7_QUERY_PERIOD

	CP	BF_SNDQ_VOLUME
	JR	Z, SN7_QUERY_VOLUME

	CP	BF_SNDQ_DEV
	JR	Z, SN7_QUERY_DEV

	OR	$FF			; SIGNAL FAILURE
	RET

SN7_QUERY_CHCNT:
	LD	B, SN7_TONECNT
	LD	C, SN7_NOISECNT
	XOR	A
	RET

SN7_QUERY_PERIOD:
	LD	HL, (SN7_PENDING_PERIOD)
	XOR	A
	RET

SN7_QUERY_VOLUME:
	LD	A, (SN7_PENDING_VOLUME)
	LD	L, A
	LD	H, 0

	XOR	A
	RET

SN7_QUERY_DEV:

	LD	B, SNDDEV_SN76489
	LD	DE, SN76489_PORT_LEFT 	; E WITH LEFT PORT
	LD	HL, SN76489_PORT_RIGHT	; L WITH RIGHT PORT

	XOR	A
	RET
;
;	UTIL FUNCTIONS
;

SN7_APPLY_VOL:				; APPLY VOLUME TO BOTH LEFT AND RIGHT CHANNELS
	PUSH	BC			; D CONTAINS THE CHANNEL NUMBER
	PUSH	AF
	LD	A, D
	AND	$3
	RLCA
	RLCA
	RLCA
	RLCA
	RLCA
	OR	$90
	LD	B, A

	LD	A, (SN7_PENDING_VOLUME)
	RRCA
	RRCA
	RRCA
	RRCA

	AND	$0F
	LD	C, A
	LD	A, $0F
	SUB	C
	AND	$0F
	OR	B			; A CONTAINS COMMAND TO SET VOLUME FOR CHANNEL

	AUDTRACE(SNT_REGWR)
	AUDTRACE_A
	AUDTRACE_CR

#IFDEF SBCV2004
	PUSH	AF
	LD	A,(HB_RTCVAL)
	OR	%00001000		; SBC-V2-004+ CHANGE
	EZ80_IO
	OUT	(RTCIO),A		; TO HALF CLOCK SPEED
	POP	AF
#ENDIF
	EZ80_IO
	OUT	(SN76489_PORT_LEFT), A
	EZ80_IO
	OUT	(SN76489_PORT_RIGHT), A

#IFDEF SBCV2004
	LD	A,(HB_RTCVAL)
	AND	%11110111		; SBC-V2-004+ CHANGE TO
	EZ80_IO
	OUT	(RTCIO),A		; NORMAL CLOCK SPEED
#ENDIF

	POP	AF
	POP	BC
	RET

SN7_APPLY_PRD:
	PUSH	DE
	PUSH	BC
	PUSH	AF
	LD	HL, (SN7_PENDING_PERIOD)

	LD	A, D
	AND	$3
	RLCA
	RLCA
	RLCA
	RLCA
	RLCA
	OR	$80
	LD	B, A			; PERIOD COMMAND 1 - CONTAINS CHANNEL ONLY

	LD	A, L			; GET LOWER 4 BITS FOR COMMAND 1
	AND	$F
	OR	B			; A NOW CONTAINS FIRST PERIOD COMMAND

	AUDTRACE(SNT_REGWR)
	AUDTRACE_A
	AUDTRACE_CR

#IFDEF SBCV2004
	PUSH	AF
	LD	A,(HB_RTCVAL)
	OR	%00001000		; SBC-V2-004+ CHANGE
	EZ80_IO
	OUT	(RTCIO),A		; TO HALF CLOCK SPEED
	POP	AF
#ENDIF
	EZ80_IO
	OUT	(SN76489_PORT_LEFT), A
	EZ80_IO
	OUT	(SN76489_PORT_RIGHT), A

#IFDEF SBCV2004
	LD	A,(HB_RTCVAL)
	AND	%11110111		; SBC-V2-004+ CHANGE TO
	EZ80_IO
	OUT	(RTCIO),A		; NORMAL CLOCK SPEED
#ENDIF

	LD	A, L			; RIGHT SHIFT OUT THE LOWER 4 BITS
	RRCA
	RRCA
	RRCA
	RRCA
	AND	$F
	LD	B, A

	LD	A, H
	AND	$3
	RLCA
	RLCA
	RLCA
	RLCA				; AND PLACE IN BITS 5 AND 6
	OR	B			; OR THE TWO SETS OF BITS TO MAKE 2ND PERIOD COMMAND

	AUDTRACE(SNT_REGWR)
	AUDTRACE_A
	AUDTRACE_CR

#IFDEF SBCV2004
	PUSH	AF
	LD	A,(HB_RTCVAL)
	OR	%00001000		; SBC-V2-004+ CHANGE
	EZ80_IO
	OUT	(RTCIO),A		; TO HALF CLOCK SPEED
	POP	AF
#ENDIF
	EZ80_IO
	OUT	(SN76489_PORT_LEFT), A
	EZ80_IO
	OUT	(SN76489_PORT_RIGHT), A

#IFDEF SBCV2004
	LD	A,(HB_RTCVAL)
	AND	%11110111		; SBC-V2-004+ CHANGE TO
	EZ80_IO
	OUT	(RTCIO),A		; NORMAL CLOCK SPEED
#ENDIF

	POP	AF
	POP	BC
	POP	DE
	RET

SN7_DURATION:
	LD	(SN7_PENDING_DURATION),HL	; SET TONE DURATION
	XOR	A
	RET

SN7_DEVICE:
	LD	D,SNDDEV_SN76489	; D := DEVICE TYPE
	LD	E,0			; E := PHYSICAL UNIT
	LD	C,$00			; C := DEVICE TYPE
	LD	H,0			; H := 0, DRIVER HAS NO MODES
	LD	L,SN76489_PORT_LEFT	; L := BASE I/O ADDRESS
	XOR	A
	RET

SN7_BEEP:
	JP	SND_BEEP		; DEFER TO GENERIC CODE IN HBIOS

SN7_FNTBL:
	.DW	SN7_RESET
	.DW	SN7_VOLUME
	.DW	SN7_PERIOD
	.DW	SN7_NOTE
	.DW	SN7_PLAY
	.DW	SN7_QUERY
	.DW	SN7_DURATION
	.DW	SN7_DEVICE
	.DW	SN7_BEEP
;
#IF (($ - SN7_FNTBL) != (SND_FNCNT * 2))
	.ECHO	"*** INVALID SND FUNCTION TABLE ***\n"
	!!!!!
#ENDIF

SN7_PENDING_PERIOD	.DW	0	; PENDING PERIOD (10 BITS)
SN7_PENDING_VOLUME	.DB	0	; PENDING VOL (8 BITS -> DOWNCONVERTED TO 4 BITS AND INVERTED)
SN7_PENDING_DURATION	.DW	0	; PENDING DURATION (16 BITS)

STR_MESSAGELT	.DB	"\r\nSN76489: LEFT IO=0x$"
STR_MESSAGERT	.DB	", RIGHT IO=0x$"

#IF AUDIOTRACE
SNT_INIT		.DB	"\r\nSN7_INIT\r\n$"
SNT_VOLOFF		.DB	"\r\nSN7_VOLUME OFF\r\n$"
SNT_VOL			.DB	"\r\nSN7_VOLUME: $"
SNT_NOTE		.DB	"\r\nSN7_NOTE: $"
SNT_PERIOD		.DB	"\r\nSN7_PERIOD: $"
SNT_PLAY		.DB	"\r\nSN7_PLAY CH: $"
SNT_REGWR		.DB	"\r\nOUT SN76489, $"
#ENDIF
;
;======================================================================
;	QUARTER TONE FREQUENCY TABLE
;======================================================================
;
; THE FOLLOWING TABLE MAPS A FULL OCTAVE OF QUARTER-NOTES
; STARTING AT A# IN OCTAVE 0 TO THE CORRESPONDING PERIOD
; VALUE TO USE ON THE PSG TO ACHIEVE THE DESIRED NOTE FREQUENCY.
;
; THE FREQUENCY PRODUCED BY THE SN76489 IS:
; FREQ = CLOCK / 32 / PERIOD
;
; SO, TO MAP A DESIRED FREQUENCY TO A PERIOD, WE USE:
; PERIOD = CLOCK / 32 / FREQ
;
; IN ORDER TO IMPROVE THE RESOLUTION OF THE FREQUENCY
; VALUE USED, WE ALSO MULTPLY BOTH SIDES OF THE EQUATION
; BY 100:
; PERIOD * 100 = (CLOCK / 32 / FREQ) * 100
;
; THE RESULTING PERIOD VALUE CAN BE REPEATEDLY HALVED
; TO TO JUMP UP AS MANY OCTAVES AS DESIRED.
;
; THE FINAL VALUE IS SHIFTED BY AUD_SCALE BITS
; IN ORDER TO IMPROVE THE RESOLUTION.  THIS FINAL SHIFT
; IS REMOVED WHEN IN THE AY_NOTE ROUTINE.
;
; ASSUMING A CLOCK OF 3.579545 MHZ, THE FIRST PLAYABLE
; NOTE WILL BE A2 (HBIOS NOTE CODE 92).
;
SN7RATIO	.EQU	(SN7CLK * 100) / (32 >> AUD_SCALE)
;
SN7NOTETBL:
	.DW	SN7RATIO / 2913		; A0#/B0b
	.DW	SN7RATIO / 2956         ;
	.DW	SN7RATIO / 2999         ;
	.DW	SN7RATIO / 3042         ;
	.DW	SN7RATIO / 3086         ; B0
	.DW	SN7RATIO / 3131         ; 
	.DW	SN7RATIO / 3177         ; 
	.DW	SN7RATIO / 3223         ; 
	.DW	SN7RATIO / 3270         ; C1
	.DW	SN7RATIO / 3318         ; 
	.DW	SN7RATIO / 3366         ; 
	.DW	SN7RATIO / 3415         ; 
	.DW	SN7RATIO / 3464         ; C1#/D1b
	.DW	SN7RATIO / 3515         ; 
	.DW	SN7RATIO / 3566         ; 
	.DW	SN7RATIO / 3618         ; 
	.DW	SN7RATIO / 3670         ; D1
	.DW	SN7RATIO / 3724         ; 
	.DW	SN7RATIO / 3778         ; 
	.DW	SN7RATIO / 3833         ; 
	.DW	SN7RATIO / 3889         ; D1#/E1b
	.DW	SN7RATIO / 3945         ; 
	.DW	SN7RATIO / 4003         ; 
	.DW	SN7RATIO / 4061         ; 
	.DW	SN7RATIO / 4120         ; E1
	.DW	SN7RATIO / 4180         ; 
	.DW	SN7RATIO / 4241         ; 
	.DW	SN7RATIO / 4302         ; 
	.DW	SN7RATIO / 4365         ; F1
	.DW	SN7RATIO / 4428         ; 
	.DW	SN7RATIO / 4493         ; 
	.DW	SN7RATIO / 4558         ; 
	.DW	SN7RATIO / 4624         ; F1#/G1b
	.DW	SN7RATIO / 4692         ; 
	.DW	SN7RATIO / 4760         ; 
	.DW	SN7RATIO / 4829         ; 
	.DW	SN7RATIO / 4899         ; G1
	.DW	SN7RATIO / 4971         ; 
	.DW	SN7RATIO / 5043         ; 
	.DW	SN7RATIO / 5116         ; 
	.DW	SN7RATIO / 5191         ; G1#/A1b
	.DW	SN7RATIO / 5266         ; 
	.DW	SN7RATIO / 5343         ; 
	.DW	SN7RATIO / 5421         ; 
	.DW	SN7RATIO / 5499         ; A1
	.DW	SN7RATIO / 5579         ; 
	.DW	SN7RATIO / 5661         ; 
	.DW	SN7RATIO / 5743         ; 
