[Back to COMM SWAG index]  [Back to Main SWAG index]  [Original]


program SBDial;

{ This program demonstrates how to use an Adlib or SoundBlaster card
  for automatically dialing a phone number. You just have to hold the
  microphone close to the speakers and run the program. If you have
  implemented your own address list you no longer have to read the
  phone number from the screen and dial it on the phone. }

{ This program was written by Jes R. Klinke, if you include it in your
  own creations a credit would be nice. }

uses
  Crt;

const
  NoteLength   = 100;  { Length of each note in ms }
  NoteDelay    = 50;   { Delay between two notes }
  LongDelay    = 1000; { Long delay between two notes indicated by
                         a hyphen in number }

{ Constants for MiscFlags }

  mfAmpMod     = $80; { Amplitude modulation }
  mfVibrato    = $40;
  mfShortNote  = $20; { No sustain, jump directly from decay to release }
  mfShorten    = $10; { Shorten the note as its pitch rises }

{ Constants for ScalingLevel }
{ Causes output levels to decrease as the frequency rises }

  slNo         = $00; { no change }
  sl15db       = $40; { 1,5 db per octave }
  sl3db        = $80; { 3 db per octave }
  sl6db        = $C0; { 6 db per octave }

{ Sets specified FM register }
procedure SetSBReg(Address, Value: Byte); assembler;
asm
    MOV    DX,0388h
    MOV    AL,Address
    OUT    DX,AL
    MOV    CX,6
@@0:IN AL,DX
    LOOP   @@0
    INC    DX
    MOV    AL,Value
    OUT    DX,AL
    DEC    DX
    MOV    CX,35
@@1:IN AL,DX
    LOOP   @@1
end;

{ Reads then FM status register }
function GetSBStatus: Byte; assembler;
asm
    MOV    DX,0388h
    IN     AL,DX
end;

{ Resets the FM chips }
procedure ResetSB;
var
  Adr: Byte;
begin
  for Adr := 0 to $F5 do
    SetSBReg(Adr, 0);
end;

{ Calculates offset of the control byte for the given channel and operator }
function OperatorOffset(Channel, Operator: Byte): Byte;
begin
  OperatorOffset := Operator * 3 + Channel mod 3 + (Channel div 3) * 8;
end;

{ Sets miscellaneous flags and parameters }
procedure SetMiscParam(Channel, Operator, MiscFlag, FreqvFaktor: Byte);
begin
  SetSBReg($20 + OperatorOffset(Channel, Operator), MiscFlag or FreqvFaktor);
end;

{ Sets volume and scaling level }
procedure SetVolume(Channel, Operator, ScalingLevel, Volume: Byte);
begin
  SetSBReg($40 + OperatorOffset(Channel, Operator), ScalingLevel or Volume);
end;

{ Sets attack, decay, sustain and release rates }
procedure SetADSR(Channel, Operator, Attack, Decay, Sustain, Release: Byte);
begin
  SetSBReg($60 + OperatorOffset(Channel, Operator), Attack shl 4 or Decay);
  SetSBReg($80 + OperatorOffset(Channel, Operator), Sustain shl 4 or Release);
end;

{ Sets feedback 0..7, 0 being no feedback, 7 the most }
{ Also sets whether operator 0 modulates operator 2 or
  they produce sound individually }
procedure SetFeedback(Channel, Feedback: Byte; Separate: Boolean);
begin
  SetSBReg($C0 + Channel, Feedback shl 1 or Byte(Separate));
end;

{ Starts playing the note given by Octave and Freq }
procedure PlayNote(Channel, Octave: Byte; Freq: Word);
begin
  SetSBReg($A0 + Channel, Lo(Freq));
  SetSBReg($B0 + Channel, $20 or Octave shl 2 or Hi(Freq) and 3);
end;

{ Stops playing }
procedure StopNote(Channel, Octave: Byte; Freq: Word);
begin
  SetSBReg($A0 + Channel, Lo(Freq));
  SetSBReg($B0 + Channel, Octave shl 2 or Hi(Freq) and 3);
end;

{ Detect if FM-chips i present }
function DetectSB: Boolean;
var
  Result1, Result2: Byte;
begin
  SetSBReg($4, $60);
  SetSBReg($4, $80);
  Result1 := GetSBStatus;
  SetSBReg($2, $FF);
  SetSBReg($4, $21);
  Delay (10);
  Result2 := GetSBStatus;
  DetectSB := ((Result1 and $E0) = 0) and ((Result2 and $E0) = $C0);
end;

{ Dials the specified phone number }
procedure Dial(const No: string);
const
{ Frequency combinations for the keys on a phone }
  Tone: array [0..11, 0..1] of Byte = (
    (3, 1),
    (0, 0),
    (0, 1),
    (0, 2),
    (1, 0),
    (1, 1),
    (1, 2),
    (2, 0),
    (2, 1),
    (2, 2),
    (3, 0),
    (3, 2));
{ Low frequency of the pair }
  LoFreq: array [0..3] of
    record
      O: Byte;
      F: Word;
    end = (
    (O: 5; F: 457),
    (O: 5; F: 505),
    (O: 5; F: 558),
    (O: 5; F: 617));
{ High frequency of the pair }
  HiFreq: array [0..2] of
    record
      O: Byte;
      F: Word;
    end = (
    (O: 6; F: 396),
    (O: 6; F: 438),
    (O: 6; F: 484));
var
  I, ToneNo: Integer;
begin
  SetMiscParam(0, 0, 0, 1);
  SetVolume(0, 0, 0, 8);
  SetADSR(0, 0, 15, 1, 7, 15);
  SetFeedBack(0, 0, True);
  SetMiscParam(1, 0, 0, 1);
  SetVolume(1, 0, 0, 8);
  SetADSR(1, 0, 15, 1, 7, 15);
  SetFeedBack(1, 0, True);
  SetMiscParam(0, 1, 0, 1);
  SetVolume(0, 1, 0, 8);
  SetADSR(0, 1, 15, 1, 7, 15);
  SetMiscParam(1, 1, 0, 1);
  SetVolume(1, 1, 0, 8);
  SetADSR(1, 1, 15, 1, 7, 15);
  for I := 1 to Length(No) do
  begin
    if No[I] in ['0'..'9', '*', '#'] then
    begin
      { Determine the number of the key }
      if No[I] = '*' then
        ToneNo := 10
      else if No[I] = '#' then
        ToneNo := 11
      else
        ToneNo := Byte(No[I]) - Byte('0');
      { Play the two frequencies in channel 0 and 1}
      PlayNote(0, LoFreq[Tone[ToneNo, 0]].O, LoFreq[Tone[ToneNo, 0]].F);
      PlayNote(1, HiFreq[Tone[ToneNo, 1]].O, HiFreq[Tone[ToneNo, 1]].F);
      Delay(NoteLength);
      { The contral ought to have had enough time to recognize the keypress }
      StopNote(0, LoFreq[Tone[ToneNo, 0]].O, LoFreq[Tone[ToneNo, 0]].F);
      StopNote(1, HiFreq[Tone[ToneNo, 1]].O, HiFreq[Tone[ToneNo, 1]].F);
      Delay(NoteDelay);
    end;
    { a hyphen in the number indicates a longer delay between the keypresses }
    if No[I] = '-' then
      Delay(LongDelay);
  end;
end;

begin
  if not DetectSB then
  begin
    WriteLn('This program requires an Adlib or compatible soundcard');
    Halt;
  end;
  Dial('875 8133');
end.



[Back to COMM SWAG index]  [Back to Main SWAG index]  [Original]