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.
