;====================================================================== ; 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 CHANNEL_0_SILENT .EQU $9F CHANNEL_1_SILENT .EQU $BF CHANNEL_2_SILENT .EQU $DF CHANNEL_3_SILENT .EQU $FF ; #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_VOLUME_OFF XOR A ; SIGNAL SUCCESS RET ;====================================================================== ; SN76489 DRIVER - SOUND ADAPTER (SND) FUNCTIONS ;====================================================================== ; SN7_RESET: AUDTRACE(SNT_INIT) CALL SN7_VOLUME_OFF XOR A ; SIGNAL SUCCESS RET SN7_VOLUME_OFF: AUDTRACE(SNT_VOLOFF) #IFDEF SBCV2004 LD A,(HB_RTCVAL) OR %00001000 ; SBC-V2-004+ CHANGE OUT (RTCIO),A ; TO HALF CLOCK SPEED #ENDIF LD A, CHANNEL_0_SILENT OUT (SN76489_PORT_LEFT), A OUT (SN76489_PORT_RIGHT), A LD A, CHANNEL_1_SILENT OUT (SN76489_PORT_LEFT), A OUT (SN76489_PORT_RIGHT), A LD A, CHANNEL_2_SILENT OUT (SN76489_PORT_LEFT), A OUT (SN76489_PORT_RIGHT), A LD A, CHANNEL_3_SILENT OUT (SN76489_PORT_LEFT), A OUT (SN76489_PORT_RIGHT), A #IFDEF SBCV2004 LD A,(HB_RTCVAL) AND %11110111 ; SBC-V2-004+ CHANGE TO OUT (RTCIO),A ; NORMAL CLOCK SPEED #ENDIF 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 OUT (RTCIO),A ; TO HALF CLOCK SPEED POP AF #ENDIF OUT (SN76489_PORT_LEFT), A OUT (SN76489_PORT_RIGHT), A #IFDEF SBCV2004 LD A,(HB_RTCVAL) AND %11110111 ; SBC-V2-004+ CHANGE TO 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 OUT (RTCIO),A ; TO HALF CLOCK SPEED POP AF #ENDIF OUT (SN76489_PORT_LEFT), A OUT (SN76489_PORT_RIGHT), A #IFDEF SBCV2004 LD A,(HB_RTCVAL) AND %11110111 ; SBC-V2-004+ CHANGE TO 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 OUT (RTCIO),A ; TO HALF CLOCK SPEED POP AF #ENDIF OUT (SN76489_PORT_LEFT), A OUT (SN76489_PORT_RIGHT), A #IFDEF SBCV2004 LD A,(HB_RTCVAL) AND %11110111 ; SBC-V2-004+ CHANGE TO 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_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 ; #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 ;