;====================================================================== ; ; AY-3-8910 / YM2149 SOUND DRIVER ; ; UPDATED BY: DAN WERNER -- 2/11/2024 - DUODYNE SUPPORT ;====================================================================== ; ; AY-3-8910 & YM2149 PSG CHIPS NEED AN INPUT CLOCK FREQUENCY OF ; NO MORE THAN 2 MHZ. THE CLOSEST THING THERE IS TO A STANDARD ; IS THE MSX FREQ OF 1.7897725 MHZ. ; ; @1.7897725 OCTAVE RANGE IS 2 - 7 (Bb2/A#2 .. A7) ; @2.0000000 OCTAVE RANGE IS 2 - 7 (B2 .. A7) ; ; DIFFENCES BETWEEN AY-3-8910 AND YM2149 ; THE AY-3-8910 HAS 16 ENVELOPE LEVELS, YM2149 HAS 32. ; THIS AFFECTS AUDIO OUTPUT ONLY. THERE IS NO PROGRAMMING IMPACT. ; UNUSED BITS IN REGISTERS ARE READ AS ZERO ON AY-3-8910. ; UNUSED BITS CAN BE READ BACK AND WRITTEN ON YM. ; VOLTAGE LEVEL OUTPUT ON A AY-3-8910 IS LOW AND AROUND 2V ON YM2149. ; ; THERE ARE TWO VARIANTS OF AY-3-8910 SOUND CARDS THAT HAVE BEEN ; PRODUCED FOR THE RCBUS. THE ONE PRODUCED BY ED BRINDLEY (EB) USES ; THE SAME PORT FOR REGISTER SELECT (RSEL) AND REGISTER IN (RIN). ; THE ONE PRODUCED BY MARTEN FELDTMANN (MF) USES THE PORT FOLLOWING ; REGISTER SELECT (RSEL) FOR THE REGISTER IN (RIN) PORT. THE FOLLOWING ; EQUATE MUST BE SET CORRECTLY FOR THE HARDWARE BEING USED. THIS ; HAS NOT BEEN MOVED TO A CONFIG VARIABLE BECAUSE THE MF MODULE IS ; RARELY ENCOUNTERED IN THE WILD. ; AY_RCSND .EQU 0 ; 0 = EB MODULE, 1=MF MODULE ; DEVECHO "AY38910: MODE=" ; #IF (AYMODE == AYMODE_SCG) AY_RSEL .EQU $9A AY_RDAT .EQU $9B AY_RIN .EQU AY_RSEL AY_ACR .EQU $9C DEVECHO "SCG" #ENDIF ; #IF (AYMODE == AYMODE_N8) AY_RSEL .EQU $9C AY_RDAT .EQU $9D AY_RIN .EQU AY_RSEL AY_ACR .EQU N8_ACR DEVECHO "N8" #ENDIF ; #IF (AYMODE == AYMODE_RCZ80) AY_RSEL .EQU $D8 AY_RDAT .EQU $D0 AY_RIN .EQU AY_RSEL+AY_RCSND DEVECHO "RCZ80" #ENDIF ; #IF (AYMODE == AYMODE_RCZ180) AY_RSEL .EQU $68 AY_RDAT .EQU $60 AY_RIN .EQU AY_RSEL+AY_RCSND DEVECHO "RCZ180" #ENDIF ; #IF (AYMODE == AYMODE_MSX) AY_RSEL .EQU $A0 AY_RDAT .EQU $A1 AY_RIN .EQU $A2 DEVECHO "MSX" #ENDIF ; #IF (AYMODE == AYMODE_LINC) AY_RSEL .EQU $33 AY_RDAT .EQU $32 AY_RIN .EQU $32 DEVECHO "LINC" #ENDIF ; #IF (AYMODE == AYMODE_MBC) AY_RSEL .EQU $A0 AY_RDAT .EQU $A1 AY_RIN .EQU AY_RSEL AY_ACR .EQU $A2 DEVECHO "MBC" #ENDIF ; #IF (AYMODE == AYMODE_DUO) AY_RSEL .EQU $A4 AY_RDAT .EQU $A5 AY_RIN .EQU AY_RSEL AY_ACR .EQU $A6 DEVECHO "DUO" #ENDIF ; #IF (AYMODE == AYMODE_NABU) AY_RSEL .EQU $41 AY_RDAT .EQU $40 AY_RIN .EQU $40 DEVECHO "NABU" #ENDIF ; DEVECHO ", IO=" DEVECHO AY_RSEL DEVECHO ", CLOCK=" DEVECHO AY_CLK DEVECHO " HZ\n" ; ;====================================================================== ; ; REGISTERS ; AY_R2CHBP .EQU $02 AY_R3CHBP .EQU $03 AY_R7ENAB .EQU $07 AY_R8AVOL .EQU $08 ; ;====================================================================== ; ; DRIVER FUNCTION TABLE AND INSTANCE DATA ; AY_FNTBL: .DW AY_RESET .DW AY_VOLUME .DW AY_PERIOD .DW AY_NOTE .DW AY_PLAY .DW AY_QUERY .DW AY_DURATION .DW AY_DEVICE .DW AY_BEEP ; #IF (($ - AY_FNTBL) != (SND_FNCNT * 2)) .ECHO "*** INVALID SND FUNCTION TABLE ***\n" !!!!! #ENDIF ; AY_IDAT .EQU 0 ; NO INSTANCE DATA ASSOCIATED WITH THIS DEVICE ; ;====================================================================== ; ; DEVICE CAPABILITIES AND CONFIGURATION ; AY_TONECNT .EQU 3 ; COUNT NUMBER OF TONE CHANNELS AY_NOISECNT .EQU 1 ; COUNT NUMBER OF NOISE CHANNELS ;; ;#IF (AY_CLK > 3579545) ; DEPENDING ON THE ;AY_SCALE .EQU 2 ; INPUT CLOCK FREQUENCY ;#ELSE ; PRESCALE THE TONE PERIOD ;AY_SCALE .EQU 3 ; DATA TO MAINTAIN MAXIMUM ;#ENDIF ; RANGE AND ACCURACY ; #INCLUDE "audio.inc" ; ;====================================================================== ; ; DRIVER INITIALIZATION (THERE IS NO PRE-INITIALIZATION) ; ; ANNOUNCE DEVICE ON CONSOLE. ACTIVATE DEVICE IF REQUIRED. ; SETUP FUNCTION TABLES. SETUP THE DEVICE. ; RETURN INITIALIZATION STATUS ; AY38910_INIT: CALL NEWLINE ; ANNOUNCE PRTS("AY:$") ; #IF (AYMODE == AYMODE_SCG) PRTS(" MODE=SCG$") #ENDIF ; #IF (AYMODE == AYMODE_N8) PRTS(" MODE=N8$") #ENDIF ; #IF (AYMODE == AYMODE_RCZ80) PRTS(" MODE=RCZ80$") #ENDIF ; #IF (AYMODE == AYMODE_RCZ180) PRTS(" MODE=RCZ180$") #ENDIF ; #IF (AYMODE == AYMODE_MSX) PRTS(" MODE=MSX$") #ENDIF ; #IF (AYMODE == AYMODE_MBC) PRTS(" MODE=MBC$") #ENDIF ; #IF (AYMODE == AYMODE_DUO) PRTS(" MODE=DUO$") #ENDIF ; #IF (AYMODE == AYMODE_LINC) PRTS(" MODE=LINC$") #ENDIF ; PRTS(" IO=0x$") LD A,AY_RSEL CALL PRTHEXBYTE ; #IF ((AYMODE == AYMODE_SCG) | (AYMODE == AYMODE_N8) | (AYMODE == AYMODE_MBC)) LD A,$FF ; ACTIVATE DEVICE BIT 4 IS AY RESET CONTROL, BIT 3 IS ACTIVE LED OUT (AY_ACR),A ; SET INIT AUX CONTROL REG #ENDIF ; #IF ((AYMODE == AYMODE_DUO)) LD A,$FE ; OUT (AY_ACR),A ; SET INIT AUX CONTROL REG #ENDIF ; LD DE,(AY_R2CHBP*256)+$55 ; SIMPLE HARDWARE PROBE CALL AY_WRTPSG ; WRITE AND CALL AY_RDPSG ; READ TO A LD A,$55 ; SOUND CHANNEL CP E ; REGISTER JR Z,AY_FND ; CALL PRTSTRD \ .TEXT " NOT PRESENT$" ; LD A,$FF ; UNSUCCESSFULL INIT RET ; AY_FND: LD IY, AY_IDAT ; SETUP FUNCTION TABLE LD BC, AY_FNTBL ; POINTER TO INSTANCE DATA LD DE, AY_IDAT ; BC := FUNCTION TABLE ADDRESS CALL SND_ADDENT ; DE := INSTANCE DATA PTR ; CALL AY_RESET ; SET DEFAULT CHIP CONFIGURATION XOR A ; SUCCESSFULL INIT RET ; ;====================================================================== ; INITIALIZE DEVICE ;====================================================================== ; AY_INIT: ; HANDLE R7 SPECIAL #IF (AYMODE == AYMODE_NABU) ; I/O B=INPUT, I/O A=OUTPUT, NOISE CHANNEL C, B, A DISABLE, TONE CHANNEL C, B, A ENABLE LD DE,(AY_R7ENAB*256)+$78 ; SET MIXER CONTROL / IO ENABLE. $78 - 01 111 000 #ELSE ; I/O PORTS = OUTPUT, NOISE CHANNEL C, B, A DISABLE, TONE CHANNEL C, B, A ENABLE LD DE,(AY_R7ENAB*256)+$F8 ; SET MIXER CONTROL / IO ENABLE. $F8 - 11 111 000 #ENDIF CALL AY_WRTPSG ; SETUP R7 ; ; THEN JUST SET ALL OTHER REGISTERS TO ZERO LD E,0 ; VALUE ZERO LD D,0 ; START W/ R0 LD B,7 ; DO 7 REGISTERS (R0-R6) CALL AY_INIT1 ; DO IT INC D ; SKIP R7 LD B,6 ; DO 6 MORE REGISTERS (R8-R13) ; FALL THRU TO DO IT ; AY_INIT1: CALL AY_WRTPSG ; WRITE REGISTER INC D ; BUMP TO NEXT DJNZ AY_INIT1 ; LOOP RET ; ;====================================================================== ; SOUND DRIVER FUNCTION - RESET ; ; INITIALIZE DEVICE. SET VOLUME OFF. RESET VOLUME AND TONE VARIABLES. ; ;====================================================================== ; AY_RESET: AUDTRACE(AYT_INIT) CALL AY_INIT ; SET DEFAULT CHIP CONFIGURATION ; ; RESET DEFAULTS IN CASE OF AN IN-PLACE HBIOS RESTART LD HL,0 LD (AY_PENDING_PERIOD),HL ; SET TONE PERIOD TO ZERO LD (AY_PENDING_DURATION),HL; SET DURATION TO ZERO XOR A ; SIGNAL SUCCESS LD (AY_PENDING_VOLUME),A ; SET VOLUME TO ZERO RET ; DONE, A=0 ABOVE ; ;====================================================================== ; SOUND DRIVER FUNCTION - VOLUME ;====================================================================== ; AY_VOLUME: AUDTRACE(AYT_VOL) AUDTRACE_L AUDTRACE_CR LD A,L ; SAVE VOLUME LD (AY_PENDING_VOLUME), A ; XOR A ; SIGNAL SUCCESS RET ; ;====================================================================== ; SOUND DRIVER FUNCTION - NOTE ;====================================================================== ; AY_NOTE: LD DE, AY3NOTETBL CALL AUD_NOTE ; RETURNS PERIOD IN HL, FALL THRU ; ;====================================================================== ; SOUND DRIVER FUNCTION - PERIOD ;====================================================================== ; AY_PERIOD: AUDTRACE(AYT_PERIOD) AUDTRACE_HL AUDTRACE_CR ; LD A,H ; IF ZERO - ERROR OR L JR Z,AY_PERIOD1 ; LD A,H ; MAXIMUM TONE PERIOD IS 12-BITS AND 11110000B ; ALLOWED RANGE IS 0001-0FFF (4095) JR NZ,AY_PERIOD1 ; RETURN NZ IF NUMBER TOO LARGE LD (AY_PENDING_PERIOD),HL ; SAVE AND RETURN SUCCESSFUL XOR A ; SET SUCCESS RET ; AY_PERIOD1: LD HL,$FFFF ; REQUESTED PERIOD IS LARGER LD (AY_PENDING_PERIOD),HL ; THAN PSG CAN SUPPORT, SO OR $FF ; SET PERIOD TO $FFFF RET ; AND RETURN FAILURE ; ;====================================================================== ; SOUND DRIVER FUNCTION - PLAY ; B = FUNCTION ; C = AUDIO DEVICE ; D = CHANNEL ; A = EXIT STATUS ;====================================================================== ; AY_PLAY: AUDTRACE(AYT_PLAY) AUDTRACE_D AUDTRACE_CR ; LD A, (AY_PENDING_PERIOD + 1) ; CHECK THE HIGH BYTE OF THE PERIOD INC A JR NZ, AY_PLAY1 ; PERIOD IS OK, CONTINUE OR $FF ; ELSE TOO LARGE, SIGNAL FAILURE RET ; AND RETURN ; AY_PLAY1: PUSH HL PUSH DE LD A,D ; LIMIT CHANNEL 0-2 AND $3 ; AND INDEX TO THE ADD A,A ; CHANNEL REGISTER LD D,A ; FOR THE TONE PERIOD ; AUDTRACE(AYT_REGWR) AUDTRACE_A AUDTRACE_CR ; LD HL,AY_PENDING_PERIOD ; WRITE THE LOWER LD E,(HL) ; 8-BITS OF THE TONE PERIOD CALL AY_WRTPSG INC D ; NEXT REGISTER INC HL ; NEXT BYTE LD E,(HL) ; WRITE THE UPPER CALL AY_WRTPSG ; 8-BITS OF THE TONE PERIOD ; POP DE ; RECALL CHANNEL PUSH DE ; SAVE CHANNEL ; LD A,D ; LIMIT CHANNEL 0-2 AND $3 ; AND INDEX TO THE ADD A,AY_R8AVOL ; CHANNEL VOLUME LD D,A ; REGISTER ; AUDTRACE(AYT_REGWR) AUDTRACE_A AUDTRACE_CR ; INC HL ; NEXT BYTE LD A,(HL) ; PENDING VOLUME RRCA ; MAP THE VOLUME RRCA ; FROM 00-FF RRCA ; TO 00-0F RRCA AND $0F LD E,A CALL AY_WRTPSG ; SET VOL (E) IN CHANNEL REG (D) ; POP DE ; RECALL CHANNEL POP HL ; XOR A ; SIGNAL SUCCESS RET ; ;====================================================================== ; SOUND DRIVER FUNCTION - QUERY AND SUBFUNCTIONS ;====================================================================== ; AY_QUERY: LD A, E CP BF_SNDQ_CHCNT ; SUB FUNCTION 01 JR Z, AY_QUERY_CHCNT ; CP BF_SNDQ_VOLUME ; SUB FUNCTION 02 JR Z, AY_QUERY_VOLUME ; CP BF_SNDQ_PERIOD ; SUB FUNCTION 03 JR Z, AY_QUERY_PERIOD ; CP BF_SNDQ_DEV ; SUB FUNCTION 04 JR Z, AY_QUERY_DEV ; OR $FF ; SIGNAL FAILURE RET ; AY_QUERY_CHCNT: LD BC,(AY_TONECNT*256)+AY_NOISECNT ; RETURN NUMBER OF XOR A ; TONE AND NOISE RET ; CHANNELS IN BC ; AY_QUERY_PERIOD: LD HL, (AY_PENDING_PERIOD) ; RETURN 16-BIT PERIOD XOR A ; IN HL REGISTER RET ; AY_QUERY_VOLUME: LD A, (AY_PENDING_VOLUME) ; RETURN 8-BIT VOLUME LD L, A ; IN L REGISTER XOR A ; LD H, A RET ; AY_QUERY_DEV: LD B, SNDDEV_AY38910 ; RETURN DEVICE IDENTIFIER LD DE, (AY_RSEL*256)+AY_RDAT ; AND ADDRESS AND DATA PORT XOR A RET ; ;====================================================================== ; SOUND DRIVER FUNCTION - DURATION ;====================================================================== ; AY_DURATION: LD (AY_PENDING_DURATION),HL ; SET TONE DURATION XOR A RET ; ;====================================================================== ; SOUND DRIVER FUNCTION - DEVICE ;====================================================================== ; AY_DEVICE: LD D,SNDDEV_AY38910 ; D := DEVICE TYPE LD E,0 ; E := PHYSICAL UNIT LD C,$00 ; C := DEVICE TYPE LD H,AYMODE ; H := MODE LD L,AY_RSEL ; L := BASE I/O ADDRESS XOR A RET ; ;====================================================================== ; SOUND DRIVER FUNCTION - BEEP ;====================================================================== ; AY_BEEP: JP SND_BEEP ; DEFER TO GENERIC CODE IN HBIOS ; ;====================================================================== ; ; WRITE DATA IN E REGISTER TO DEVICE REGISTER D ; INTERRUPTS DISABLE DURING WRITE. WRITE IN SLOW MODE IF Z180 CPU. ; ;====================================================================== ; AY_WRTPSG: #IFDEF SBCV2004 LD A,(HB_RTCVAL) ; GET CURRENT RTC LATCH VALUE OR %00001000 ; SBC-V2-004 CHANGE OUT (RTCIO),A ; TO HALF CLOCK SPEED #ENDIF #IF (CPUFAM == CPU_Z180) IN0 A,(Z180_DCNTL) ; GET WAIT STATES PUSH AF ; SAVE VALUE OR %00110000 ; FORCE SLOW OPERATION (I/O W/S=3) OUT0 (Z180_DCNTL),A ; AND UPDATE DCNTL #ENDIF LD A,D ; SELECT THE REGISTER WE EZ80_IO OUT (AY_RSEL),A ; WANT TO WRITE TO LD A,E ; WRITE THE VALUE TO EZ80_IO OUT (AY_RDAT),A ; THE SELECTED REGISTER #IF (CPUFAM == CPU_Z180) POP AF ; GET SAVED DCNTL VALUE OUT0 (Z180_DCNTL),A ; AND RESTORE IT #ENDIF #IFDEF SBCV2004 LD A,(HB_RTCVAL) ; SBC-V2-004 CHANGE TO OUT (RTCIO),A ; NORMAL CLOCK SPEED #ENDIF RET ; ;====================================================================== ; ; READ FROM REGISTER D AND RETURN WITH RESULT IN E ; AY_RDPSG: #IFDEF SBCV2004 LD A,(HB_RTCVAL) ; GET CURRENT RTC LATCH VALUE OR %00001000 ; SBC-V2-004 CHANGE OUT (RTCIO),A ; TO HALF CLOCK SPEED #ENDIF #IF (CPUFAM == CPU_Z180) IN0 A,(Z180_DCNTL) ; GET WAIT STATES PUSH AF ; SAVE VALUE OR %00110000 ; FORCE SLOW OPERATION (I/O W/S=3) OUT0 (Z180_DCNTL),A ; AND UPDATE DCNTL #ENDIF LD A,D ; SELECT THE REGISTER WE EZ80_IO OUT (AY_RSEL),A ; WANT TO READ EZ80_IO IN A,(AY_RIN) ; READ SELECTED REGISTER LD E,A #IF (CPUFAM == CPU_Z180) POP AF ; GET SAVED DCNTL VALUE OUT0 (Z180_DCNTL),A ; AND RESTORE IT #ENDIF #IFDEF SBCV2004 LD A,(HB_RTCVAL) ; SBC-V2-004 CHANGE TO OUT (RTCIO),A ; NORMAL CLOCK SPEED #ENDIF RET ; ;====================================================================== ; AY_PENDING_PERIOD .DW 0 ; PENDING PERIOD (12 BITS) ; ORDER AY_PENDING_VOLUME .DB 0 ; PENDING VOL (8 BITS) ; SIGNIFICANT AY_PENDING_DURATION .DW 0 ; PENDING DURATION (16 BITS) ; #IF AUDIOTRACE AYT_INIT .DB "\r\nAY_INIT\r\n$" AYT_VOLOFF .DB "\r\nAY_VOLUME OFF\r\n$" AYT_VOL .DB "\r\nAY_VOLUME: $" AYT_NOTE .DB "\r\nAY_NOTE: $" AYT_PERIOD .DB "\r\nAY_PERIOD $" AYT_PLAY .DB "\r\nAY_PLAY CH: $" AYT_REGWR .DB "\r\nOUT AY-3-8910 $" #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 AY-3-8910 IS: ; FREQ = CLOCK / 16 / PERIOD ; ; SO, TO MAP A DESIRED FREQUENCY TO A PERIOD, WE USE: ; PERIOD = CLOCK / 16 / 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 / 16 / 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 1.7897725 MHZ, THE FIRST PLAYABLE ; NOTE WILL BE A0#/B0b (HBIOS NOTE CODE 0). ; AY_RATIO .EQU (AY_CLK * 100) / (16 >> AUD_SCALE) ; AY3NOTETBL: .DW AY_RATIO / 2913 ; A0#/B0b 178977250 / 2913 = 61440; PROOF: 61440 >> 3 = 7680, 3579545 / 7680 / 16 = 29.13 .DW AY_RATIO / 2956 ; .DW AY_RATIO / 2999 ; .DW AY_RATIO / 3042 ; .DW AY_RATIO / 3086 ; B0 .DW AY_RATIO / 3131 ; .DW AY_RATIO / 3177 ; .DW AY_RATIO / 3223 ; .DW AY_RATIO / 3270 ; C1 .DW AY_RATIO / 3318 ; .DW AY_RATIO / 3366 ; .DW AY_RATIO / 3415 ; .DW AY_RATIO / 3464 ; C1#/D1b .DW AY_RATIO / 3515 ; .DW AY_RATIO / 3566 ; .DW AY_RATIO / 3618 ; .DW AY_RATIO / 3670 ; D1 .DW AY_RATIO / 3724 ; .DW AY_RATIO / 3778 ; .DW AY_RATIO / 3833 ; .DW AY_RATIO / 3889 ; D1#/E1b .DW AY_RATIO / 3945 ; .DW AY_RATIO / 4003 ; .DW AY_RATIO / 4061 ; .DW AY_RATIO / 4120 ; E1 .DW AY_RATIO / 4180 ; .DW AY_RATIO / 4241 ; .DW AY_RATIO / 4302 ; .DW AY_RATIO / 4365 ; F1 .DW AY_RATIO / 4428 ; .DW AY_RATIO / 4493 ; .DW AY_RATIO / 4558 ; .DW AY_RATIO / 4624 ; F1#/G1b .DW AY_RATIO / 4692 ; .DW AY_RATIO / 4760 ; .DW AY_RATIO / 4829 ; .DW AY_RATIO / 4899 ; G1 .DW AY_RATIO / 4971 ; .DW AY_RATIO / 5043 ; .DW AY_RATIO / 5116 ; .DW AY_RATIO / 5191 ; G1#/A1b .DW AY_RATIO / 5266 ; .DW AY_RATIO / 5343 ; .DW AY_RATIO / 5421 ; .DW AY_RATIO / 5499 ; A1 .DW AY_RATIO / 5579 ; .DW AY_RATIO / 5661 ; .DW AY_RATIO / 5743 ;