#IF AUDIOTRACE ; #DEFINE AUDTRACE(STR) PUSH DE \ LD DE, STR \ CALL WRITESTR \ POP DE #DEFINE AUDTRACE_A CALL PRTHEXBYTE #DEFINE AUDTRACE_B PUSH AF \ LD A, B \ CALL PRTHEXBYTE \ POP AF #DEFINE AUDTRACE_D PUSH AF \ LD A, D \ CALL PRTHEXBYTE \ POP AF #DEFINE AUDTRACE_E PUSH AF \ LD A, E \ CALL PRTHEXBYTE \ POP AF #DEFINE AUDTRACE_L PUSH AF \ LD A, L \ CALL PRTHEXBYTE \ POP AF #DEFINE AUDTRACE_HL CALL PRTHEXWORDHL #DEFINE AUDTRACE_BC PUSH HL \ PUSH BC \ POP HL \ CALL PRTHEXWORDHL \ POP HL #DEFINE AUDTRACE_DE PUSH HL \ PUSH DE \ POP HL \ CALL PRTHEXWORDHL \ POP HL #DEFINE AUDTRACE_IY PUSH HL \ PUSH IY \ POP HL \ CALL PRTHEXWORDHL \ POP HL ; #DEFINE AUDDEBUG(S) push hl \ CALL PRTSTRD \ .TEXT S \ .TEXT "$" \ pop hl ; $$$$$$ PRINT STRING S TO CONSOLE - PRTD("HELLO") - NO TRAILING $ REQUIRED ; #DEFINE AUDTRACE_CR AUDDEBUG("\r\n$") ; #ELSE ; #DEFINE AUDTRACE(S) #DEFINE AUDTRACE_A #DEFINE AUDTRACE_B #DEFINE AUDTRACE_D #DEFINE AUDTRACE_E #DEFINE AUDTRACE_L #DEFINE AUDTRACE_BC #DEFINE AUDTRACE_HL #DEFINE AUDTRACE_DE #DEFINE AUDTRACE_IY ; #DEFINE AUDDEBUG(STR) ; #DEFINE AUDTRACE_CR ; #ENDIF ; #IFNDEF AUDIO_INC #DEFINE AUDIO_INC ; ; NOTE TABLE VALUES ARE SHIFTED A FEW EXTRA BITS TO MAKE THE ; DIVISION AS ACCURATE AS POSSIBLE. ; AUD_SCALE .EQU 3 ; ; ON ENTRY, DE IS ADDRESS OF NOTE TABLE, HL IS NOTE TO PLAY ; NOTE VALUE 0 MEANS B0b/A0# IN OCTAVE 0 WHICH IS THE FIRST ENTRY ; OF THE NOTE TABLE. THE NOTE TABLE REPRESENTS THE FREQUENCIES ; FOR 1 FULL OCTAVE IN EIGHTH TONES. SINCE THERE ARE 12 HALF TONES ; IN AN OCTAVE, THE TABLE HAS 48 ENTRIES FOR ALL EIGHTH TONES. ; ; ON EXIT, HL CONTAINS THE PERIOD VALUE TO PROGRAM INTO THE PSG ; DERIVED FROM THE NOTE TABLE SCALED TO THE REQUESTED OCTAVE. ; AUD_NOTE: AUDDEBUG("AUDNOTE ") AUDTRACE_HL AUDTRACE_CR AUDTRACE_DE AUDTRACE_CR ; ; START BY SEPARATING THE NOTE AND OCTAVE PORTION ; OF THE INCOMING TONE VALUE IN HL PUSH DE ; SAVE NOTE TABLE ADR LD DE,48 ; 48 QUARTERNOTES PER OCTAVE CALL DIV16 ; SEPARATE OCTAVE AND NOTE ; ; THE QUOTIENT (BC) IS THE OCTAVE NUMBER REQUESTED ; THE REMAINDER (HL) IS THE QUARTER NOTE WITHIN THE OCTAVE. ; ; NOW USE THE QUARTER NOTE VALUE TO LOOKUP THE CORRESPONDING ; PSG PERIOD VALUE IN THE NOTE TABLE. ADD HL,HL ; SCALE FOR 2 BYTE TABLE ENTRY SIZE POP DE ; RECOVER THE TABLE ADR ADD HL,DE ; HL := DESIRED TABLE ENTRY ADR LD A,(HL) ; GET LOW BYTE OF PERIOD TO A INC HL ; POINT TO HIGH BYTE VALUE LD H,(HL) ; GET HIGH BYTE OF PERIOD TO H LD L,A ; PUT LOW BYTE INTO L ; ; NOW WE SCALE THE PERIOD VALUE DOWN BASED ON THE OCTAVE VALUE IN C ; AND BY THE AUD_SCALE VALUE THAT WAS USED IN THE NOTE TABLE. ; FOR EACH OCTAVE, THE PERIOD IS HALVED WHICH DOUBLES THE FREQUENCY ; THAT WILL BE PRODUCED BY THE PSG. SINCE MOVING UP AN OCTAVE ; SHOULD PRODUCE A FREQUENCY THAT IS TWICE THE LOWER OCTIAVE, THIS ; WORKS NICELY, ALTHOUGH SOME ERROR MAY BE INTRODUCED. ; LD A,AUD_SCALE ; SHIFT BY AUD_SCALE BITS ADD A,C ; ... AND OCTAVE LD B,A ; USE AS LOOP COUNTER AUD_NOTE1: SRL H ; RIGHT SHIFT HL RR L ; ... BY ONE BIT DJNZ AUD_NOTE1 ; LOOP UNTIL DONE ; ; IF THE RESULTANT PERIOD IS ZERO, IT MEANS THAT THE REQUESTED ; PERIOD IS TOO LOW (FREQUENCY TOO HIGH) FOR THE PSG TO PRODUCE. LD A, L ; CHECK FOR ZERO OR H ; ... MEANING PSG CAN'T DO IT RET NZ ; IF NOT ZERO, RETURN THE CALCULATED PERIOD DEC HL ; OTHERWISE RETURN -1 PERIOD (ERROR) RET #ENDIF