program ADDA(Input, Output);

{ This program is to be used in conjunction with the analog extension
  board described in the note "ANALOG I/O BOARD FOR PC'S". It is assumed
  that the AD7569 is operated in one of its two bipolar modes (-1.25 V -->
  +1.25 V or -2.5 --> +2.5 V). }

uses Dos, Crt, Graph;

const Title= 'ADDA V1.0 - Jos Groot (September 24, 1992)';

{$R-} { this prevents Turbo Pascal from complaining about putting a Byte
        into a ShortInt type variable }

const Status = $300 ;  { bit 0 is 1 when an AD conversion is going on and
                        becomes 0 upon completion of the conversion }
      ADC    = $301 ;  { read : get result of most recently completed AD
                        conversion and start the next }
      DAC    = ADC  ;  { write: start DA conversion }
      Max_Sam= 60000;  { maximum number of samples }

var Buf: array[0..Max_Sam] of ShortInt; { global array receiving samples }

{ ******* }

procedure Initialize_Graphics;

{ initializes graphics mode }

var GraphMode, GraphDriver, ErrorCode: Integer;

begin
  GraphDriver:= Detect;
  InitGraph(GraphDriver,GraphMode,'.');
  ErrorCode:= GraphResult;

  if ErrorCode<> GrOk then
  begin
    WriteLn; WriteLn;
    WriteLn('Graphics error: ', GraphErrorMsg(ErrorCode));
    WriteLn('Sorry, I have to quit...');
    Halt(1)
  end;

  SetColor(White)
end;

{ ******* }

procedure Flush_Keyboard_Buffer;

{ flushes the keyboard buffer by reading all pending characters }

var Kar: Char;

begin
  while KeyPressed do Kar:= ReadKey
end;

{ ******* }

procedure Get_Samples(Samples: Word);

{ reads as fast as possible samples 0,1,...,Samples into array Buf. Sample
  Buf[0] should not be used, because this is the ADC result of a conversion
  started at an unknown earlier time. }

var i: Word;
    Pause: Integer;

begin
  for i:= 0 to Samples do { start at 0, 1 is the first good sample to use }
  begin
    asm   { small delay to enable the ADC to complete a conversion before  }
      nop { reading the result. The necessity and length of this delay may }
      nop { vary for different AD7569's. }
      nop
    end;

{   for Pause:= 1 to 50 do;} { add this loop to decrease sampling frequency }

    Buf[i]:= Port[ADC] { get and store sample & initiate next conversion }
  end
end;

{ ******* }

procedure Compute_Statistics(Samples: Word;
                             var Average, RMS_Amp: Real;
                             var Min, Max: ShortInt);

{ computes the average, RMS amplitude, minimum and maximum of the samples
  Buf[1], Buf[2], ..., Buf[Samples] }

var s: ShortInt;
    i: Word;
    Sum, Sum2: Real;

begin
  Sum :=    0;
  Sum2:=    0; { sum of squares }
  Min :=  127;
  Max := -128;

  for i:= 1 to Samples do
  begin
    s:= Buf[i];
    Sum := Sum  + s;
    Sum2:= Sum2 + Sqr(s);
    if s< Min then Min:= s;
    if s> Max then Max:= s
  end;

  Average:= Sum/Samples;
  RMS_Amp:= Sqrt(Sum2/Samples - Sqr(Average))
end;

{ ******* }

procedure Sampling_Frequency(var Fs: Real);

{ computes the sampling frequency Fs of procedure Get_Samples from the time
  it takes to measure n*Max_Sam samples }

const n= 20;

var h, m, s, s100: Word;
    i: Integer;
    Start, Duration: LongInt;

begin
  WriteLn; WriteLn; WriteLn;
  Write('Taking ', n*Max_Sam:1, ' samples at ... ');
  Gettime(h, m, s, s100);
  Start:= Round(360000.0*h+6000.0*m+100.0*s+s100);

  for i:= 1 to n do Get_Samples(Max_Sam);

  Gettime(h, m, s, s100);
  Duration:= Round(360000.0*h+6000.0*m+100.0*s+s100) - Start;
  Fs:= n*Max_Sam/Duration/10;
  Write(Fs:1:1, ' KHz sampling frequency approximately.');

  repeat until KeyPressed
end;

{ ******* }

procedure Plot_Samples;

{ takes samples and plots these with some statistical information }

var x, CenterY, MinY, MaxY, Samples: Integer;
    Min, Max: ShortInt;
    Average, RMS_Amp: Real;
    Ave, RMS, Mi, Ma: String[6];

begin
  Initialize_Graphics;

  Samples:= GetMaxX+1;    { number of samples to take and plot    }

  CenterY:= GetMaxY div 2;
  MinY:= CenterY-127;     { Y coordinate for most positive sample }
  MaxY:= CenterY+128;     { Y coordinate for most negative sample }

  MoveTo(0, MinY-1);      { border line for most positive sample  }
  LineTo(GetMaxX, MinY-1);
  MoveTo(0, MaxY+1);      { border line for most negative sample  }
  LineTo(GetMaxX, MaxY+1);

  SetViewPort(0, MinY, GetMaxX, MaxY, Clipoff);
  OutTextXY(0, -40, 'minimum/maximum/average/RMS amplitude: ');
  SetFillStyle(EmptyFill, Black);

  repeat
    Get_Samples(Samples);

    MoveTo(0, 127-Buf[1]);
    for x:= 2 to Samples do LineTo(x, 127-Buf[x+1]); { plot samples }

    { compute and print some statistical information }

    Compute_Statistics(Samples, Average, RMS_Amp, Min, Max);
    Str(Average:1:2, Ave);
    Str(RMS_Amp:1:2, RMS);
    Str(Min:1, Mi);
    Str(Max:1, Ma);
    Bar(308, -40, 480, -32); { whipe previous values from the screen }
    OutTextXY(308, -40, Mi + ' ' + Ma + ' ' + Ave + ' ' + RMS);

    Delay(1000);
    ClearviewPort { only the data area is cleared }
  until KeyPressed;

  CloseGraph
end;

{ ******* }

procedure ADC_To_DAC;

{ reroutes ADC input directly to DAC output }

begin
  repeat
    Port[ADC]:= Port[DAC]
  until KeyPressed
end;

{ ******* }

procedure Distortion;

{ reads ADC values, and outputs 100 to the DAC for the ones with absolute
  value>= Clip_Level. This produces a compressed and heavily distorted
  sound. }

const Clip_Level= 10; { should at least exceed the maximum value of absolute
                        noise samples }

var s: ShortInt;
    Table: array[-128..127] of ShortInt; { lookup table for fast execution }

begin
  for s:= -128 to 127 do
    if s>  Clip_Level then Table[s]:= 100 else
    if s< -Clip_Level then Table[s]:= 100 else { -100 for softer distortion }
                           Table[s]:= Abs(Round(s/Clip_Level*100));

  repeat
    Port[DAC]:= Table[ShortInt(Port[ADC])]
  until KeyPressed
end;

{ ******* }

procedure Echo;

{ produces an echo by adding ADC samples from some time ago to the present
  samples, and outputting the result to the DAC }

var i, j, d: Word;
    k, s, Pause, Buffers: Integer;
    Table: array[-128..127] of Integer; { lookup table for fast execution }
    Dr: Real;

begin
  Pause  := 20   ; { Pause determines the sampling frequency (40 kHz)       }
  d      := 20000; { d and Pause determine the delay time (20E3/40E3=0.5 s) }
  Dr     := 0.5  ; { Dr is inversely proportional to the decay rate         }
  Buffers:= 0    ; { number of Max_Sam byte Buffers processed               }

  for i:= 0 to Max_Sam do Buf[i]:= 0;            { clear buffer }
  for k:= -128 to 127 do Table[k]:= Round(Dr*k); { fill table }

  WriteLn; WriteLn; WriteLn;
  Write('Number of ', Max_Sam:1, ' byte buffers processed: ');

  repeat
    GotoXY(41,16);    { print the number of processed buffers indicating }
    Write(Buffers:1); { the sampling frequency }
    Inc(Buffers);

    for i:= 0 to Max_Sam do
    begin
      for k:= 1 to Pause do;                      { lower sampling frequency }
      if i>=d then j:= i-d else j:= i-d+Max_Sam+1; { j= index delayed sample }
      s:= ShortInt(Port[ADC]) + Table[Buf[j]];     { compute original + echo }
      if s<-128 then s:= -128 else if s>127 then s:= 127; { correct overflow }
      Buf[i]:= s;                                  { store compound sample   }
      Port[DAC]:= s                                { output sample to DAC    }
    end
  until KeyPressed
end;

{ ******* }

procedure Quit;

{ resets DAC output to 0 Volts and halts program }

begin
  Port[DAC]:= 0;
  Halt
end;

{ ******* }

procedure Menu;

{ presents the user different actions to choose from }

var i, Choice: Integer;
    Fs: Real; { sampling frequency determined by menu item 1 }

begin
  Fs:= 0; { sampling frequency not yet determined }

  repeat
    ClrScr;

    for i:= 1 to Length(Title)+4 do Write('*');
    WriteLn;
    WriteLn('* ', Title, ' *');
    for i:= 1 to Length(Title)+4 do Write('*');
    WriteLn; WriteLn; WriteLn;

    Write('1. Determine highest sampling frequency ');
    if Fs>0 then WriteLn('(', Fs:1:1, ' KHz)') else WriteLn;
    WriteLn('2. Plot samples');
    WriteLn('3. Reroute ADC input directly to DAC output');
    WriteLn('4. Compression/distortion');
    WriteLn('5. Echo');
    WriteLn('6. Quit');
    WriteLn;

    repeat
      GotoXY(1, 13);
      Write('Your choice: ');
      Choice:= Ord(ReadKey) - Ord('0')
    until Choice in [1..6];

    Write(Choice);

    case Choice of
      1: Sampling_Frequency(Fs);
      2: Plot_Samples;
      3: ADC_To_DAC;
      4: Distortion;
      5: Echo;
      6: Quit
    end;

    if Choice= 2 then Flush_KeyBoard_Buffer
  until False
end;

{ ******* }

begin
  Menu
end.
