{***************************************}
{  Adlib Low Level Unit                 }
{  By Carl Eric Codre                  }
{  Montral, Canada - September 1995    }
{***************************************}
{ Contact me at: carl.codere@evening.magicnet.com }
{  From 1995-1998 you can also contact me at:     }
{      cecodere@andrew.sca.usherb.ca (preferred)  }
{ INFORMATION:                                                             }
{   FIRST AND FOREMOST -> Jeffrey S. Lee for his documentation on the      }
{      OPL2 Chipset found in the Adlib Sound Card.                         }
{   UNDISCLOSED ADLIB CODE -> some BASIC Code written by Adlib, which      }
{      actually contained some bugs.                                       }
{   SBFMDRV Tracing to find out how to calculate the SemiTones and Note    }
{      modifications.                                                      }
{   and last but not least: The person who gave me the original idea of    }
{      starting this project: Ezra Dreisbach with his C SCR FM Player.     }

{  I did not succeed in creating the sounds exactly like the SBFMDRV (this }
{  was my ultimate goal), but it is quite close, except in a few rare cases}
{  where the notes will sound awful.                                       }
{  If you know how to create sound quality as good as the SBFMDRV let me   }
{  know!!!                                                                 }

(* Conditions for use of this source code:                                *)
(*      - No re-release of this or the modified source code is done.      *)
(*      - Proper credit is given to the author of these routines.         *)
(*      - That I be notified of any bugfixes / major upgrades.            *)


(* In percussive mode, the last 4 voices (SD TOM HH CYMB) are created      *)
(* using melodic voices 7 & 8. A noise generator uses channels 7 & 8       *)
(* frequency information for creating rhythm instruments. Best result      *)
(* are obtained by setting TOM two octaves below middle C and SD 7         *)
(* half-tones above TOM.                                                   *)

{  Updates/Bugfixes:                                                       }
{      (09/12/95)                                                          }
{   $      Removed some bugs in array definitions, forgot to load Self     }
{          pointer for Object fields in certain routines.                  }
{   $  (09/15/95)                                                          }
{          Forgot Object Inherited Init -> I.e Data WAS NOT initialized to }
{          zero.                                                           }

{$S+}
{$R+}
{$Q+}
{$I-}
Unit Sound;

(* Declaration for this module's routines *)
Interface

Uses Objects,Crt;
{-----------------------------------------------------------------}
Const
  { Do not forget that the voices start from voice ZERO ! }
  Modulator = 0;
  Carrier =   1;
(* Percussive voice numbers *)
  BassDrumVoice  = 6;          { Bass Drum Voice number   }
  SnareDrumVoice = 7;          { Snare Drum Voice number  }
  TomTomVoice    = 8;          { Tom-Tom Voice Number     }
  CymbalVoice    = 9;          { Cymbal Voice number      }
  HihatVoice     = 10;         { Hihat Voice number       }

Const
 NoteTab:Array[0..126] of Byte = (
		  $00,$01,$02,$03,$04,$05,
		  $06,$07,$08,$09,$0a,$0b,
		  $00,$01,$02,$03,$04,$05,
		  $06,$07,$08,$09,$0a,$0b,
		  $10,$11,$12,$13,$14,$15,
		  $16,$17,$18,$19,$1a,$1b,
		  Ord(' '),Ord('!'),Ord('"'),
		  Ord('#'),Ord('$'),Ord('%'),
		  Ord('&'), 00,     Ord('('),
		  Ord(')'),Ord('*'),Ord('+'),
		  Ord('0'),Ord('1'),Ord('2'),
		  Ord('3'),Ord('4'),Ord('5'),
		  Ord('6'),Ord('7'),Ord('8'),
		  Ord('9'),Ord(':'),Ord(';'),
		  Ord('@'),Ord('A'),Ord('B'),
		  $43,$44,$45,$46,$47,$48,
		  $49,$4a,$4b,$50,$51,$52,
		  $53,$54,$55,$56,$57,$58,
		  Ord('Y'),Ord('Z'),Ord('['),
		  Ord('`'),Ord('a'),Ord('b'),
		  Ord('c'),Ord('d'),Ord('e'),
		  Ord('f'),Ord('g'),Ord('i'),
		  Ord('j'),Ord('k'),Ord('p'),
		  Ord('q'),Ord('r'),Ord('s'),
		  Ord('t'),Ord('u'),
		  Ord('v'),Ord('w'),Ord('x'),
		  Ord('y'),Ord('z'),Ord('{'),
		  Ord('p'),Ord('q'),Ord('r'),
		  Ord('s'),Ord('t'),Ord('u'),
		  Ord('v'),Ord('w'),Ord('x'),
		  Ord('y'),Ord('z'),Ord('{'),
		  Ord('{'),Ord('{'),Ord('{'),
		  Ord('{'),Ord('{'),Ord('{'),
		  Ord('{'),Ord('{')
		  );
FreqTable:Array[0..767] of Byte = (
		  Ord('W'),
		  $01,$58,$01,$58,$01,$59,
		  $01,$59,$01,$5a,$01,$5b,
		  $01,$5b,$01,$5c,$01,$5d,
		  $01,$5d,$01,$5e,$01,$5e,
		  $01,$5f,$01,$60,$01,$60,
		  $01,$61,$01,$62,$01,$62,
		  $01,$63,$01,$64,$01,$64,
		  $01,$65,$01,$65,$01,$66,
		  $01,$67,$01,$67,$01,$68,
		  $01,$69,$01,$69,$01,$6a,
		  $01,$6b,$01,$6b,$01,$6c,
		  $01,$6d,$01,$6d,$01,$6e,
		  $01,$6f,$01,$6f,$01,$70,
		  $01,$71,$01,$71,$01,$72,
		  $01,$73,$01,$73,$01,$74,
		  $01,$75,$01,$75,$01,$76,
		  $01,$77,$01,$77,$01,$78,
		  $01,$79,$01,$79,$01,$7a,
		  $01,$7b,$01,$7b,$01,$7c,
		  $01,$7d,$01,$7d,$01,$7e,
		  $01,$7f,$01,$80,$01,$80,
		  $01,$81,$01,$82,$01,$82,
		  $01,$83,$01,$84,$01,$84,
		  $01,$85,$01,$86,$01,$87,
		  $01,$87,$01,$88,$01,$89,
		  $01,$89,$01,$8a,$01,$8b,
		  $01,$8b,$01,$8c,$01,$8d,
		  $01,$8e,$01,$8e,$01,$8f,
		  $01,$90,$01,$91,$01,$91,
		  $01,$92,$01,$93,$01,$93,
		  $01,$94,$01,$95,$01,$96,
		  $01,$96,$01,$97,$01,$98,
		  $01,$99,$01,$99,$01,$9a,
		  $01,$9b,$01,$9c,$01,$9c,
		  $01,$9d,$01,$9e,$01,$9e,
		  $01,$09f,$01,$0a0,$01,$0a1,
		  $01,$0a1,$01,$0a2,$01,$0a3,
		  $01,$0a4,$01,$0a5,$01,$0a5,
		  $01,$0a6,$01,$0a7,$01,$0a8,
		  $01,$0a8,$01,$0a9,$01,$0aa,
		  $01,$0ab,$01,$0ab,$01,$0ac,
		  $01,$0ad,$01,$0ae,$01,$0ae,
		  $01,$0af,$01,$0b0,$01,$0b1,
		  $01,$0b2,$01,$0b2,$01,$0b3,
		  $01,$0b4,$01,$0b5,$01,$0b6,
		  $01,$0b6,$01,$0b7,$01,$0b8,
		  $01,$0b9,$01,$0ba,$01,$0ba,
		  $01,$0bb,$01,$0bc,$01,$0bd,
		  $01,$0be,$01,$0be,$01,$0bf,
		  $01,$0c0,$01,$0c1,$01,$0c2,
		  $01,$0c2,$01,$0c3,$01,$0c4,
		  $01,$0c5,$01,$0c6,$01,$0c6,
		  $01,$0c7,$01,$0c8,$01,$0c9,
		  $01,$0ca,$01,$0cb,$01,$0cb,
		  $01,$0cc,$01,$0cd,$01,$0ce,
		  $01,$0cf,$01,$0d0,$01,$0d0,
		  $01,$0d1,$01,$0d2,$01,$0d3,
		  $01,$0d4,$01,$0d5,$01,$0d5,
		  $01,$0d6,$01,$0d7,$01,$0d8,
		  $01,$0d9,$01,$0da,$01,$0db,
		  $01,$0db,$01,$0dc,$01,$0dd,
		  $01,$0de,$01,$0df,$01,$0e0,
		  $01,$0e1,$01,$0e1,$01,$0e2,
		  $01,$0e3,$01,$0e4,$01,$0e5,
		  $01,$0e6,$01,$0e7,$01,$0e8,
		  $01,$0e8,$01,$0e9,$01,$0ea,
		  $01,$0eb,$01,$0ec,$01,$0ed,
		  $01,$0ee,$01,$0ef,$01,$0f0,
		  $01,$0f0,$01,$0f1,$01,$0f2,
		  $01,$0f3,$01,$0f4,$01,$0f5,
		  $01,$0f6,$01,$0f7,$01,$0f8,
		  $01,$0f9,$01,$0fa,$01,$0fa,
		  $01,$0fb,$01,$0fc,$01,$0fd,
		  $01,$0fe,$01,$0ff,$01,$00,
		  $02,$01,$02,$02,$02,$03,
		  $02,$04,$02,$05,$02,$06,
		  $02,$06,$02,$07,$02,$08,
		  $02,$09,$02,$0a,$02,$0b,
		  $02,$0c,$02,$0d,$02,$0e,
		  $02,$0f,$02,$10,$02,$11,
		  $02,$12,$02,$13,$02,$14,
		  $02,$15,$02,$16,$02,$17,
		  $02,$18,$02,$19,$02,$1a,
		  $02,$1a,$02,$1b,$02,$1c,
		  $02,$1d,$02,$1e,$02,$1f,
		  $02,$20,$02,$21,$02,$22,
		  $02,$23,$02,$24,$02,$25,
		  $02,$26,$02,$27,$02,$28,
		  $02,$29,$02,$2a,$02,$2b,
		  $02,$2c,$02,$2d,$02,$2e,
		  $02,$2f,$02,$30,$02,$31,
		  $02,$32,$02,$33,$02,$34,
		  $02,$35,$02,$36,$02,$37,
		  $02,$38,$02,$39,$02,$3b,
		  $02,$3c,$02,$3d,$02,$3e,
		  $02,$3f,$02,$40,$02,$41,
		  $02,$42,$02,$43,$02,$44,
		  $02,$45,$02,$46,$02,$47,
		  $02,$48,$02,$49,$02,$4a,
		  $02,$4b,$02,$4c,$02,$4d,
		  $02,$4e,$02,$4f,$02,$51,
		  $02,$52,$02,$53,$02,$54,
		  $02,$55,$02,$56,$02,$57,
		  $02,$58,$02,$59,$02,$5a,
		  $02,$5b,$02,$5c,$02,$5e,
		  $02,$5f,$02,$60,$02,$61,
		  $02,$62,$02,$63,$02,$64,
		  $02,$65,$02,$66,$02,$67,
		  $02,$69,$02,$6a,$02,$6b,
		  $02,$6c,$02,$6d,$02,$6e,
		  $02,$6f,$02,$70,$02,$72,
		  $02,$73,$02,$74,$02,$75,
		  $02,$76,$02,$77,$02,$78,
		  $02,$79,$02,$7b,$02,$7c,
		  $02,$7d,$02,$7e,$02,$7f,
		  $02,$80,$02,$82,$02,$83,
		  $02,$84,$02,$85,$02,$86,
		  $02,$87,$02,$89,$02,$8a,
		  $02,$8b,$02,$8c,$02,$8d,
		  $02,$8e,$02,$90,$02,$91,
		  $02,$92,$02,$93,$02,$94,
		  $02,$96,$02,$97,$02,$98,
		  $02,$99,$02,$9a,$02,$9c,
		  $02,$9d,$02,$9e,$02,$09f,
		  $02,$0a0,$02,$0a2,$02,$0a3,
		  $02,$0a4,$02,$0a5,$02,$0a6,
		  $02,$0a8,$02,$0a9,$02,$0aa,
		  $02,$0ab,$02,$0ad,$02
		  );

(* Offset of modulator for each voice *)
TableModulator:Array[0..13] of byte = (
		  $00,$01,$02,$08,$09,$0a,
		  $10,$11,$12,$01,$11,$4f,
		  $00,$0f1);

(* Offset of operator for percussive mode     *)
(* Offset memory boundary cross in ASM Source *)
(* Error is quite possible                    *)
TablePModulator:Array[0..10] of Byte = (
		   $f2,$53,$74,$00,$00,$08,
		   $10,$14,$12,$15,$11);

(* Offset of Modulator in each voice *)
SlotMVoice: Array[0..8,0..1] of Byte = (

		  ($00, $03),
		  ($01, $04),
		  ($02, $05),
		  ($08, $0B),
		  ($09, $0C),
		  ($0a, $0D),
		  ($10, $13),
		  ($11, $14),
		  ($12, $15));

  { Percussion Rhythm Enable bits }
  PercBits:Array[0..4] of Byte =
{ FIRST TABLE TAKEN FROM SBFMDRV.ASM  }
{               ($08,$10,$14,$12,$15) }
{ 2ND TABLE TAKEN FROM ADLIB.C        }
      { VOICES:   6   7   8   9  10 }
		($10,$08,$04,$02,$01)
			  ; {Percussion Mode Enable Voice bits.}


PercussiveOffset:Array[0..10] of Byte = (
			  $11,$10,$08,$04,$02,$01,
			  $06,$07,$08,$08,$07)
			  ; { Offset voice when in percussive voice. }


{----------------------------------------------------------------- }



Type
 TBit = 0..1;


PAdlib = ^TAdlib;
TAdlib = Object(TObject)
 Public
  { This determines if botyh operators produce sound directly      }
  { i so, then modyfing volume must be done by with both operators }
  { TRUE = Both Modulator and Carrier must be changed to change    }
  { the volume.                                                    }
  Algorithm: Array[0..11] of Boolean;
  Percussive: Boolean;              (* Percussion Mode parameter *)
  Voices: Integer;                  (* maximum number of available voices *)
  PlayingNotes: Array[0..15] of Byte;
  Volume: Array[0..15] of Byte;
  ModulatorScalingLevel:Array[0..15] of Byte;
  CarrierScalingLevel:Array[0..15] of Byte;
  Output : Array[0..15] of Word;
  SemiToneCurrent: Array[0..15] of Integer;
  BdRegister: Byte;   { Current Value in BDRegister }
  Constructor Init;
  Destructor Done; Virtual;
  Procedure CardNotInstalled; Virtual;
  Procedure NoteOff(Voice:Byte; Note:Byte);
  Procedure NoteOn(Voice: Byte; Note: Byte);
  Procedure SetVolume(Voice: Byte; AVolume: Byte);
  Procedure SemiToneup(Voice: Byte; Value: Byte);
  Procedure SemiToneDown(Voice: Byte; Value: Byte);

(* Selects either Melodic or Percussive mode *)
(* TRUE = Percussive Mode                    *)
  Procedure SetPercussive(Mode: Boolean);



 {----------------------------------------------------------------}
 (* *  Writes to Register 08h                                  * *)
 (* *   KEYBOARD SPLIT POINT                                   * *)
 (* *  SndNoteSel -> Toggles the Keyboard Split point          * *)
 (* *                                                          * *)
 {----------------------------------------------------------------}
  Procedure SndNoteSel(NoteSel: Boolean);

 {----------------------------------------------------------------}
 (* *  Writes to Registers 20h-35h                             * *)
 (* *    AMP. MODULATION / VIBRATO / EG TYPE / MODULATION      * *)
 (* *     MULTIPLIER                                           * *)
 (* *    SetWaveChar -> Writes Values directly to Register     * *)
 {----------------------------------------------------------------}
  Procedure SetWaveChar(Voice: Byte; ModCar: TBit; Value: Byte);

 {----------------------------------------------------------------}
 (* *  Writes to Register 40h-55h                              * *)
 (* *    SCALING LEVEL  / OUTPUT LEVEL                         * *)
 (* *  AllScalingOutput -> Writes Byte directly to register    * *)
 {----------------------------------------------------------------}
  Procedure AllScalingOutput(Voice: Byte; ModCar: TBit; Value: Byte);

 {----------------------------------------------------------------}
 (* *  Writes to Registers 60h-75h                             * *)
 (* *    ATTACK RATE / DECAY RATE                              * *)
 (* *  AllAttackDecay -> Writes Value directly to Register     * *)
 {----------------------------------------------------------------}
  Procedure AllAttackDecay(Voice: Byte; ModCar: TBit; Value: Byte);

 {----------------------------------------------------------------}
 (* *  Writes to Registers 80h-95h                             * *)
 (* *     SUSTAIN RATE/ RELEASE RATE                           * *)
 (* *   AllSusRelease -> Outputs Byte directly to Register     * *)
 {----------------------------------------------------------------}
  Procedure AllSusRelease(Voice: Byte; ModCar: TBit; Value: Byte);

 {----------------------------------------------------------------}
 (* *  Writes To Register BDh                                  * *)
 (* *  AMPLITUDE MODULATION DEPTH / VIBRATO DEPTH / RHYTHM     * *)
 (* *                                                          * *)
 (* *  SetAM  -> Toggles the AM Depth On/Off Only              * *)
 (* *  SetVib -> Toggles the Vibrato Depth On/Off Only         * *)
 (* *                                                          * *)
 {----------------------------------------------------------------}
  Procedure SetAM(AM: Boolean);
  Procedure SetVibrato(Vib: Boolean);

 {----------------------------------------------------------------}
 (* *  Writes To Registers C0h-C8h                             * *)
 (* *   AllFeedBack -> Outputs Byte directly to Register       * *)
 {----------------------------------------------------------------}
  Procedure AllFeedback(Voice: Byte; Value: Byte);

 {----------------------------------------------------------------}
 (* * Writes To Port E0h-F5h -> Selects Waveform to use        * *)
 (* *    WAVEFORM SELECT                                       * *)
 (* *   Only value in this register                            * *)
 {----------------------------------------------------------------}
  Procedure SetWaveSelect(Voice:Byte;ModCar: TBit; Value: Byte);
Private
  { Calculates the note to play }
  Function CalcNote(Voice:byte; Note: Byte): Word;
  { Sets the default rhythm instruments }
  Procedure SetRhythmInstruments;
end; { TAdlib Object }



   {-----------------------------------------------------------------}

 (* Check if an Adlib Board is installed *)
Function BoardInstalled: Boolean;
Procedure SndOutput(reg: Integer; Value: Byte);







Carl Eric Codre aka Black One.
cecodere@andrew.sca.usherb.ca
carl.codere@evening.magicnet.com
<Electrical Engineering, University of Sherbrooke>
Implementation

{ Percussive voices instruments }
Type
 TInstrument = Record
  WaveChar: Byte;       { Wave Characteristic         }
  ScalingOutput: Byte;  { Scaling Level / Ouput level }
  AttackDecay: Byte;    { Attack / Decay Rate         }
  SustainRelease: Byte; { Sustain / Release Rate      }
  Feedback: Byte;       { Feedback                    }
  SetWaveSelect: Byte;  { Type of Wave to use         }
 end;

Const
  { ALL MELODIC VOICES ARE INITIALIZED TO THIS }
  ElectricPiano:array[0..1] of TInstrument = (
  (WaveChar: 0; ScalingOutput: 0; AttackDecay: 0; SustainRelease: 0;
   FeedBack: 0; SetWaveSelect: 0),
  (WaveChar: 0; ScalingOutput: 0; AttackDecay: 0; SustainRelease: 0;
   FeedBack: 0; SetWaveSelect: 0));

  { PERCUSSIVE MODE VOICES }

  BassDrum:array[0..1] of TInstrument = (
  (WaveChar: 0; ScalingOutput: 11; AttackDecay: 168; SustainRelease: 76;
   FeedBack: 0; SetWaveSelect: 0),
  (WaveChar: 0; ScalingOutput: 0; AttackDecay: 214; SustainRelease: 79;
   FeedBack: 0; SetWaveSelect: 0));

   SnareDrum: TInstrument =
  (WaveChar:12; ScalingOutput: 0; AttackDecay: 248; SustainRelease: 181;
   FeedBack: 1; SetWaveSelect: 0);

   TomTom: TInstrument =
  (WaveChar: 4; ScalingOutput: 0; AttackDecay: 247; SustainRelease: 181;
   FeedBack: 1; SetWaveSelect: 0);

   Cymbal: TInstrument =
  (WaveChar: 4; ScalingOutput: 0; AttackDecay: 247; SustainRelease: 181;
   FeedBack: 1; SetWaveSelect: 0);

   HiHat: TInstrument =
  (WaveChar: 4; ScalingOutput: 0; AttackDecay: 247; SustainRelease: 181;
   FeedBack: 1; SetWaveSelect: 0);


   {-----------------------------------------------------------------}

Function BoardInstalled:Boolean;
(* Returns TRUE if an adlib card was detected *)
(* otherwise returns false.                   *)
Var
 Stat1, Stat2: Integer;
Begin
 SndOutput($04,$60);      (* Reset both timers       *)
 SndOutput($04,$80);      (* Enable Timer interrupts *)
 Stat1:=Port[$388];       (* Read Status Register    *)
 SndOutput($02,$FF);
 SndOutput($04,$21);      (* Start Timer 21h         *)
 Delay(100);              (* Wait 80 ms              *)
 Stat2:=Port[$388];       (* Read Status Register    *)
 SndOutput($04,$60);      (* Reset both timers       *)
 SndOutput($04,$80);      (* Enable timer interrupts *)
 Stat1:=stat1 and $E0;
 Stat2:=Stat2 and $E0;
 If (stat1 = $00) and (stat2 = $C0) then
   BoardInstalled:=True;
end;

Procedure SndOutput(reg: Integer; Value: Byte);
 (****************************************************************)
 (* * PROCEDURE SndOutPut(reg: Integer; Value: Byte);          * *)
 (* *  Writes to a specified Adlib register.                   * *)
 (* *  reg -> register number to write to                      * *)
 (* *  value -> value to write to register                     * *)
 (****************************************************************)
Var
 i:Integer;
 Tmp:Integer;
Begin
 Port[$388]:=Reg;
 ASM
    MOV DX, 388h
    MOV CX, 35
 @DelayLoop:
    IN AL, DX
    LOOP @DelayLoop
 end;
 Port[$389]:=Value;
end;


   {-----------------------------------------------------------------}



Constructor TAdlib.Init;
Var i:Integer;
Begin
   Inherited Init;
   (* If an Adlib compatible sound card is NOT installed *)
   If Not BoardInstalled then
     CardNotInstalled;
   (* Reset the Sound Card *)
   FOR i := $01 TO $F5 do
      SndOutput(i, 0);

   SndOutput($01, $02);
   SndOutput($08, $00);
   SetPercussive(False);
END;



Procedure TAdlib.CardNotInstalled;
Begin
 WriteLn('ADLIB CARD NOT FOUND, HALTING PROGRAM');
 Halt(1);
end;





{ Start a note playing.                         }
{-----------------------------------------------------------
        Routine to start a note playing.

        0 <= voice <= 8 in melodic mode,
        0 <= voice <= 10 in percussive mode;
        0 <= pitch <= 127, 60 == MID_C ( the card can play between 12 and 107 )
-----------------------------------------------------------
}

Procedure TAdlib.NoteOn(Voice: Byte; Note: Byte);
Begin
 If ((Percussive) and (Voice < BassDrumVoice)) OR (NOT Percussive) then
 Begin
   Output[Voice] := CalcNote(Voice, Note);
   SndOutput($A0 + Voice, Lo(Output[Voice]));
   { Enable key on by adding bitwise bit 5 }
   SndOutput($B0 + Voice, hi(Output[Voice]) OR $20);
 end
 else
 Begin
   BDRegister := BDRegister OR PercBits[Voice-6];
   Output[Voice] := CalcNote(Voice, Note);

   SndOutput($A0 + PercussiveOffset[Voice], Lo(Output[Voice]));
   SndOutput($B0 + PercussiveOffset[Voice], Hi(Output[Voice]));

   { Enable Key on in Rhythm Node }
   SndOutput($BD, BDRegister);
 end;
END;



Procedure TAdlib.NoteOff(Voice:Byte; Note:Byte);
Begin
 If ((Percussive) and (Voice < BassDrumVoice)) OR (NOT Percussive) then
 Begin
{   Output[Voice] := CalcNote(Voice, Note);}
   SndOutput($A0 + Voice, Lo(Output[Voice]));
   SndOutput($B0 + Voice, Hi(Output[Voice]));
 end
 else
 Begin
{   Output[voice] := CalcNote(Voice, Note);}
   BDRegister := BDRegister AND (NOT Percbits[Voice - 6]);

   { Disable thee key-on for that percussive voice }
   SndOutPut($BD, BDRegister);
 end;
end;




{ Put the chip in melodic mode (mode == 0), or in percussive mode (mode <> 0).}
{ If the melodic mode is chosen, all voices are set to electric-piano, else   }
{ the first 5 are set to electric-piano, and the percussion voices to their   }
{ default timbres.                                                            }
Procedure TAdlib.SetPercussive(Mode: Boolean);
BEGIN
 If NOT Mode then
 Begin
   Voices := $09;
   BdRegister := $00C0;
   SndOutput($Bd, BDRegister);
   Percussive := FALSE;
 end
 else
 Begin
   Voices := $06;
   BDRegister := $E0;
   SndOutput($Bd, BDRegister);
   Percussive := TRUE;
   SetRhythmInstruments;
 end;
END;


Procedure TAdlib.SetRhythmInstruments;
Begin
 { BASS-DRUM Voice has two operators }
 SndOutput(TablePModulator[BassDrumVoice] + $20, BassDrum[0].WaveChar);
 SndOutput(TablePModulator[BassDrumVoice] + $40, BassDrum[0].ScalingOutput);
 SndOutput(TablePModulator[BassDrumVoice] + $60, BassDrum[0].AttackDecay);
 SndOutput(TablePModulator[BassDrumVoice] + $80, BassDrum[0].SustainRelease);
 SndOutput( BassDrumVoice                 + $C0, BassDrum[0].Feedback);
 SndOutput(TablePModulator[BassDrumVoice] + $E0, BassDrum[0].SetWaveSelect);

 SndOutput(TablePModulator[BassDrumVoice] + $23, BassDrum[1].WaveChar);
 SndOutput(TablePModulator[BassDrumVoice] + $43, BassDrum[1].ScalingOutput);
 SndOutput(TablePModulator[BassDrumVoice] + $63, BassDrum[1].AttackDecay);
 SndOutput(TablePModulator[BassDrumVoice] + $83, BassDrum[1].SustainRelease);
 SndOutput(TablePModulator[BassDrumVoice] + $E3, BassDrum[1].SetWaveSelect);

 { The other percussive voices only have one }
 SndOutput(TablePModulator[SnareDrumVoice] + $20, SnareDrum.WaveChar);
 SndOutput(TablePModulator[SnareDrumVoice] + $40, SnareDrum.ScalingOutput);
 SndOutput(TablePModulator[SnareDrumVoice] + $60, SnareDrum.AttackDecay);
 SndOutput(TablePModulator[SnareDrumVoice] + $80, SnareDrum.SustainRelease);
 SndOutput(SnareDrumVoice + $C0, SnareDrum.Feedback);
 SndOutput(TablePModulator[SnareDrumVoice] + $E0, SnareDrum.SetWaveSelect);

 SndOutput(TablePModulator[TomTomVoice] + $20, TomTom.WaveChar);
 SndOutput(TablePModulator[TomTomVoice] + $40, TomTom.ScalingOutput);
 SndOutput(TablePModulator[TomTomVoice] + $60, TomTom.AttackDecay);
 SndOutput(TablePModulator[TomTomVoice] + $80, TomTom.SustainRelease);
 SndOutput(TomTomVoice + $C0, TomTom.Feedback);
 SndOutput(TablePModulator[TomTomVoice] + $E0, TomTom.SetWaveSelect);

 SndOutput(TablePModulator[CymbalVoice] + $20, Cymbal.WaveChar);
 SndOutput(TablePModulator[CymbalVoice] + $40, Cymbal.ScalingOutput);
 SndOutput(TablePModulator[CymbalVoice] + $60, Cymbal.AttackDecay);
 SndOutput(TablePModulator[CymbalVoice] + $80, Cymbal.SustainRelease);
 SndOutput(CymbalVoice + $C0, Cymbal.Feedback);
 SndOutput(TablePModulator[CymbalVoice] + $E0, Cymbal.SetWaveSelect);

 SndOutput(TablePModulator[HihatVoice] + $20, Hihat.WaveChar);
 SndOutput(TablePModulator[HihatVoice] + $40, Hihat.ScalingOutput);
 SndOutput(TablePModulator[HihatVoice] + $60, Hihat.AttackDecay);
 SndOutput(TablePModulator[HihatVoice] + $80, Hihat.SustainRelease);
 SndOutput(HihatVoice + $C0, Hihat.Feedback);
 SndOutput(TablePModulator[HihatVoice] + $E0, Hihat.SetWaveSelect);
end;






Carl Eric Codre aka Black One.
cecodere@andrew.sca.usherb.ca
carl.codere@evening.magicnet.com
<Electrical Engineering, University of Sherbrooke>
Function Tadlib.CalcNote(Voice:byte; Note: Byte): Word; Assembler;
(* Calculate the note to output to A0 and B0 registers *)
(* Taken directly from the SBlaster FM Driver          *)
(* Possible value is from 0 to 127                     *)
{ 0 = Silence, 127 = Highest volume                     }
ASM
                Xor     Ax, Ax
                Xor     Bx, Bx
                LES     SI, [Self]

                Mov     Bl, [Voice]
                Mov     Al,Byte Ptr [Note]
                Mov     Byte Ptr ES:[SI].PlayingNotes[BX],Al
                Cbw
      {         Mov     Di,[SmiToneModify]     }
      {  Used to manually modify to semi-tones }
      {  replaced by this:                     }
                Xor     Di, Di  { }
                Add     Di,Ax
                Jns     @b06c12
                Sub     Di,Di
@b06c12:        And     Di,7fh
                Mov     Al,Byte Ptr NoteTab[Di]
                Mov     Dl,Al
                And     Dl,70h
                Shr     Dl,1
                Shr     Dl,1
                And     Al,0fh
                Cbw
                Xchg    Al,Ah
                Shr     Ax,1
                Shr     Ax,1
                Shr     Ax,1
                Shl     Bx,1
                Add     Ax,Word ptr ES:[SI].SemiToneCurrent[Bx]
                Jns     @b06c43
                Add     Ax,0180h
                Sub     Dl,04
                Jns     @b06c58
                Sub     Dl,Dl
                Sub     Ax,Ax
                Jmp     @b06c58
@b06c43:        Cmp     Ax,0180h
                Jb      @b06c58
                Sub     Ax,0180h
                Add     Dl,04
                Cmp     Dl,1ch
                Jnb     @b06c58
                Mov     Ax,017fh
                Mov     Dl,1ch
@b06c58:        Shl     Ax,1
                Mov     Di,Ax
                Mov     Ax,Word Ptr FreqTable[Di]
                Or      Ah,Dl
end;


Procedure TAdlib.SetVolume(Voice: Byte; AVolume: Byte);
(* AVolume is a value between 0-127      *)
(* It changes the volume but incorrectly *)
{ INFORMATION: From Adlib docs, if the algorithm does     support }
{ additive synthesis (i.e Alg = 0), the volume is modified by the }
{ carrier output level only.                                      }
{ On the other hand, if the Alg = 1, each operator produces a     }
{ sound directly, in that case, BOTH operators must be used to    }
{ change the output level.                                        }
Var
 ValueOne: Byte;  { Modulator modification }
 ValueTwo: Byte;  { Carrier Modification   }
Begin
 ASM
        Les     Si, [Self]
        Xor     Ax, Ax
        Xor     Bx, Bx
        Xor     Cx, Cx
        Mov     Bl, [Voice]
        Mov     Cl, [Avolume]
        Or      Cl,80h
        Mov     Al,Byte ptr ES:[SI].Volume[Bx]
        Mul     Cl
        Mov     Al,3fh
        Sub     Al,Ah

        ; { Change the volume level.}

        Mov     AH,Byte Ptr Es:[Si].CarrierScalingLevel[Bx]
        Or      AL, AH
        mov     [ValueOne], Al
 { Ceci a ete ajoute apres }
        Mov     AH,Byte Ptr Es:[Si].ModulatorScalingLevel[Bx]
        Or      AL, AH
        mov     [ValueTwo], AL


end;
  { If Melodic mode, or if the voice number is a melodic voice }
  { then output volume level normally.                         }
  If (Not Percussive) OR ((Voice < BassDrumVoice) AND Percussive) then
  Begin
     SndOutput(TableModulator[Voice] + $43, ValueOne);
     { If the sound is produced directly by both operators }
     If Algorithm[Voice] then
        SndOutput(TableModulator[Voice] + $40, ValueTwo);
  end
  else
  Begin
   { This Voice has two operators in Percussive mode, write to }
   { normal operator.                                          }
   If Voice = BassDrumVoice then
   Begin
     SndOutput(TablePModulator[Voice] + $43, ValueOne);
     If Algorithm[Voice] then
        SndOutput(TableModulator[Voice] + $40, ValueTwo);
   end
   else
     { These percussive voices only have one operator each, write }
     { the volume level directly.                                 }
     SndOutput(TablePModulator[Voice] + $40, ValueOne);
  end;
end;


Procedure TAdlib.SemiToneup(Voice: Byte; Value: Byte); Assembler;
{ Number of Semi-tones to increase the notes by }
ASM
      Les     Di, [Self]
      Mov     Ah, [Value]
      Mov     Al,Ah
      Cbw
      Sar     Ax,1
      Sar     Ax,1
      Xor     Bh, Bh
      Mov     Bl,[Voice]
      Shl     Bx,1
      Mov     Word Ptr ES:[DI].SemiToneCurrent[Bx],Ax
end;

Procedure TAdlib.SemiToneDown(Voice: Byte; Value: Byte); Assembler;
{ Number of Semi-tones to decrease the notes by }
ASM
      Les     Di, [Self]
      Mov     Ah, [Value]
      Neg     Ah
      Mov     Al,Ah
      Cbw
      Sar     Ax,1
      Sar     Ax,1
      Xor     Bh, Bh
      Mov     Bl,[Voice]
      Shl     Bx,1
      Mov     Word Ptr ES:[DI].SemiToneCurrent[Bx],Ax
end;







 (****************************************************************)
(* $BD REGISTER FUNCTIONS *)

{
'Write to the register at &HBD which controls AM Depth, VIB depth, rhythm
'(melo/perc mode) and note on/off for the percussive voices.
'}
(* SETS BIT 7 - AM depth *)
Procedure TAdlib.SetAM(AM: Boolean);
Begin
 If AM then
   BDRegister := BDRegister OR $80
 else
   BDRegister := BDRegister AND $7F;
 SndOutPut($BD, BdRegister);
end;

Procedure TAdlib.SetVibrato(Vib: Boolean);
Begin
 If Vib then
 (* Enable bit 6 *)
  BDRegister := BDRegister OR $40
 else
  BDRegister := BDRegister AND $Bf;
(* Clear Cport.Depth bit 6 *)
 SndOutPut($BD, BDRegister);
end;




 (****************************************************************)
(* REGISTERS $60-$75 ->  Attack Rate/ Decay Rate Change                         *)


Procedure TAdlib.AllAttackDecay(Voice: Byte; ModCar: TBit; Value: Byte);
Begin
 SndOutput($60+SlotMVoice[Voice][ModCar], Value);
end;
 (****************************************************************)


 (****************************************************************)
(* AM / Vibrato / EG (Sustain) / Keyboard Scaling Rate (KSR) / Mod. multiplicator *)
Procedure TAdlib.SetWaveChar(Voice: Byte; ModCar: TBit; Value: Byte);
Begin
 Case ModCar of
  Modulator: Begin
          SndOutPut($20+SlotMVoice[Voice][ModCar], Value);
        end;
  Carrier:Begin
         SndOutput($20+SlotMVoice[Voice][ModCar], Value);
        end;
  end;
end;





Procedure TAdlib.AllScalingOutput(Voice: Byte; ModCar: TBit; Value: Byte);
Begin
 SndOutput($40+ SlotMVoice[Voice][ModCar], Value);
end;


{'Write to the register at &H8.}
Procedure TAdlib.SndNoteSel(NoteSel: Boolean);
Begin
   IF noteSel THEN
      SndOutput($08, 64)
   ELSE
      SndOutput($08, 0);
end;


 (****************************************************************)
(*  BYTES $C0-$C8 = Feedback/ Algorithm                                         *)

{'Write to the register at &HC0 which controls FEED-BACK and FM (connection).
'Applicable only to operator 0.
'}

Procedure TAdlib.AllFeedback(Voice: Byte; Value: Byte);
Begin
 SndOutput($C0 + Voice, Value);
end;
 (****************************************************************)

 (****************************************************************)
(* BYTES $80-95 SUSTAIN/ RELEASE RATES                                          *)

{'Write to the register at &H80 which controls the sustain level and
'release rate.
'}

Procedure TAdlib.AllSusRelease(Voice: Byte; ModCar: TBit; Value: Byte);
Begin
 SndOutput($80+SlotMVoice[Voice][ModCar], Value);
end;
 (****************************************************************)


 (****************************************************************)
{'Write to the register at &HE0 which controls the type of wave-form which
'will be used for this slot.
'}

Procedure TAdlib.SetWaveSelect(Voice:Byte;ModCar: TBit; Value: Byte);
Begin
 Case ModCar of
  Modulator: Begin
      SndOutput($E0+SlotMVoice[Voice][ModCar], Value);
       end;
  Carrier: Begin
      SndOutput($E0+SlotMVoice[Voice][ModCar], Value);
       end;
  end;
END;




Destructor TAdlib.Done;
Var
 i: Word;
Begin
   (* Reset\Silence the Sound Card *)
   { This should be replaced by a gradual scale down }
   FOR i := $01 TO $F5 do
      SndOutput(i, 0);
end;




end.

Carl Eric Codre aka Black One.
cecodere@andrew.sca.usherb.ca
carl.codere@evening.magicnet.com
<Electrical Engineering, University of Sherbrooke>
{************************************************}
{                                                }
{   Simple CMF Player.                           }
{   Freeware  1995 by Carl Eric Codre.          }
{   Montreal, Canada - September 1995.           }
{************************************************}
{ Contact me at: carl.codere@evening.magicnet.com       }
{  From 1995-1998 you can also contact me at:           }
{      cecodere@andrew.sca.usherb.ca  (preferred method)}

(* Conditions for use of this source code:                                *)
(*      - No re-release of this or the modified source code is done.      *)
(*      - Proper credit is given to the author of these routines.         *)
(*      - That I be notified of any bugfixes / major upgrades.            *)

{ BUG: Do not use the semi-tones in melodic mode, they do not seem to work }
{ and they modify the music drastically.                                   }

{ All information on the CMF file format was taken from the PCGPE magazine }
{ which was created by Mark Feldman. The PIT Interrupt was also taken from }
{ him. Thanks for that great encyclopedia!                                 }
Program CMF;
{$S-}
{$F+}

Uses Sound,Crt,Dos, Objects;
{ BUGFIX -> Scaling level was not saved correctly }


Type
 THeader = Record
  ID: Array[0..3] of Char;    (* Should Contain 'CTMF' *)
  Version: Word;              (* LSB = Minor Version, MSB = Major Version *)
  InstrumentOffset: Word;     (* Offset of file into Instrument block     *)
  MusicOffset: Word;          (* Offset of file into music block          *)
  TicksPerQuartet: Word;      (* Clock ticks per quarter note             *)
(* Calculation :=(TicksPerQuartet*Tempo) div 60 sec.                      *)
  TicksPerSecond: Word;       (* Clock Ticks per second                   *)
  TitleOffset: Word;          (* Offset of file to title                  *)
  AuthorOffset: Word;         (* Offset of file to author name            *)
  NotaOffset: Word;           (* Offset of file to notations              *)
  (* For each voice, tells if voice is used or not:                       *)
  (* 0 = Unused voice                                                     *)
  (* 1 = Used voice                                                       *)
  Channels:Array[1..16] of Byte;  (* For all 16 midi CHANNELS Offset 20-35*)
  Instruments: Word;          (* Number of instruments used-1        36-37*)
  Tempo: Word;                                          (* Offset    38-39*)
 end;


 (* A CMF File Instrument Record *)
 TCMFInstrument = Record
  ModulatorChar: Byte;      (* Modulator Characteristic *)
  CarrierChar: Byte;        (* Carrier Characteristic   *)
  ModScalingOutput: Byte;
  CarScalingOutput: Byte;
  ModAttackDelay: Byte;
  CarAttackDelay: Byte;
  ModSustainRelease: Byte;
  CarSustainRelease: Byte;
  ModWaveSelect: Byte;
  CarWaveSelect: Byte;
  FeedBackConnection: Byte;
  Reserved: Array[1..5] of Byte;
 end;

  { Maximum Size of a CMF File possible }
  TSong = Array[1..40000] of Byte;


Var
  Playing: Boolean;
 Var
  Header: THeader;
  Instr: Array[0..255] of TCMFInstrument;
  Song: TSong;
  CurrentPos: Word;
  Adlib :TAdlib;



(*************************************************************************)
(* Create a stream error procedure which will be called on error of the  *)
(* stream. Will Terminate executing program, as well as display info     *)
(* on the type of error encountered.                                     *)
(*************************************************************************)
Procedure StreamErrorProcedure(Var S: TStream); FAR;
Begin
 If S.Status = StError then
 Begin
  WriteLn('ERROR: General Access failure. Halting');
  Halt(1);
 end;
 If S.Status = StInitError then
 Begin
  WriteLn('ERROR: Cannot Init Stream. Halting');
  { SPECIFIC TO DOS STREAMS }
  Case S.ErrorInfo of
  2: WriteLn('File not found.');
  3: WriteLn('Path not found.');
  5: Writeln('Access denied.');
  end;
  Halt(1);
 end;
 If S.Status = StReadError then
 Begin
  WriteLn('ERROR: Read beyond end of Stream. Halting');
  Halt(1);
 end;
 If S.Status = StWriteError then
 Begin
  WriteLn('ERROR: Cannot expand Stream. Halting');
  Halt(1);
 end;
 If S.Status = StGetError then
 Begin
  WriteLn('ERROR: Get of Unregistered type. Halting');
  Halt(1);
 end;
 If S.Status = StPutError then
 Begin
  WriteLn('ERROR: Put of Unregistered type. Halting');
  Halt(1);
 end;
end;


Procedure SetVoice(Voice:Byte; Instr: TCMFInstrument);
Begin
 If (Voice < 9) AND (NOT Adlib.Percussive)  OR (Adlib.Percussive)
 { In Melodic mode call this }
 { In percussive mode, calls this if Bass drum modes }
 { or if a melodic voice is used.                    }
  AND (Voice < 7) then
 Begin
  Adlib.SetWaveChar(Voice, Modulator, Instr.ModulatorChar);
  Adlib.SetWaveChar(Voice, Carrier, Instr.CarrierChar);
  Adlib.AllScalingOutput(Voice, Modulator, Instr.ModScalingOutput);
  Adlib.AllScalingOutput(Voice, Carrier, Instr.CarScalingOutput);
  { SAve the scaling output for the second operator }
  { this is the type which controls the volume of   }
  { the voice.                                      }
  Adlib.CarrierScalingLevel[Voice] := Instr.CarScalingOutput AND $C0;
  Adlib.ModulatorScalingLevel[Voice] := Instr.ModScalingOutput AND $C0;
  Adlib.Algorithm[Voice] := Boolean(Instr.FeedBackConnection AND $01);
  Adlib.Volume[Voice] := Instr.CarScalingOutput AND $3f;
  Adlib.Volume[Voice] := Byte(Adlib.Volume[Voice] - $3F);
  Adlib.Volume[Voice] := Byte(0 - Adlib.Volume[Voice]);
  Adlib.AllAttackDecay(Voice, Modulator, Instr.ModAttackDelay);
  Adlib.AllAttackDecay(Voice, Carrier, Instr.CarAttackDelay);
  Adlib.AllFeedBack(Voice, Instr.FeedBackConnection);
  Adlib.AllSusRelease(Voice, Modulator, Instr.ModSustainRelease);
  Adlib.AllSusRelease(Voice, Carrier,   Instr.CarSustainRelease);
  Adlib.SetWaveSelect(Voice, Modulator, Instr.ModWaveSelect);
  Adlib.SetWaveSelect(Voice, Carrier, Instr.CarWaveSelect);
 end
 else
  { This is a normal percussive voice }
 Begin
  SndOutput(TablePModulator[Voice] + $20, Instr.ModulatorChar);
  SndOutput(TablePModulator[Voice] + $40, Instr.ModScalingOutput);
  SndOutput(TablePModulator[Voice] + $60, Instr.ModAttackDelay);
  SndOutput(TablePModulator[Voice] + $80, Instr.ModSustainRelease);
  SndOutput(Voice + $C0, Instr.FeedbackConnection);
  SndOutput(TablePModulator[Voice] + $E0, Instr.ModWaveSelect);
  Adlib.CarrierScalingLevel[Voice] := Instr.ModScalingOutput AND $C0;
  Adlib.Volume[Voice] := Instr.ModScalingOutput AND $3f;
  Adlib.Volume[Voice] := Byte(Adlib.Volume[Voice] - $3F);
  Adlib.Volume[Voice] := Byte(0 - Adlib.Volume[Voice]);
 end;
end;




Var
 CopiedValue: Byte;
 Delay: Longint;


Procedure CalculateDelay(Var Index: Byte); Near;
{ Ideas for this procedure                           }
{  Taken from 'Le Grand Livre de la Sound Blaster'   }
{  ISBN: 2-86899-758-9                               }
Var
 LocalIndex: Byte;
 i: Byte;
 byteValues: Array[0..3] of Byte;
 Power: Real;
 ActData: Byte;
Begin
    For i:=0 to 3 do ByteValues[i] := 0;
    LocalIndex := 0;
    ActData := 128;
    WHILE (ActData AND $80) = $80 DO
    BEGIN
     ActData := Song[CurrentPos+Index+1];
     ByteValues[LocalIndex] := ActData;
     Inc(Index);
     Inc(LocalIndex);
    END;


    FOR i:= (LocalIndex-1) downto 0 do
    Begin
     Power := Exp(i * Ln(128));
    Delay := Delay + (Trunc(Power)*(ByteValues[(LocalIndex-1)-i] AND $7F));
    end;
end;

(*************************************************************************)
(*  THIS IS THE MAIN LOOP IN THE PROCESSING OF THE MIDI EVENTS           *)
(*************************************************************************)
Procedure ProcessCMFEvent;
Var
 Volume: Byte;
 Index: Byte;
 Note: byte;
 Voice: Byte;
Begin
  If Delay > 0 then
  Begin
    Dec(Delay);
    Exit;
 end;
 If not Playing then
 Begin
  CurrentPos:=1;
  Playing:=True;
 end;


 Volume :=0;
 Note   :=0;
 Voice  :=0;
 Case Song[CurrentPos] of
(* Note Number Off *)
 $80,$81,$82,$83,$84,$85,$86,$87,$88,$89,$8A,$8B,$8C,$8D,$8E,$8F:
    Begin
       CopiedValue:=Song[CurrentPos];
       Voice := Song[CurrentPos] AND $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
       If Voice > 9 then Voice := Voice - 5;
       Note := Song[CurrentPos+1];
       Volume:=Song[CurrentPos+2];

       Index:=2;

       Adlib.SetVolume(Voice, Volume);
       Adlib.NoteOff(Voice, Note);

       CalculateDelay(Index);
       Inc(CurrentPos, Index+1);
     end;

(* Note Number on *)
 $90,$91,$92,$93,$94,$95,$96,$97,$98,$99,$9A,$9B,$9C,$9D,$9E,$9F:
    Begin
       Index:=2;
       CopiedValue:=Song[CurrentPos];
       Voice  := Song[CurrentPos] and $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
       If Voice > 9 then Voice := Voice - 5;
       Note   := Song[CurrentPos+1];
       Volume := Song[CurrentPos+2];

{       Repeat
        Inc(i);
        Delay:=Delay+Song[CurrentPos+i];
       Until Song[CurrentPos+i] <= 127; }

       Adlib.SetVolume(Voice, Volume);
       If Volume = 0 then
          Adlib.NoteOff(Voice, note)
       else
          Adlib.NoteOn(Voice, Note);

         CalculateDelay(Index);
       Inc(CurrentPos, Index+1);
     end;
(* END OF SONG *)
 (* Check if end of song *)
 $FF: Begin
     If (Song[CurrentPos+1] = $2f) and (Song[CurrentPos+2] = $00) then
     Begin
       Volume:=Song[CurrentPos+2];
       (* Value in song is from 0 to 127 convert to 0 to 63 *)
       Playing:=False;
       WriteLn('La fin de la musique! On recommence!');
      end;
      end;
(* CONTROLLER CHANGE *)
 $B0,$B1,$B2,$B3,$B4,$B5,$B6,$B7,$B8,$B9,$BA,$BB,$BC,$BD,$BE,$BF:
    Begin
      CopiedValue := Song[CurrentPos];
      Case Song[CurrentPos+1] of
      $66: (* Music Marker *)
           Begin
             Index:=1;
             CalculateDelay(Index);
             Inc(CurrentPos, Index+2);
           end;
      $67: (* Specify which mode *)
           Begin
            Index:=1;
            Adlib.SetPercussive(Boolean(Song[CurrentPos+2]));
            CalculateDelay(Index);
            Inc(CurrentPos, Index+2);
           end;
      $68: (* All note upward, unfinished *)
           Begin
            Index:=2;
            Voice := Song[CurrentPos] AND $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
            If Voice > 9 then Voice := Voice - 5;
{*********** DEBUG **********}
       If Adlib.Percussive then Adlib.SemiToneUp(Voice, Song[CurrentPos+2]);
{*********** ***** **********}
            CalculateDelay(Index);
            Inc(CurrentPos, Index+1);
           end;
      $69: (* All notes downward, unfinished *)
           Begin
            Voice := Song[CurrentPos] AND $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
            If Voice > 9 then Voice := Voice - 5;
            Index:=2;
            CalculateDelay(Index);
{*********** DEBUG **********}
            If Adlib.Percussive then Adlib.SemiToneDown(Voice, Song[Currentpos+2]);
{*********** ***** **********}
            Inc(CurrentPos, Index+1);
           end;
      end;
    end;
(* Change to musical instrument nn *)
 $C0,$C1,$C2,$c3,$c4,$c5,$c6,$c7,$c8,$C9,$CA,$CB,$CC,$CD,$CE,$CF:
  Begin
     Voice := Song[CurrentPos] AND $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
     If Voice > 9 then Voice := Voice - 5;
     CopiedValue := Song[CurrentPos];
     SetVoice(Voice, Instr[Song[CurrentPos+1]]);
     Index:=1;
     CalculateDelay(Index);
     Inc(CurrentPos, Index+1);
  end;
 $00: Inc(CurrentPos);
 else
(*************************************************************************)
(*                      A REPEAT MIDI EVENT                              *)
(*************************************************************************)
 Begin
 Case CopiedValue of
(* Note Number Off *)
 $80,$81,$82,$83,$84,$85,$86,$87,$88,$89,$8A,$8B,$8C,$8D,$8E,$8F:
    Begin
       Voice := CopiedValue AND $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
       If Voice > 9 then Voice := Voice - 5;
       Note := Song[CurrentPos+1];
       Volume:=Song[CurrentPos+2];
       Index:=1;


       Adlib.SetVolume(Voice, Volume);
       Adlib.NoteOff(Voice, Note);

       CalculateDelay(Index);
       Inc(CurrentPos, Index+1);
     end;

(* Note Number on *)
 (* Open Voice 1,2,3,4,5,6,7,8 or 9 *)
 $90,$91,$92,$93,$94,$95,$96,$97,$98,$99,$9A,$9B,$9C,$9D,$9E,$9F:
    Begin
       Index:=1;
       Voice :=CopiedValue and $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
       If Voice > 9 then Voice := Voice - 5;
       Volume:=Song[CurrentPos+1];
       Note := Song[CurrentPos];

      Adlib.SetVolume(Voice, Volume);
      If Volume = 0 then
        Adlib.NoteOff(Voice, Note)
      else
        Adlib.NoteOn(Voice, Note);
        CalculateDelay(Index);
       Inc(CurrentPos, Index+1);
     end;
(* CONTROLLER CHANGE *)
 $B0,$B1,$B2,$B3,$B4,$B5,$B6,$B7,$B8,$B9,$BA,$BB,$BC,$BD,$BE,$BF:
    Begin
      Case Song[CurrentPos+1] of
      $66: (* Music Marker *)
           Begin
            Index:=0;
            Calculatedelay(Index);
            Inc(CurrentPos, Index+2);
           end;
      $67: (* Specify which mode *)
           Begin
            Index:=0;
            Adlib.SetPercussive(Boolean(Song[CurrentPos+1]));
            CalculateDelay(Index);
            Inc(CurrentPos, Index+2);
           end;
      $68:
           Begin
            Voice := CopiedValue AND $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
            If Voice > 9 then Voice := Voice -5;
            Index:=1;
{*********** DEBUG **********}
       If Adlib.Percussive then Adlib.SemiToneUp(Voice, Song[CurrentPos+2]);
           CalculateDelay(Index);
           Inc(CurrentPos, Index+1);
           end;
      $69:
           Begin
            Index:=1;
            Voice := CopiedValue AND $0F;
            If Voice > 9 then Voice := Voice -5;
{*********** DEBUG **********}
            If Adlib.Percussive then Adlib.SemiToneDown(Voice, Song[Currentpos+2]);
{*********** ***** **********}
            CalculateDelay(Index);
           Inc(CurrentPos, Index+1);
           end;
      end;
    end;
(* Change to musical instrument nn *)
 $C0,$C1,$C2,$c3,$c4,$c5,$c6,$c7,$c8,$C9,$CA,$CB,$CC,$CD,$CE,$CF:
  Begin
     Voice := CopiedValue AND $0F;
       { CMF channels 12 to 15 are the equivalent of voices }
       { 7,8,9,10                                           }
     If Voice > 9 then Voice := Voice - 5;
     SetVoice(Voice, Instr[Song[CurrentPos+1]]);
      Index:=1;
      CalculateDelay(Index);
      Inc(CurrentPos, Index+1);
  end;
 else
  WriteLn('MIDI FORMAT ERROR At Position:',CurrentPos);
 end;
 end;
end;
end;




const TIMERINTR = 8;
       PIT_FREQ = $1234DD;

var BIOSTimerHandler : procedure;
    clock_ticks, counter : longint;


procedure SetTimer(TimerHandler : pointer; frequency : word);
begin

  { Do some initialization }
  clock_ticks := 0;
  counter := $1234DD div frequency;

  { Store the current BIOS handler and set up our own }
  GetIntVec(TIMERINTR, @BIOSTimerHandler);
  SetIntVec(TIMERINTR, TimerHandler);

  { Set the PIT channel 0 frequency }
  Port[$43] := $34;
  Port[$40] := Byte(counter mod 256);
  Port[$40] := Byte(counter div 256);
end;

procedure CleanUpTimer;
begin
  { Restore the normal clock frequency }
  Port[$43] := $36;
  Port[$40] := 0;
  Port[$40] := 0;
  { Restore the normal ticker handler }
  SetIntVec(TIMERINTR, @BIOSTimerHandler);
end;

procedure Handler; Interrupt;
begin

  { DO WHATEVER WE WANT TO DO IN HERE }
   ProcessCMFEvent;
  { Adjust the count of clock ticks }
  clock_ticks := clock_ticks + counter;

  { Is it time for the BIOS handler to do it's thang? }
  if clock_ticks >= $10000 then
    begin

      { Yep! So adjust the count and call the BIOS handler }
      clock_ticks := clock_ticks - $10000;
      asm pushf end;
      BIOSTimerHandler;
    end

  { If not then just acknowledge the interrupt }
  else
    Port[$20] := $20;
end;


Var
 Stream: TBufStream;
 i: Byte;
 InLoop: Longint;
Begin
  StreamError:= @StreamErrorProcedure;
  Delay:=0;
  CurrentPos:=1;
  i:=0;
  If Paramcount = 0 then
  Begin
   Write('NO COMMAND LINE');
   Halt;
  end;
  Stream.Init(ParamStr(1),StopenRead, 1024);
  Stream.Read(Header, SizeOf(Header));
  Stream.Seek(Header.InstrumentOffset);
  Repeat
   Stream.Read(Instr[i], SizeOf(TCMFInstrument));
   inc(i);
  Until i = Header.Instruments;
  Stream.Seek(Header.MusicOffset);
 Adlib.init;
 Stream.Read(Song, Stream.GetSize - Stream.GetPos);
 Stream.Done;
 SetTimer(Addr(Handler), Header.TicksperSecond + (20*Header.TicksPerSecond div 100));
 Repeat
{   Inc(Inloop);
   ProcessCMFEvent;
   CRT.Delay(6);}
 Until Keypressed;
   WriteLn('Current Song Position:',CurrentPos);
   WriteLn('LOOP #:',InLoop);
   WriteLn('Delay:',Delay);
 Adlib.Done;
 CleanUpTimer;
end.



Carl Eric Codre aka Black One.
cecodere@andrew.sca.usherb.ca
carl.codere@evening.magicnet.com
<Electrical Engineering, University of Sherbrooke>
