Unit TIASound; {*****************************************************************************} {* *} {* Module: TIA Chip Sound Simulator, V1.0 *} {* Purpose: To emulate the sound generation hardware of the Atari TIA chip. *} {* Author: Ron Fries *} {* Date: September 10, 1996 *} {* *} {*****************************************************************************} {* *} {* License Information and Copyright Notice *} {* ======================================== *} {* *} {* TiaSound is Copyright(c) 1996 by Ron Fries *} {* *} {* This library is free software; you can redistribute it and/or modify it *} {* under the terms of version 2 of the GNU Library General Public License *} {* as published by the Free Software Foundation. *} {* *} {* This library is distributed in the hope that it will be useful, but *} {* WITHOUT ANY WARRANTY; without even the implied warranty of *} {* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library *} {* General Public License for more details. *} {* To obtain a copy of the GNU Library General Public License, write to the *} {* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *} {* *} {* Any permitted reproduction of these routines, in whole or in part, must *} {* bear this legend. *} {* *} {*****************************************************************************} Interface Const SET_TO_1 = $00; { 0000 } POLY4 = $01; { 0001 } DIV31_POLY4 = $02; { 0010 } POLY5_POLY4 = $03; { 0011 } PURE = $04; { 0100 } PURE2 = $05; { 0101 } DIV31_PURE = $06; { 0110 } POLY5_2 = $07; { 0111 } POLY9 = $08; { 1000 } POLY5 = $09; { 1001 } DIV31_POLY5 = $0A; { 1010 } POLY5_POLY5 = $0B; { 1011 } DIV3_PURE = $0C; { 1100 } DIV3_PURE2 = $0D; { 1101 } DIV93_PURE = $0E; { 1110 } DIV3_POLY5 = $0F; { 1111 } DIV3_MASK = $0C; AUDC0 = $15; AUDC1 = $16; AUDF0 = $17; AUDF1 = $18; AUDV0 = $19; AUDV1 = $1A; { the size (in entries) of the 4 polynomial tables } POLY4_SIZE = $000F; POLY5_SIZE = $001F; POLY9_SIZE = $01FF; { channel definitions } CHAN1 = 0; CHAN2 = 1; Bit4 : Array[0..POLY4_SIZE - 1] Of Boolean = (True,True,False,True,True,True,False,False,False,False,True,False,True,False,False); Bit5 : Array[0..POLY5_SIZE - 1] Of Boolean = (False,False,True,False,True,True,False,False,True,True,True,True,True,False,False, False,True,True,False,True,True,True,False,True,False,True,False,False,False,False,True); { I've treated the 'Div by 31' counter as another polynomial because of } { the way it operates. It does not have a 50% duty cycle, but instead } { has a 13:18 ratio (of course, 13+18 = 31). This could also be } { implemented by using counters. } Div31 : Array[0..POLY5_SIZE - 1] Of Boolean = (False,True ,False,False,False,False,False,False,False,False,False,False, False,False,False,False,False,False,False,True ,False,False,False,False, False,False,False,False,False,False,False); { Rather than have a table with 511 entries, I use a random number } { generator. } ProcessSet : Boolean = False; Var Bit9 : Array[0..POLY9_SIZE - 1] Of Boolean; P4 : Array[0..1] Of Word; { Position pointer for the 4-bit POLY array } P5 : Array[0..1] Of Word; { Position pointer for the 5-bit POLY array } P9 : Array[0..1] Of Word; { Position pointer for the 9-bit POLY array } Div_n_cnt : Array[0..1] Of Byte; { Divide by n counter. one for each channel } Div_n_max : Array[0..1] Of Byte; { Divide by n maximum, one for each channel } { In my routines, I treat the sample output as another divide by N counter. } { For better accuracy, the Samp_n_cnt has a fixed binary decimal point } { which has 8 binary digits to the right of the decimal point. } Samp_n_max : Word; { Sample max, multiplied by 256 } Samp_n_cnt : Word; { Sample cnt. } AUDC : Array[0..1] Of Byte; { AUDCx (15, 16) } AUDF : Array[0..1] Of Byte; { AUDFx (17, 18) } AUDV : Array[0..1] Of Byte; { AUDVx (19, 1A) } OutVol : Array[0..1] Of Byte; { last output volume for each channel } _audc0,_audv0,_audc1,_audv1 : Byte; div_n_cnt0,div_n_cnt1 : Byte; p5_0,p5_1 : Word; outvol_0,outvol_1 : ShortInt;{Byte;} ProcessJump0 : Array[0..15] Of Word; ProcessJump1 : Array[0..15] Of Word; Const B9: Boolean = False; Procedure Tia_sound_init(sample_freq,playback_freq: Word); Function Update_tia_sound(addr: Word; _val: Byte): Boolean; Procedure Tia_process(buffer: Pointer; n: Word); Procedure Tia_process1(buffer: Pointer; n: Word); Procedure Tia_process2(buffer: Pointer; n: Word); Procedure ProcessSound(buffer: Pointer; n: Word); Implementation Procedure ProcessSound(buffer: Pointer; n: Word); External; {$L PROCESS.OBJ} { define some data types to keep it platform independent } { CONSTANT DEFINITIONS } { definitions for AUDCx (15, 16) } { LOCAL GLOBAL VARIABLE DEFINITIONS } { structures to hold the 6 tia sound control bytes } { Initialze the bit patterns for the polynomials. } { The 4bit and 5bit patterns are the identical ones used in the tia chip. } { Though the patterns could be packed with 8 bits per byte, using only a } { single bit per byte keeps the math simple, which is important for } { efficient processing. } {***************************************************************************} { Module: Tia_sound_init() } { Purpose: to handle the power-up initialization functions } { these functions should only be executed on a cold-restart } { } { Author: Ron Fries } { Date: September 10, 1996 } { } { Inputs: sample_freq - the value for the '30 Khz' Tia audio clock } { playback_freq - the playback frequency in samples per second } { } { Outputs: Adjusts local globals - no return value } { } {***************************************************************************} Procedure Tia_sound_init(sample_freq,playback_freq: Word); Var chan : Byte; n : Integer; L : LongInt; Begin { fill the 9bit polynomial with random bits } If Not B9 Then Begin for n := 0 To POLY9_SIZE - 1 Do Bit9[n] := (Random(2) = 1); { fill poly9 with random bits } for n := 0 To POLY9_SIZE - 1 Do Bit9[n] := (Random(2) = 1); { fill poly9 with random bits } B9 := True; End; { calculate the sample 'divide by N' value based on the playback freq. } L := Sample_Freq; Samp_n_max := Round((L Shl 8) / playback_freq); Samp_n_cnt := 0; { initialize all bits of the sample counter } { initialize the local globals } for chan := CHAN1 To CHAN2 Do Begin Outvol[chan] := 0; Div_n_cnt[chan] := 0; Div_n_max[chan] := 0; AUDC[chan] := 0; AUDF[chan] := 0; AUDV[chan] := 0; P4[chan] := 0; P5[chan] := 0; P9[chan] := 0; End; { For Chan } End; { Tia_sound_init } {***************************************************************************} { Module: Update_tia_sound() } { Purpose: To process the latest control values stored in the AUDF, AUDC, } { and AUDV registers. It pre-calculates as much information as } { possible for better performance. This routine has not been } { optimized. } { } { Author: Ron Fries } { Date: September 10, 1996 } { } { Inputs: addr - the address of the parameter to be changed } { val - the new value to be placed in the specified address } { } { Outputs: Adjusts local globals - no return value } { } {***************************************************************************} Function Update_tia_sound(addr: Word; _val: Byte): Boolean; Const new_val: Integer = 0; Var chan : Byte; B: Boolean; Begin B := False; { determine which address was changed } Case addr Of AUDC0: Begin B := (AUDC[0] <> (_val And $0f)); AUDC[0] := _val And $0f; chan := 0; End; AUDC1: Begin B := (AUDC[1] <> (_val And $0f)); AUDC[1] := _val And $0f; chan := 1; End; AUDF0: Begin B := (AUDF[0] <> (_val And $1f)); AUDF[0] := _val And $1f; chan := 0; End; AUDF1: Begin B := (AUDF[1] <> (_val And $1f)); AUDF[1] := _val And $1f; chan := 1; End; AUDV0: Begin B := (AUDV[0] <> ((_val And $0f) Shl 3)); AUDV[0] := (_val And $0f) Shl 3; chan := 0; End; AUDV1: Begin B := (AUDV[1] <> ((_val And $0f) Shl 3)); AUDV[1] := (_val And $0f) Shl 3; chan := 1; End; Else chan := 255; End; { Case } { If the output value changed } If chan <> 255 Then Begin { an AUDC value of 0 is a special case } If AUDC[chan] = SET_TO_1 Then Begin { indicate the clock is zero so no processing will occur } new_val := 0; { and set the output to the selected volume } Outvol[chan] := AUDV[chan]; End Else Begin { otherwise calculate the 'divide by N' value } new_val := AUDF[chan] + 1; { If bits 2 And 3 are set, then multiply the 'div by n' count by 3 } If (AUDC[chan] And DIV3_MASK) = DIV3_MASK Then new_val := new_val * 3; End; { only reset those channels that have changed } If new_val <> Div_n_max[chan] Then Begin { reset the divide by n counters } Div_n_max[chan] := new_val; Div_n_cnt[chan] := new_val; { reset the polynomial counters } P4[chan] := 0; P5[chan] := 0; P9[chan] := 0; End; End; Update_TIA_Sound := B; End; { Update_tia_sound } (* {***************************************************************************} { Module: Tia_process_2() } { Purpose: To fill the output buffer with the sound output based on the } { tia chip parameters. This routine has not been optimized. } { Though it is not used by the program, I've left it for reference.} { } { Author: Ron Fries } { Date: September 10, 1996 } { } { Inputs: *buffer - pointer to the buffer where the audio output will } { be placed } { n - size of the playback buffer } { } { Outputs: the buffer will be filled with n bytes of audio - no return val } { } {***************************************************************************} void Tia_process_2 (register unsigned char *buffer, register uint16 n) { register uint8 chan; { loop until the buffer is filled } while (n) { { loop through the channels } for (chan = CHAN1; chan <= CHAN2; chan++) { { NOTE: this routine intentionally does not count down to zero } { since 0 is used as a special case - no clock } { If the divide by N counter can count down } If (Div_n_cnt[chan] > 1) { { decrement and loop } Div_n_cnt[chan]--; } { otherwise If we've reached the bottom } Else If (Div_n_cnt[chan] == 1) { { reset the counter } Div_n_cnt[chan] = Div_n_max[chan]; { the P5 counter has multiple uses, so we inc it here } P5[chan]++; If (P5[chan] == POLY5_SIZE) P5[chan] = 0; { check clock modifier for clock tick } { If we're using pure tones OR we're using DIV31 and the DIV31 bit is set OR we're using POLY5 and the POLY5 bit is set } If (((AUDC[chan] And $02) == 0) Or (((AUDC[chan] And $01) == 0) And Div31[P5[chan]]) Or (((AUDC[chan] And $01) == 1) And Bit5[P5[chan]])) { If (AUDC[chan] And $04) { pure modified clock selected } { If (Outvol[chan]) { If the output was set } Outvol[chan] = 0; { turn it off } Else Outvol[chan] = AUDV[chan]; { Else turn it on } } Else If (AUDC[chan] And $08) { check for p5/p9 } { If (AUDC[chan] == POLY9) { check for poly9 } { { inc the poly9 counter } P9[chan]++; If (P9[chan] == POLY9_SIZE) P9[chan] = 0; If (Bit9[P9[chan]]) { If poly9 bit is set } Outvol[chan] = AUDV[chan]; Else Outvol[chan] = 0; } Else { must be poly5 } { If (Bit5[P5[chan]]) Outvol[chan] = AUDV[chan]; Else Outvol[chan] = 0; } } Else { poly4 is the only remaining option } { { inc the poly4 counter } P4[chan]++; If (P4[chan] == POLY4_SIZE) P4[chan] = 0; If (Bit4[P4[chan]]) Outvol[chan] = AUDV[chan]; Else Outvol[chan] = 0; } } } } { decrement the sample counter - value is 256 since the lower byte contains the fractional part } Samp_n_cnt -= 256; { If the count down has reached zero } If (Samp_n_cnt < 256) { { adjust the sample counter } Samp_n_cnt += Samp_n_max; { calculate the latest output value and place in buffer } *(buffer++) = Outvol[0] + Outvol[1]; { and indicate one less byte to process } n--; } } } *) {***************************************************************************} { Module: Tia_process() } { Purpose: To fill the output buffer with the sound output based on the } { tia chip parameters. This routine has been optimized. } { } { Author: Ron Fries } { Date: September 10, 1996 } { } { Inputs: *buffer - pointer to the buffer where the audio output will } { be placed } { n - size of the playback buffer } { } { Outputs: the buffer will be filled with n bytes of audio - no return val } { } {***************************************************************************} Procedure Tia_process(buffer: Pointer; n: Word); Var _audc0,_audv0,_audc1,_audv1 : Byte; div_n_cnt0,div_n_cnt1 : Byte; p5_0,p5_1,outvol_0,outvol_1 : Byte; OV0,OV1: Integer; _OutVol : Byte; Begin _audc0 := AUDC[0]; _audv0 := AUDV[0]; _audc1 := AUDC[1]; _audv1 := AUDV[1]; { make temporary local copy } p5_0 := P5[0]; p5_1 := P5[1]; outvol_0 := Outvol[0]; outvol_1 := Outvol[1]; div_n_cnt0 := Div_n_cnt[0]; div_n_cnt1 := Div_n_cnt[1]; { loop until the buffer is filled } while N <> 0 Do Begin { Process channel 0 } If div_n_cnt0 > 1 Then Dec(div_n_cnt0) Else If div_n_cnt0 = 1 Then Begin div_n_cnt0 := Div_n_max[0]; { the P5 counter has multiple uses, so we inc it here } Inc(p5_0); If p5_0 = POLY5_SIZE Then p5_0 := 0; { check clock modifier for clock tick } If (((_audc0 And $02) = 0) Or (((_audc0 And $01) = 0) And Div31[p5_0]) Or (((_audc0 And $01) = 1) And Bit5[p5_0])) Then Begin If (_audc0 And $04) <> 0 Then { pure modified clock selected } Begin If outvol_0 <> 0 Then { If the output was set } outvol_0 := 0 { turn it off } Else outvol_0 := _audv0; { Else turn it on } End Else If (_audc0 And $08) <> 0 Then { check for p5/p9 } Begin If _audc0 = POLY9 Then { check for poly9 } Begin { inc the poly9 counter } Inc(P9[0]); If P9[0] = POLY9_SIZE Then P9[0] := 0; If Bit9[P9[0]] Then outvol_0 := _audv0 Else outvol_0 := 0; End Else { must be poly5 } Begin If Bit5[p5_0] Then outvol_0 := _audv0 Else outvol_0 := 0; End; End Else { poly4 is the only remaining option } Begin { inc the poly4 counter } Inc(P4[0]); If P4[0] = POLY4_SIZE Then P4[0] := 0; If Bit4[P4[0]] Then outvol_0 := _audv0 Else outvol_0 := 0; End; End; End; { Process channel 1 } If div_n_cnt1 > 1 Then Dec(div_n_cnt1) Else If div_n_cnt1 = 1 Then Begin div_n_cnt1 := Div_n_max[1]; { the P5 counter has multiple uses, so we inc it here } Inc(p5_1); If p5_1 = POLY5_SIZE Then p5_1 := 0; { check clock modifier for clock tick } If (((_audc1 And $02) = 0) Or (((_audc1 And $01) = 0) And Div31[p5_1]) Or (((_audc1 And $01) = 1) And Bit5[p5_1])) Then Begin If (_audc1 And $04) <> 0 Then { pure modified clock selected } Begin If outvol_1 <> 0 Then { If the output was set } outvol_1 := 0 { turn it off } Else outvol_1 := _audv1; { Else turn it on } End Else If (_audc1 And $08) <> 0 Then { check for p5/p9 } Begin If _audc1 = POLY9 Then { check for poly9 } Begin { inc the poly9 counter } Inc(P9[1]); If P9[1] = POLY9_SIZE Then P9[1] := 0; If Bit9[P9[1]] Then outvol_1 := _audv1 Else outvol_1 := 0; End Else { must be poly5 } Begin If Bit5[p5_1] Then outvol_1 := _audv1 Else outvol_1 := 0; End; End Else { poly4 is the only remaining option } Begin { inc the poly4 counter } Inc(P4[1]); If P4[1] = POLY4_SIZE Then P4[1] := 0; If Bit4[P4[1]] Then outvol_1 := _audv1 Else outvol_1 := 0; End; End; End; { decrement the sample counter - value is 256 since the lower byte contains the fractional part } Dec(Samp_n_cnt,256); { If the count down has reached zero } If Samp_n_cnt < 256 Then Begin { adjust the sample counter } Inc(Samp_n_cnt,Samp_n_max); { calculate the latest output value and place in buffer } Byte(Buffer^) := outvol_0 + outvol_1; { OV0 := OutVol_0 - (_audv0 Div 2); OV1 := OutVol_1 - (_audv1 Div 2); _OutVol := (OV0 + OV1) + 128; Byte(Buffer^) := _OutVol; } Inc(LongInt(Buffer)); { and indicate one less byte to process } Dec(N); End; End; { While } { save for next round } P5[0] := p5_0; P5[1] := p5_1; Outvol[0] := outvol_0; Outvol[1] := outvol_1; Div_n_cnt[0] := div_n_cnt0; Div_n_cnt[1] := div_n_cnt1; End; { Tia_process } Procedure Tia_process1(buffer: Pointer; n: Word); {Var _audc0,_audv0,_audc1,_audv1 : Byte; div_n_cnt0,div_n_cnt1 : Byte; p5_0,p5_1,outvol_0,outvol_1 : Byte;} Begin _audc0 := AUDC[0]; _audv0 := AUDV[0]; _audc1 := AUDC[1]; _audv1 := AUDV[1]; { make temporary local copy } p5_0 := P5[0]; p5_1 := P5[1]; outvol_0 := Outvol[0]; outvol_1 := Outvol[1]; div_n_cnt0 := Div_n_cnt[0]; div_n_cnt1 := Div_n_cnt[1]; { loop until the buffer is filled } Asm { Self-modifying code for more speed } MOV AX,WORD PTR Div_N_Max MOV BYTE PTR CS:[@Rep1 - 1],AL MOV BYTE PTR CS:[@Rep2 - 1],AH MOV AL,BYTE PTR _audv0 MOV AH,BYTE PTR _audv1 MOV BYTE PTR CS:[@Rep3 - 1],AL MOV BYTE PTR CS:[@Rep4 - 1],AH { Load commonly-used values into registers } MOV BL,BYTE PTR OutVol_0 MOV BH,BYTE PTR OutVol_1 MOV CL,BYTE PTR _audc0 MOV CH,BYTE PTR _audc1 MOV DL,0FFh LES DI,DWORD PTR Buffer CLD CMP WORD PTR N,0 JZ @GetOut @CheckN: { Process channel 0 } CMP BYTE PTR Div_N_Cnt0,1 JBE @IsOne DEC BYTE PTR Div_N_Cnt0 JMP @Channel1 @IsOne: JNE @Channel1 { MOV AL,BYTE PTR Div_N_Max MOV BYTE PTR Div_N_Cnt0,AL} MOV BYTE PTR Div_N_Cnt0,12h { Self-modified } @Rep1: INC WORD PTR P5_0 CMP WORD PTR P5_0,POLY5_SIZE JNE @L2 MOV WORD PTR P5_0,0 @L2: TEST CL,2 JZ @Cont1 MOV SI,WORD PTR P5_0 MOV AL,BYTE PTR Div31[SI] OR AL,AL JZ @L3 TEST CL,1 JZ @Cont1 @L3: TEST BYTE PTR Bit5[SI],CL JNZ @Cont1 JMP @Channel1 @Cont1: TEST CL,4 JZ @Not4 OR BL,BL JNZ @NotZero JMP @True2 @NotZero: SUB BL,BL JMP @Channel1 @Not4: TEST CL,8 JZ @Not8 CMP CL,POLY9 JNE @NotPoly9 INC WORD PTR P9 CMP WORD PTR P9,POLY9_SIZE JNE @NotPOLY9_SIZE MOV WORD PTR P9,0 @NotPOLY9_SIZE: MOV SI,WORD PTR P9 TEST BYTE PTR Bit9[SI],DL{0FFh} JNZ @True2 SUB BL,BL JMP @Channel1 @NotPoly9: MOV SI,WORD PTR P5_0 TEST BYTE PTR Bit5[SI],DL{0FFh} JNZ @True2 SUB BL,BL JMP @Channel1 @Not8: INC WORD PTR P4 CMP WORD PTR P4,POLY4_SIZE JNE @NotPOLY4_SIZE MOV WORD PTR P4,0 @NotPOLY4_SIZE: MOV SI,WORD PTR P4 TEST BYTE PTR Bit4[SI],DL{0FFh} JNZ @True2 SUB BL,BL JMP @Channel1 @True2: MOV BL,12h{DL} { Self-modified } @Rep3: { ------------------------------------------------------ } @Channel1: { Process channel 1 } CMP BYTE PTR Div_N_Cnt1,1 JBE @IsOneA DEC BYTE PTR Div_N_Cnt1 JMP @Next @IsOneA: JNE @Next { MOV AL,BYTE PTR Div_N_Max[1] MOV BYTE PTR Div_N_Cnt1,AL} MOV BYTE PTR Div_N_Cnt1,12h { Self-modified } @Rep2: INC WORD PTR P5_1 CMP WORD PTR P5_1,POLY5_SIZE JNE @L2A MOV WORD PTR P5_1,0 @L2A: TEST CH,2 JZ @Cont1A MOV SI,WORD PTR P5_1 MOV AL,BYTE PTR Div31[SI] OR AL,AL JZ @L3A TEST CL,1 JZ @Cont1A @L3A: TEST BYTE PTR Bit5[SI],CH JNZ @Cont1A JMP @Next @Cont1A: TEST CH,4 JZ @Not4A OR BH,BH JNZ @NotZeroA JMP @True2A @NotZeroA: SUB BH,BH JMP @Next @Not4A: TEST CH,8 JZ @Not8A CMP CH,POLY9 JNE @NotPoly9A INC WORD PTR P9[2] CMP WORD PTR P9[2],POLY9_SIZE JNE @NotPOLY9_SIZEA MOV WORD PTR P9[2],0 @NotPOLY9_SIZEA: MOV SI,WORD PTR P9[2] TEST BYTE PTR Bit9[SI],DL{0FFh} JNZ @True2A SUB BH,BH JMP @Next @NotPoly9A: MOV SI,WORD PTR P5_1 TEST BYTE PTR Bit5[SI],DL{0FFh} JNZ @True2A SUB BH,BH JMP @Next @Not8A: INC WORD PTR P4[2] CMP WORD PTR P4[2],POLY4_SIZE JNE @NotPOLY4_SIZEA MOV WORD PTR P4[2],0 @NotPOLY4_SIZEA: MOV SI,WORD PTR P4[2] TEST BYTE PTR Bit4[SI],DL{0FFh} JNZ @True2A SUB BH,BH JMP @Next @True2A: MOV BH,12h{DH} { Self-modified } @Rep4: { ------------------------------------------------------ } @Next: DEC BYTE PTR Samp_N_Cnt[1] JNZ @Continue MOV AX,WORD PTR Samp_N_Max ADD WORD PTR Samp_N_Cnt,AX SUB AX,AX MOV AL,BL ADD AL,BH STOSB DEC WORD PTR N JZ @GetOut { ------------------------------------------------------ } @Continue: JMP @CheckN @GetOut: MOV WORD PTR OutVol,BX End; { Asm } P5[0] := p5_0; P5[1] := p5_1; { Outvol[0] := outvol_0; Outvol[1] := outvol_1;} Div_n_cnt[0] := div_n_cnt0; Div_n_cnt[1] := div_n_cnt1; (* while N <> 0 Do Begin { Process channel 0 } If div_n_cnt0 > 1 Then Dec(div_n_cnt0) Else If div_n_cnt0 = 1 Then Begin div_n_cnt0 := Div_n_max[0]; { the P5 counter has multiple uses, so we inc it here } Inc(p5_0); If p5_0 = POLY5_SIZE Then p5_0 := 0; { check clock modifier for clock tick } If (((_audc0 And $02) = 0) Or (((_audc0 And $01) = 0) And Div31[p5_0]) Or (((_audc0 And $01) = 1) And Bit5[p5_0])) Then Begin If (_audc0 And $04) <> 0 Then { pure modified clock selected } Begin If outvol_0 <> 0 Then { If the output was set } outvol_0 := 0 { turn it off } Else outvol_0 := _audv0; { Else turn it on } End Else If (_audc0 And $08) <> 0 Then { check for p5/p9 } Begin If _audc0 = POLY9 Then { check for poly9 } Begin { inc the poly9 counter } Inc(P9[0]); If P9[0] = POLY9_SIZE Then P9[0] := 0; If Bit9[P9[0]] Then outvol_0 := _audv0 Else outvol_0 := 0; End Else { must be poly5 } Begin If Bit5[p5_0] Then outvol_0 := _audv0 Else outvol_0 := 0; End; End Else { poly4 is the only remaining option } Begin { inc the poly4 counter } Inc(P4[0]); If P4[0] = POLY4_SIZE Then P4[0] := 0; If Bit4[P4[0]] Then outvol_0 := _audv0 Else outvol_0 := 0; End; End; End; { Process channel 1 } If div_n_cnt1 > 1 Then Dec(div_n_cnt1) Else If div_n_cnt1 = 1 Then Begin div_n_cnt1 := Div_n_max[1]; { the P5 counter has multiple uses, so we inc it here } Inc(p5_1); If p5_1 = POLY5_SIZE Then p5_1 := 0; { check clock modifier for clock tick } If (((_audc1 And $02) = 0) Or (((_audc1 And $01) = 0) And Div31[p5_1]) Or (((_audc1 And $01) = 1) And Bit5[p5_1])) Then Begin If (_audc1 And $04) <> 0 Then { pure modified clock selected } Begin If outvol_1 <> 0 Then { If the output was set } outvol_1 := 0 { turn it off } Else outvol_1 := _audv1; { Else turn it on } End Else If (_audc1 And $08) <> 0 Then { check for p5/p9 } Begin If _audc1 = POLY9 Then { check for poly9 } Begin { inc the poly9 counter } Inc(P9[1]); If P9[1] = POLY9_SIZE Then P9[1] := 0; If Bit9[P9[1]] Then outvol_1 := _audv1 Else outvol_1 := 0; End Else { must be poly5 } Begin If Bit5[p5_1] Then outvol_1 := _audv1 Else outvol_1 := 0; End; End Else { poly4 is the only remaining option } Begin { inc the poly4 counter } Inc(P4[1]); If P4[1] = POLY4_SIZE Then P4[1] := 0; If Bit4[P4[1]] Then outvol_1 := _audv1 Else outvol_1 := 0; End; End; End; { decrement the sample counter - value is 256 since the lower byte contains the fractional part } Dec(Samp_n_cnt,256); { If the count down has reached zero } If Samp_n_cnt < 256 Then Begin { adjust the sample counter } Inc(Samp_n_cnt,Samp_n_max); { calculate the latest output value and place in buffer } Byte(Buffer^) := outvol_0 + outvol_1; Inc(LongInt(Buffer)); { and indicate one less byte to process } Dec(N); End; End; { While } { save for next round } P5[0] := p5_0; P5[1] := p5_1; Outvol[0] := outvol_0; Outvol[1] := outvol_1; Div_n_cnt[0] := div_n_cnt0; Div_n_cnt[1] := div_n_cnt1; *) End; { Tia_process1 } Procedure Tia_process2(buffer: Pointer; n: Word); Var _audc0,_audv0,_audc1,_audv1 : Byte; div_n_cnt0,div_n_cnt1 : Byte; p5_0,p5_1 : Byte; outvol_0,outvol_1 : Word; Vol0Dir,Vol1Dir : Boolean; Begin _audc0 := AUDC[0]; _audv0 := AUDV[0]; _audc1 := AUDC[1]; _audv1 := AUDV[1]; { make temporary local copy } p5_0 := P5[0]; p5_1 := P5[1]; outvol_0 := Outvol[0]; outvol_1 := Outvol[1]; div_n_cnt0 := Div_n_cnt[0]; div_n_cnt1 := Div_n_cnt[1]; { loop until the buffer is filled } Vol0Dir := False; Vol1Dir := False; while N <> 0 Do Begin { Process channel 0 } If div_n_cnt0 > 1 Then Dec(div_n_cnt0) Else If div_n_cnt0 = 1 Then Begin div_n_cnt0 := Div_n_max[0]; { the P5 counter has multiple uses, so we inc it here } Inc(p5_0); If p5_0 = POLY5_SIZE Then p5_0 := 0; { check clock modifier for clock tick } If (((_audc0 And $02) = 0) Or (((_audc0 And $01) = 0) And Div31[p5_0]) Or (((_audc0 And $01) = 1) And Bit5[p5_0])) Then Begin If (_audc0 And $04) <> 0 Then { pure modified clock selected } Begin If outvol_0 <> 0 Then { If the output was set } Begin { If (outvol_0 < _audv0) And Vol0Dir Then outvol_0 := _audv0 Else} outvol_0 := outvol_0 Shr 1; Vol0Dir := False; End Else Begin If outvol_0 = 0 then outvol_0 := _audv0 Shr 1 Else outvol_0 := outvol_0 Shl 1; If outvol_0 > _audv0 Then outvol_0 := _audv0; { Else turn it on } Vol0Dir := True; End; End Else If (_audc0 And $08) <> 0 Then { check for p5/p9 } Begin If _audc0 = POLY9 Then { check for poly9 } Begin { inc the poly9 counter } Inc(P9[0]); If P9[0] = POLY9_SIZE Then P9[0] := 0; If Bit9[P9[0]] Then Begin If outvol_0 = 0 then outvol_0 := _audv0 Shr 1 Else outvol_0 := outvol_0 Shl 1; If outvol_0 > _audv0 Then outvol_0 := _audv0; Vol0Dir := True; End Else Begin { If (outvol_0 < _audv0) And Vol0Dir Then outvol_0 := _audv0 Else} outvol_0 := outvol_0 Shr 1; Vol0Dir := False; End; End Else { must be poly5 } Begin If Bit5[p5_0] Then Begin If outvol_0 = 0 then outvol_0 := _audv0 Shr 1 Else outvol_0 := outvol_0 Shl 1; If outvol_0 > _audv0 Then outvol_0 := _audv0; Vol0Dir := True; End Else Begin { If (outvol_0 < _audv0) And Vol0Dir Then outvol_0 := _audv0 Else} outvol_0 := outvol_0 Shr 1; Vol0Dir := False; End; End; End Else { poly4 is the only remaining option } Begin { inc the poly4 counter } Inc(P4[0]); If P4[0] = POLY4_SIZE Then P4[0] := 0; If Bit4[P4[0]] Then Begin If outvol_0 = 0 then outvol_0 := _audv0 Shr 1 Else outvol_0 := outvol_0 Shl 1; If outvol_0 > _audv0 Then outvol_0 := _audv0; Vol0Dir := True; End Else Begin { If (outvol_0 < _audv0) And Vol0Dir Then outvol_0 := _audv0 Else} outvol_0 := outvol_0 Shr 1; Vol0Dir := False; End; End; End; End; { Process channel 1 } If div_n_cnt1 > 1 Then Dec(div_n_cnt1) Else If div_n_cnt1 = 1 Then Begin div_n_cnt1 := Div_n_max[1]; { the P5 counter has multiple uses, so we inc it here } Inc(p5_1); If p5_1 = POLY5_SIZE Then p5_1 := 0; { check clock modifier for clock tick } If (((_audc1 And $02) = 0) Or (((_audc1 And $01) = 0) And Div31[p5_1]) Or (((_audc1 And $01) = 1) And Bit5[p5_1])) Then Begin If (_audc1 And $04) <> 0 Then { pure modified clock selected } Begin If outvol_1 <> 0 Then { If the output was set } Begin { If (outvol_1 < _audv1) And Vol1Dir Then outvol_1 := _audv1 Else} outvol_1 := outvol_1 Shr 1; { turn it off } Vol1Dir := False; End Else Begin If outvol_1 = 0 then outvol_1 := _audv0 Shr 1 Else outvol_1 := outvol_1 Shl 1; If outvol_1 > _audv1 Then outvol_1 := _audv1; { Else turn it on } Vol1Dir := True; End; End Else If (_audc1 And $08) <> 0 Then { check for p5/p9 } Begin If _audc1 = POLY9 Then { check for poly9 } Begin { inc the poly9 counter } Inc(P9[1]); If P9[1] = POLY9_SIZE Then P9[1] := 0; If Bit9[P9[1]] Then Begin If outvol_1 = 0 then outvol_1 := _audv0 Shr 1 Else outvol_1 := outvol_1 Shl 1; If outvol_1 > _audv1 Then outvol_1 := _audv1; Vol1Dir := True; End Else Begin { If (outvol_1 < _audv1) And Vol1Dir Then outvol_1 := _audv1 Else} outvol_1 := outvol_1 Shr 1; Vol1Dir := False; End; End Else { must be poly5 } Begin If Bit5[p5_1] Then Begin If outvol_1 = 0 then outvol_1 := _audv0 Shr 1 Else outvol_1 := outvol_1 Shl 1; If outvol_1 > _audv1 Then outvol_1 := _audv1; Vol1Dir := True; End Else Begin { If (outvol_1 < _audv1) And Vol1Dir Then outvol_1 := _audv1 Else} outvol_1 := outvol_1 Shr 1; Vol1Dir := False; End; End; End Else { poly4 is the only remaining option } Begin { inc the poly4 counter } Inc(P4[1]); If P4[1] = POLY4_SIZE Then P4[1] := 0; If Bit4[P4[1]] Then Begin If outvol_1 = 0 then outvol_1 := _audv0 Shr 1 Else outvol_1 := outvol_1 Shl 1; If outvol_1 > _audv1 Then outvol_1 := _audv1; Vol1Dir := True; End Else Begin { If (outvol_1 < _audv1) And Vol1Dir Then outvol_1 := _audv1 Else} outvol_1 := outvol_1 Shr 1; Vol1Dir := False; End; End; End; End; { decrement the sample counter - value is 256 since the lower byte contains the fractional part } Dec(Samp_n_cnt,256); { If the count down has reached zero } If Samp_n_cnt < 256 Then Begin { adjust the sample counter } Inc(Samp_n_cnt,Samp_n_max); { calculate the latest output value and place in buffer } Byte(Buffer^) := outvol_0 + outvol_1; Inc(LongInt(Buffer)); { and indicate one less byte to process } Dec(N); End; End; { While } { save for next round } P5[0] := p5_0; P5[1] := p5_1; Outvol[0] := outvol_0; Outvol[1] := outvol_1; Div_n_cnt[0] := div_n_cnt0; Div_n_cnt[1] := div_n_cnt1; End; { Tia_process2 } End.