
{                 ú ú ù ùùÄÄ ÄÄÍ G U S M O D ÍÄÄ ÄÄùù ù  ú ú                 }
{             An mod player system by Mc.kriss of Extravaganza               }
{                               ÄÄÄ ÄÄÍÍÄÄ ÄÄ                                }
{                                                                            }
{ NOTiCE: To create this proggy I've used some Mark Dixon's procedures whose }
{ I took from Pc Game Programmers Encyclopedia V1.0, also i've used document }
{      which was written by Andrew Scott, before i wrote a new (my) one.     }
{                                                                            }
{                          Credits to EV's GUSMOD:                           }
{    Pascal version & player - Mc.KRiSS, WatCom C++ version by Mr.BAGGiNS.   }

Unit GUSMOD;                                    
Interface
Uses Dos;

Const
  MaxChannels          = 32;
  AmpliferVolume: Byte = 17;
  DefaultPanning: Byte = 04;                        { Default Panning;  0..7 }
  MaxPeroid            = 1000;           { Max & Min period for Amiga limits }
  MinPeroid            = 20;
  UltraBase: Word      = $200;                            { GUS Base Address }
  ModError: Word       = 0;                                   { 0..3 and 255 }

  ActiveVoice          = $102;
  Command              = $103;
  DataLow              = $104;
  DataHigh             = $105;
  DRamIo               = $107;

  ActiveVoice2: Word   = ActiveVoice;
  Command2: Word       = Command;
  DataLow2: Word       = DataLow;
  DataHigh2: Word      = DataHigh;
  DRamIo2: Word        = DRamIo;

  GusInfo: Array [0..7] of Byte = ($42,$45,$54,$41,$2f,$41,$44,$4e);

  PeroidTable: Array [0..15,1..48] of Word = (
    (856,808,762,720,678,640,604,570,538,508,480,453,  { C-1..B-1 FineTune 0 }
     428,404,381,360,339,320,302,285,269,254,240,226,  { C-2..B-2 FineTune 0 }
     214,202,190,180,170,160,151,143,135,127,120,113,  { C-3..B-3 FineTune 0 }
     107,101,095,090,085,080,075,071,067,063,060,056), { C-4..B-4 FineTune 0 }
    (850,802,757,715,674,637,601,567,535,505,477,450,  { C-1..B-1 FineTune+1 }
     425,401,379,357,337,318,300,284,268,253,239,225,  { C-2..B-2 FineTune+1 }
     213,201,189,179,169,159,150,142,134,126,119,113,  { C-3..B-3 FineTune+1 }
     106,100,094,089,084,079,075,071,067,083,059,056), { C-4..B-4 FineTune+1 }
    (844,796,752,709,670,632,597,563,532,502,474,447,  { C-1..B-1 FineTune+2 }
     422,398,376,355,335,316,298,282,266,251,237,224,  { C-2..B-2 FineTune+2 }
     211,199,188,177,167,158,149,141,133,125,118,112,  { C-3..B-3 FineTune+2 }
     105,099,094,088,083,079,074,070,066,062,059,056), { C-4..B-4 FineTune+2 }
    (838,791,746,704,665,628,592,559,528,498,470,444,  { C-1..B-1 FineTune+3 }
     419,395,373,352,332,314,296,280,264,249,235,222,  { C-2..B-2 FineTune+3 }
     209,198,187,176,166,157,148,140,132,125,118,111,  { C-3..B-3 FineTune+3 }
     104,099,093,088,083,078,074,070,066,062,059,055), { C-4..B-4 FineTune+3 }
    (832,785,741,699,660,623,588,555,524,495,467,441,  { C-1..B-1 FineTune+4 }
     416,392,370,350,330,312,294,278,262,247,233,220,  { C-2..B-2 FineTune+4 }
     208,196,185,175,165,156,147,139,131,124,117,110,  { C-3..B-3 FineTune+4 }
     104,098,092,087,082,078,073,069,065,062,058,055), { C-4..B-4 FineTune+4 }
    (826,779,736,694,655,619,584,551,520,491,463,437,  { C-1..B-1 FineTune+5 }
     413,390,368,347,328,309,292,276,260,245,232,219,  { C-2..B-2 FineTune+5 }
     206,195,184,174,164,155,146,138,130,123,116,109,  { C-3..B-3 FineTune+5 }
     103,097,092,087,082,077,073,069,065,061,058,054), { C-4..B-4 FineTune+5 }
    (820,774,730,689,651,614,580,547,516,487,460,434,  { C-1..B-1 FineTune+6 }
     410,387,365,345,325,307,290,274,258,244,230,217,  { C-2..B-2 FineTune+6 }
     205,193,183,172,163,154,145,137,129,122,115,109,  { C-3..B-3 FineTune+6 }
     102,096,091,086,081,077,072,068,064,061,057,054), { C-4..B-4 FineTune+6 }
    (814,768,725,684,646,610,575,543,513,484,457,431,  { C-1..B-1 FineTune+7 }
     407,384,363,342,323,305,288,272,256,242,228,216,  { C-2..B-2 FineTune+7 }
     204,192,181,171,161,152,144,136,128,121,114,108,  { C-3..B-3 FineTune+7 }
     102,096,090,085,080,076,072,068,064,060,057,054), { C-4..B-4 FineTune+7 }
    (907,856,808,762,720,678,640,604,570,538,504,480,  { C-1..B-1 FineTune-8 }
     453,428,404,381,360,339,320,302,285,269,254,240,  { C-2..B-2 FineTune-8 }
     226,214,202,190,180,170,160,151,143,135,127,120,  { C-3..B-3 FineTune-8 }
     113,107,101,095,090,085,080,075,071,067,063,060), { C-4..B-4 FineTune-8 }
    (900,850,802,757,715,675,636,601,567,535,505,477,  { C-1..B-1 FineTune-7 }
     450,425,401,379,357,337,318,300,284,268,253,238,  { C-2..B-2 FineTune-7 }
     225,212,200,189,179,169,159,150,142,134,126,119,  { C-3..B-3 FineTune-7 }
     112,106,100,094,089,084,079,075,071,067,063,059), { C-4..B-4 FineTune-7 }
    (894,844,796,752,709,670,632,597,563,532,502,474,  { C-1..B-1 FineTune-6 }
     447,422,398,376,355,335,316,298,282,266,251,237,  { C-2..B-2 FineTune-6 }
     223,211,199,188,177,167,158,149,141,133,125,118,  { C-3..B-3 FineTune-6 }
     111,105,099,094,088,083,079,074,070,066,062,059), { C-4..B-4 FineTune-6 }
    (887,838,791,746,704,665,628,592,559,528,498,470,  { C-1..B-1 FineTune-5 }
     444,419,395,373,352,332,314,296,280,264,249,235,  { C-2..B-2 FineTune-5 }
     222,209,198,187,176,166,157,148,140,132,125,118,  { C-3..B-3 FineTune-5 }
     111,104,099,093,088,083,078,074,070,066,062,059), { C-4..B-4 FineTune-5 }
    (881,832,785,741,699,660,623,588,555,524,494,467,  { C-1..B-1 FineTune-4 }
     441,416,392,370,350,330,312,294,278,262,247,233,  { C-2..B-2 FineTune-4 }
     220,208,196,185,175,165,156,147,139,131,123,117,  { C-3..B-3 FineTune-4 }
     110,104,098,092,087,082,078,073,069,065,061,058), { C-4..B-4 FineTune-4 }
    (875,826,779,736,694,655,619,584,551,520,491,463,  { C-1..B-1 FineTune-3 }
     437,413,390,368,347,338,309,292,276,260,245,232,  { C-2..B-2 FineTune-3 }
     219,206,195,184,174,164,155,146,138,130,123,116,  { C-3..B-3 FineTune-3 }
     109,103,097,092,087,082,077,073,069,065,061,058), { C-4..B-4 FineTune-3 }
    (868,820,774,730,689,651,614,580,547,516,487,460,  { C-1..B-1 FineTune-2 }
     434,410,387,365,345,325,307,290,274,258,244,230,  { C-2..B-2 FineTune-2 }
     217,205,193,183,172,163,154,145,137,129,122,115,  { C-3..B-3 FineTune-2 }
     108,102,096,091,086,081,077,072,068,064,061,057), { C-4..B-4 FineTune-2 }
    (862,814,768,725,684,646,610,575,543,513,484,457,  { C-1..B-1 FineTune-1 }
     431,407,384,363,342,323,305,288,272,256,242,228,  { C-2..B-2 FineTune-1 }
     216,203,192,181,171,161,152,144,136,128,121,114,  { C-3..B-3 FineTune-1 }
     108,101,096,090,085,080,076,072,068,064,060,057));{ C-4..B-4 FineTune-1 }

  GusVolume: Array [0..64] of Word =                      { GUS Volume Table }
    (0000,1750,2503,2701,2741,2781,2944,2964,2981,
     3000,3017,3034,3052,3070,3207,3215,3224,3232,
     3240,3248,3256,3263,3271,3279,3287,3294,3303,
     3310,3317,3325,3458,3462,3466,3469,3473,3478,
     3481,3484,3489,3492,3495,3499,3502,3506,3509,
     3513,3517,3520,3524,3528,3532,3534,3538,3543,
     3545,3549,3552,3556,3558,3563,3565,3570,3573,
     3577,3580);

  VibratoTable: Array [0..2,0..63] of ShortInt =
   (( 00, 06, 12, 19, 24, 30, 36, 41, 45, 49, 53, 56, 59, 61, 63, 64,
      64, 64, 63, 61, 59, 56, 53, 49, 45, 41, 36, 30, 24, 19, 12, 06,
      00,-06,-12,-19,-24,-30,-36,-41,-45,-49,-53,-56,-59,-61,-63,-64,
     -64,-64,-63,-61,-59,-56,-53,-49,-45,-41,-36,-30,-24,-19,-12,-06),
    (-63,-61,-59,-57,-55,-53,-51,-49,-47,-45,-43,-41,-39,-37,-35,-33,
     -31,-29,-27,-25,-23,-21,-19,-17,-15,-13,-11,-09,-07,-05,-03,-01,
      01, 03, 05, 07, 09, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31,
      33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63),
    (-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,
     -64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,
      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
      64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64));

Type
  MemArray             = Array [0..2000] of Word;

  Channel              = Record
    Vol                : Byte;                        { Current Volume 0..64 }
    Note               : Byte;            { Current Note 1--"C1" to 48--"B4" }
    Per, DesPer        : Word; { Period & Destination period for portamentos }
    Sample             : Byte;                       { Current Sample number }
    Pan                : Byte;                          { GUS Panning option }
    Fx, FxData         : Byte;
    FxSl2, FxVib       : Byte;                 { Slide To & Vibrato, FX data }
    VibWave            : Byte;                            { Vibrato WaveForm }
    VibCnt             : Byte;                             { Vibrato Counter }
    RetrigCnt          : Byte;                              { Retrig Counter }
    Arp1, Arp2         : Byte;                          { Arpeggio Parametrs }
    ArpeggioCnt        : Byte;                            { Arpeggio Counter }
    FXStart            : Byte;                   { Tick to start for channel }
    On                 : Byte;                   { When 0 = channel is muted }
    Bar                : Byte;                                  { Volume Bar }
    hit                : Byte;
    NoFx               : Byte;                        { 1 = Don't get new FX }
  End;

  MODSample            = Record
    Name               : Array [1..40] of Char;             { Name of sample }
    Addr               : Longint;                       { Address in GUS Mem }
    Length             : Word;
    LoopStart          : Word;
    LoopEnd            : Word;
    FTune              : Byte;
    Volume             : Byte;
  End;

  MODNote              = Record
    Per                : Word;
    Note               : Byte;
    Sample             : Byte;
    Fx, FXData         : Byte;
  End;

  ModHeader            = Record
    Name               : String [20];
    Length             : Byte;
    Tag                : Array [0..3] of Char;       { e.g. "6CHN" Tag Field }
    Chns               : Byte;            { Number of channels in MOD, 4..16 }
    Samples            : Byte;                                    { 15 or 31 }
  End;

  MODRow               = Array [0..MaxChannels-1] of MODNote;
  MODPattern           = Array [0..63] of MODRow;
  PointerPattern       = ^MODPattern;

Var
  GusAdress            : Array [0..32] of Longint;
  Periods              : Array [0..1100] of Word;
  Channels             : Array [0..MaxChannels-1] of Channel;
  Samples              : Array [0..32] of MODSample;
  Patterns             : Array [0..128] of PointerPattern;
  Orders               : Array [0..255] of Byte;
  MaxPatterns          : Word;                        { # of Patterns in MOD }
  CurrentPat           : Byte;
  CurrentRow           : Byte;
  CurrentTick          : Byte;
  NewPattern           : Byte;
  NewRow               : Byte;    
  Jump                 : Byte;
  Speed                : Byte; 
  NSpeed               : Byte;
  Tempo                : Byte;
  VBlank               : Boolean;         { TRUE means do not use BPM tempos }

  Header               : ModHeader;
  TopAdress            : LongInt;             { Next free address in GUS mem }
  TimeCounter          : LongInt;           { For syncing with demos. 1/18.2 }
  TimeCounter2         : LongInt;                      { Increase every tick }
  VrtFlag              : Byte;         { if 1 then vertical retrace happened }


Function VoicePos (V: Byte)     : Longint;
Function GUSPeek (Loc: Longint) : Byte;
Function GUSProbe (Adr: Word)   : Boolean;
Function GUSFindMem             : Longint;

Procedure GUSDelay;
Procedure GUSPoke (Loc: Longint; B: Byte);
Procedure GUSFind;
Procedure GUSSetFreq (V: Byte; Hz: Word);
Procedure GUSVoiceControl (V, B: Byte);
Procedure GUSSetBalance (V,Bal: Byte);
Procedure GUSSetVolume (V: Byte; Vol: Word);
Procedure GUSSetLoopMode (V: Byte);
Procedure GUSStopVoice (V: Byte);
Procedure GUSPlayVoice (V, Mode: Byte; VBegin,VStart,VEnd: Longint);
Procedure GUSRelVoice (V: Byte);
Procedure GUSSetOfs (V: Byte; Vbegin: Longint);
Procedure GUSReset;
Procedure GUSDeInit;

Procedure UpDateNotes;
Procedure StartPlaying;
Procedure StopPlaying;
Procedure SetTimer (Ticks: Word);
Procedure FreeModule;
Procedure LoadModule (S: String; Ofst: LongInt; Debug: Boolean);

Implementation

Var
  OldInt               : Procedure;
  IntTick              : Word;
  OldIntTick           : Word;
  IntRate              : Word;
  GusBank              : Longint;
  TempBuff             : Array [0..256*MaxChannels] of Byte;     { Clipboard }
  PTempBuff            : ^MemArray;                    { pointer to TempBuff }

{$S-}

Function Hex (W: Word): String;
Const
    H: Array[0..15] of Char = '0123456789ABCDEF';
Var
  I,J: Word;
  S  : String;
  C  : Char;
Begin
  S:='';
  S:=S+H[(W DIV $1000) MOD 16];
  S:=S+H[(W DIV $100 ) MOD 16];
  S:=S+H[(W DIV $10  ) MOD 16];
  S:=S+H[(W DIV $1   ) MOD 16];
  Hex:=S+'h';
End;

Procedure GUSDelay; Assembler;
ASM
  mov   dx,UltraBase
  add   dx,$100
  in    al, dx
  in    al, dx
  in    al, dx
  in    al, dx
  in    al, dx
  in    al, dx
  in    al, dx
End;

Function VoicePos (V: Byte): LongInt;                           { NOT TESTED }
Var
  P    : Longint;
  I    : Word;
  Temp0: Word;
  Temp1: Word;
Begin
  Asm
    mov dx,ActiveVoice2
    mov V,al
    out dx,al
    mov dx,Command2
    mov al,8Ah
    out dx,al
    mov dx,DataLow2
    in  ax,dx
    mov ax,Temp0
    mov dx,Command2
    mov al,8Bh
    out dx,al
    mov dx,DataLow2
    in  ax,dx
    mov ax,Temp1
  End;
  VoicePos:=(Temp0 SHL 7)+(Temp1 SHR 8);
End;

Function GUSPeek (Loc : Longint): Byte;
Var
  B    : Byte;
  AddLo: Word;
  AddHi: Byte;
Begin
  AddLo:=Loc And $FFFF;
  AddHi:=LongInt(Loc And $FF0000) Shr 16;
  Port [UltraBase+$103]:=$43;
  PortW[UltraBase+$104]:=AddLo;
  Port [UltraBase+$103]:=$44;
  Port [UltraBase+$105]:=AddHi;
  B:=Port[UltraBase+$107];
  GUSPeek:=B;
End;

Procedure GUSPoke(Loc : Longint; B : Byte);
Var
  AddLo: Word;
  AddHi: Byte;
Begin
  AddLo:=Loc And $FFFF;
  AddHi:=longint(Loc And $ff0000) Shr 16;
  Port [UltraBase+$103]:=$43;
  PortW[UltraBase+$104]:=AddLo;
  Port [UltraBase+$103]:=$44;
  Port [UltraBase+$105]:=AddHi;
  Port [UltraBase+$107]:=B;
End;

Function GUSProbe(adr : word) : Boolean;
Var
  B         : Byte;
  OUltraBase: word;
Begin
  OUltraBase:=UltraBase;
  UltraBase:=Adr;
  Port[UltraBase+$103]:=$4C;
  Port[UltraBase+$105]:=0;
  GUSDelay; GUSDelay;
  Port[UltraBase+$103]:=$4C;
  Port[UltraBase+$105]:=1;
  GUSPoke(0, $AA);
  GUSPoke($100, $55);
  B:=GUSPeek(0);
  UltraBase:=OUltraBase;
  If (B=$AA) then GUSProbe:=True else GUSProbe:=False;
End;

Procedure GUSFind;
Var
  I: Word;
Begin
  For I:=1 to 8 do
    Begin
      UltraBase:=$200+I*$10;
      If GUSProbe(UltraBase) Then I:=8;
    End;
  If UltraBase<$280 then
    Begin
      WriteLn('þ Gravis UltraSound found at ',Copy(Hex(UltraBase),2,4));
      ActiveVoice2 := UltraBase+ActiveVoice;
      Command2:=UltraBase+Command;
      DataLow2:=UltraBase+DataLow;
      DataHigh2:=UltraBase+DataHigh;
      DramIo2:=UltraBase+DRamIo;
    End
    Else WriteLn('þ Gravis UltraSound not found!');
End;

Function GUSFindMem: Longint;
Var
  I: Longint;
  B: Byte;
Begin
  GUSPoke($40000,$AA);
  If GUSPeek($40000)<>$AA then I:=$3FFFF
    Else
      Begin
        GUSPoke($80000, $AA);
        If GUSPeek($80000)<>$AA then I:=$8FFFF
          Else
            Begin
              GUSPoke($C0000, $AA);
              If GUSPeek($C0000)<>$AA then I:=$CFFFF
                Else I:=$FFFFF;
            End;
      End;
  GusPoke(0,$aa);
  If GusPeek(0)<>$AA then I:=0;
  GUSFindMem:=I;
End;

Procedure GUSSetFreq (V: Byte; Hz: Word); Assembler;
Asm
  cli
  mov  dx,ActiveVoice2
  mov  al,v
  out  dx,al
  mov  dx,Command2
  mov  al,1
  out  dx,al
  mov  dx,DataLow2
  mov  ax,hz
  out  dx,ax
  sti
End;

Procedure GUSVoiceControl (V,B: Byte); Assembler;               { NOT TESTED }
Asm
  cli
  mov dx,UltraBase
  add dx,102h
  mov V,al
  out dx,al
  out dx,al
  out dx,al
  inc dx
  xor al,al
  out dx,al
  inc dx
  inc dx
  mov B,al
  out dx,al
  sti
End;

Procedure GUSSetBalance (V,Bal: Byte); Assembler;
Asm
  cli
  mov  dx,ActiveVoice2
  mov  al,v
  out  dx,al
  mov  dx,Command2
  mov  al,0ch
  out  dx,al
  mov  dx,DataHigh2
  mov  al,bal
  out  dx,al
  sti
end;

Procedure GUSSetVolume (V: Byte; Vol: Word); Assembler;
Asm
  cli
  mov  dx,ActiveVoice2
  mov  al,v
  out  dx,al
  mov  dx,Command2
  mov  al,9
  out  dx,al
  mov  dx,DataLow2
  mov  ax,vol
  out  dx,ax
  sti
End;

Procedure GUSSetLoopMode (V: Byte); Assembler;                  { NOT TESTED }
Asm
  cli
  mov dx,UltraBase
  add dx,102h
  mov V,al
  out dx,al
  out dx,al
  out dx,al
  inc dx
  mov al,80h
  out dx,al
  add dx,2
  in  al,dx
  mov al,cl
  sub dx,2
  mov al,0
  out dx,al
  add dx,2
  mov al,cl
  and al,0E7h
  or  al,0
  out dx,al
  sti
End;

Procedure GUSStopVoice(V: Byte); Assembler;
Asm
  cli
  mov dx,ActiveVoice2
  mov al,V
  out dx,al
  out dx,al
  mov dx,Command2
  mov al,80h
  out dx,al
  mov dx,DataHigh2
  in  al,dx
  mov al,cl
  mov dx,Command2
  xor al,al
  out dx,al
  mov dx,DataHigh2
  mov cl,al
  and al,0DFh
  or  al,3
  call GusDelay
  mov dx,Command2
  xor al,al
  out dx,al
  mov dx,DataHigh2
  mov cl,al
  and al,0DFh
  or  al,3
  mov dx,ActiveVoice2
  mov al,v
  out dx,al
  mov dx,Command2
  mov al,9
  out dx,al
  mov dx,DataLow2
  xor ax,ax
  out dx,ax
  sti
End;

Procedure GUSPlayVoice (V,Mode: Byte; VBegin,VStart,VEnd: Longint);
Begin
  Asm
    cli
  End;
  Port[ActiveVoice2]:= V;
  Port[ActiveVoice2]:= V;
  port[Command2]:=0;
  port[DataHigh2]:=2;                                { This means voice stop }
  Port [Command2]:=$0A;
  Portw[DataLow2]:=(VBegin Shr 7) And 8191;
  Port [Command2]:=$0B;
  Portw[DataLow2]:=(VBegin And 127) Shl 9;
  Port [Command2]:=$02;
  Portw[DataLow2]:=(VStart Shr 7) And 8191;
  Port [Command2]:=$03;
  Portw[DataLow2]:=(VStart And 127) Shl 9;
  Port [Command2]:=$04;
  Portw[DataLow2]:=((VEnd) Shr 7) And 8191;
  Port [Command2]:=$05;
  Portw[DataLow2]:=((VEnd) And 127) Shl 9;
  Port [Command2]:=0;
  Port[DataHigh2]:=Mode and $FE;
  Asm
    sti
  End;
End;

Procedure
  GUSPlayAll (V,Mode: Byte; VBegin,VStart,VEnd: Longint; Freq,Vol: Word);
Begin
  asm
    cli
    mov  dx,ActiveVoice2
    mov  al,v
    out  dx,al
    mov  cx,Command2
    mov  dx,cx
    mov  al,0
    out  dx,al
    mov  dx,DataHigh2                                           { Stop Voice }
    mov  al,2
    out  dx,al
    mov  dx,cx
    mov  al,9
    out  dx,al
    mov  dx,DataLow2
    mov  ax,vol
    out  dx,ax                                                  { Set Volume }
    mov  dx,cx
    mov  al,1
    out  dx,al
    mov  dx,DataLow2
    mov  ax,freq
    out  dx,ax
  end;
  Port [Command2]:=$0A;
  Portw[DataLow2]:=(VBegin Shr 7) And 8191;
  Port [Command2]:=$0B;
  Portw[DataLow2]:=(VBegin And 127) Shl 9;
  Port [Command2]:=$02;
  Portw[DataLow2]:=(VStart Shr 7) And 8191;
  Port [Command2]:=$03;
  Portw[DataLow2]:=(VStart And 127) Shl 9;
  Port [Command2]:=$04;
  Portw[DataLow2]:=((VEnd) Shr 7) And 8191;
  Port [Command2]:=$05;
  Portw[DataLow2]:=((VEnd) And 127) Shl 9;
  Port [Command2]:=0;
  Port[DataHigh2]:=Mode and $FE;
  Asm
    sti
  End;
End;

Procedure GusRelVoice (V: Byte); Assembler;                     { NOT TESTED }
Asm
  mov dx,ActiveVoice2
  mov V,al
  out dx,al
  mov dx,Command2
  mov al,80h
  out dx,al
  mov dx,DataHigh2
  in  al,dx
  mov al,cl
  mov dx,Command2
  xor al,al
  out dx,al
  mov dx,DataHigh2
  mov cl,al
  and al,0FCh
  out dx,al
End;

Procedure GusRelVoices(N: Byte); Assembler;           { N - number of voices }
Asm
  mov  bl,n
@@1:
  mov  dx,UltraBase
  add  dx,102h
  mov  al,bl
  out  dx,al
  inc  dx
  mov  al,80h
  out  dx,al
  add  dx,2
  in   al,dx
  mov  al,0
  sub  dx,2
  out  dx,al
  add  dx,2
  mov  cl,al
  and  cl,0fdh
  mov  al,cl
  out  dx,al
  dec  bl
  jnz  @@1
End;

Procedure GusSetOfs (V: Byte; VBegin: LongInt);
Begin
  Asm
    cli
  End;
  Port [ActiveVoice2]:=V;
  Port [ActiveVoice2]:=V;
  Port [Command2]:=$0A;
  Portw[DataLow2]:=(VBegin Shr 7) And 8191;
  Port [Command2]:=$0B;
  Portw[DataLow2]:=(VBegin And 127) Shl 9;
  Asm
    sti
  End;
End;

Procedure GUSReset;
Var
  N: Byte;
Begin
  Asm
    mov dx,UltraBase
    mov al,3
    out dx,al
    add dx,103h
    mov al,4Ch
    out dx,al
    add dx,2
    mov al,1
    out dx,al
    call GusDelay
    sub dx,2
    mov al,4Ch
    out dx,al
    add dx,2
    xor al,al
    out dx,al
    call GusDelay
    sub dx,2
    mov al,4Ch
    out dx,al
    add dx,2
    mov al,1
    out dx,al
    Call GusDelay
    sub dx,2
    mov al,4Ch
    out dx,al
    add dx,2
    mov al,7
    out dx,al
    sub dx,2
    mov al,0Eh
    out dx,al
    add dx,2
    mov al,13
    or  al,0C0h
    out dx,al
    call GusDelay
    mov dx,UltraBase
    mov al,1
    out dx,al
    add dx,Command
    mov al,4Ch
    out dx,al
    mov dx,UltraBase
    add dx,DataHigh
    mov al,7
    out dx,al
  End;
  For N:=1 to 16 Do                                       { Reset All Voices }
    Begin
      port [UltraBase+ActiveVoice]:=N;
      port [UltraBase+Command]:=0;
      port [UltraBase+DataHigh]:=2;
      port [UltraBase+Command]:=$d;
      port [UltraBase+DataHigh]:=2;
      port [UltraBase+Command]:=$a;
      portw[UltraBase+DataLow]:=0;
      port [UltraBase+Command]:=$b;
      portw[UltraBase+DataLow]:=0;
      port [UltraBase+Command]:=9;
      portw[UltraBase+DataLow]:=0;
    End;
End;

{$S-}

Procedure ErrorExit;
Begin
  Write('þ ERROR #0',ModError,': ');
  Case ModError Of
    1: WriteLn('More channels in module than ',MaxChannels,'!');
    2: WriteLn('Pattern higher than 127!');
    3: WriteLn('No enough memory for patterns!');
    4: WriteLn('Couldn''t read module!');
  End;
  Halt;
End;

Procedure GusDeInit;
Var
  N: Word;
Begin
  For N:=1 To 14 do GusStopVoice(N);
End;

Procedure GeMODNotes;
var
  Chn      : Byte;
  Ptn      : Byte;
  Org_Sam  : Byte;
  Sam      : Byte;
  Note     : Byte;
  St_Ofs   : LongInt;
  Per      : Word;
  DesPer   : Word;
  Vol      : Word;
  Freq     : Word;
  TemFx    : Word;
  TemFxData: Byte;
  Mute     : Byte;
  _Ptn     : PointerPattern;

Procedure PreFx;
Var
  W         : Word;
  TemEFxdata: Byte;
Begin
  Case TemFx of
    $9: Begin
          W:=TemFxdata*$100;
          St_Ofs:=W;
          Channels[Chn].NoFx:=1;
          Channels[Chn].Fx:=TemFx;
          Channels[Chn].FxData:=TemFxData;
        End;
    $C: Begin
          If TemFxData>64 then TemFxData:=64;
          Vol:=TemFxData;
        End;
    $E: Begin
          TemEFxData:=TemFxData And 15;
           Case TemFxData Shr 4 of
             $4: Begin
                   Channels[Chn].Fx:=TemFx;
                   Channels[Chn].FxData:=TemFxData;
                   If TemEFxData And 3<3 Then Channels[Chn].VibWave:=TemEFxdata
                                       else Channels[Chn].VibWave:=0
                                         or (TemEFxData And 4);
                 End;
             $C: Begin
                   If TemEFxData And 15=0 Then
                     Begin
                       Mute:=1;
                       GusStopVoice(Chn+1);
                     End;
                 End;
             $D: Begin
                   If TemEFxData>0 Then Mute:=2 else Mute:=0;
                 End;
           End;
        End;
  End;
End;

Begin
  Ptn:=Orders[CurrentPat];
  For Chn:=0 To Header.Chns-1 Do
    Begin
      If Channels[Chn].Fx=0 Then
        Begin
          Sam:=Channels[Chn].Sample;
          Per:=PeroidTable[Samples[Sam].FTune,Channels[Chn].Note];
          GusSetFreq(Chn+1,Periods[Per]);
        End;
      Channels[Chn].Hit:=0;
      If ((Patterns[Ptn]^[CurrentRow,Chn].Per>0)
      or  (Patterns[Ptn]^[CurrentRow,Chn].Sample>0)) Then
        Begin
          Mute   :=1;
          Vol    :=Channels[Chn].Vol;
          Per    :=Channels[Chn].Per;
          Note   :=Channels[Chn].Note;
          Freq   :=Periods[Channels[Chn].Per];
          TemFx    :=Patterns[Ptn]^[CurrentRow,Chn].Fx;
          TemFxData:=Patterns[Ptn]^[CurrentRow,Chn].FxData;
          Org_Sam:=Patterns[Ptn]^[CurrentRow,Chn].Sample;
          Channels[Chn].FXStart:=0;
          Channels[Chn].RetrigCnt:=0;
          If Org_Sam=0 Then
            Begin
              Sam:=Channels[Chn].Sample;
            End
            Else
            Begin
              Sam:=Org_Sam;
            End;
          If (TemFx=3) or (TemFx=5) Then
            Begin             { Portamento To / Portamento To & Volume Slide }
              Mute:=1;                                { Don't Restart sample }
              If Patterns[Ptn]^[CurrentRow,Chn].Note>0 Then
                Begin
                  Note:=Patterns[Ptn]^[CurrentRow,Chn].Note;
                  DesPer:=PeroidTable[Samples[Sam].FTune,Note];
                  If DesPer>MaxPeroid Then DesPer:=MaxPeroid;
                  If DesPer<MinPeroid Then DesPer:=MinPeroid;
                  Channels[Chn].DesPer:=DesPer;
                End;
            End
            Else
              If Patterns[Ptn]^[CurrentRow,Chn].Per>0 Then
                Begin
                  If patterns[Ptn]^[CurrentRow,Chn].Note>0 Then
                    Begin
                      Note:=Patterns[Ptn]^[CurrentRow,Chn].Note;
                      Per:=PeroidTable[Samples[Sam].FTune,Note];
                    End
                    Else
                      If Patterns[Ptn]^[CurrentRow,Chn].Per>0 Then
                         Per:=Patterns[Ptn]^[CurrentRow,Chn].Per;
                  If Per>MaxPeroid Then Per:=MaxPeroid;
                  if Per<MinPeroid Then Per:=MinPeroid;
                  Channels[Chn].DesPer:=Per;
                  Channels[Chn].Per:=Per;
                  Freq:=Periods[Per];
                  Mute:=0;
                End;
              If Org_Sam>0 Then
                Begin
                  Vol:=Samples[Sam].Volume;
                  If Channels[Chn].Sample<>Org_Sam Then Mute:=0;
                End;
              If Samples[Sam].Length>0 Then St_Ofs:=2;{ 1..2, Amiga Loopinfo }
              Channels[Chn].NoFx:=0;
              PreFx;
              Channels[Chn].Vol:=Vol;
              Channels[Chn].Note:=Note;
              If Channels[Chn].VibWave and 4 = 0 then
                 Channels[chn].VibCnt:=0;
              Channels[Chn].Sample:=Sam;
              Channels[Chn].Bar:=Channels[Chn].Vol;
              Vol:=GusVolume[Vol]*AmpliferVolume;
              If Channels[Chn].On=0 Then Mute:=1;
              if Mute=0 Then
                Begin
                  Channels[Chn].Hit:=1;
                  GusSetBalance(Chn+1,Channels[Chn].Pan);
                  If (Samples[Sam].Loopend>2) Then
                     GusPlayAll(Chn+1,8,GusAdress[sam]+st_ofs,
                                GusAdress[sam]+samples[sam].loopstart,
                                GusAdress[sam]+samples[sam].loopend,freq,vol)
                else GusPlayAll(chn+1,0,GusAdress[sam]+st_ofs,
                                GusAdress[sam]+st_ofs,
                                 GusAdress[sam]+samples[sam].length,freq,vol);
                End
                Else
                  if (Channels[Chn].On = 1) And (Mute=1) Then
                     GusSetVolume(Chn+1,Vol);
        End;
    End;
End;

Procedure GetTemFx;
Var
  Chn       : Byte;
  Ptn       : Byte;
  TemFx     : Byte;
  TemFxData : Byte;
  TemEFx    : Byte;
  TemEFxdata: Byte;
  Per       : Word;
  B         : Byte;
  W         : Word;

Begin
  Ptn:=Orders[CurrentPat];
  NewPattern:=CurrentPat;
  NewRow:=CurrentRow;
  Jump:=0;
  For Chn:=0 To Header.Chns-1 Do
  If Channels[Chn].NoFx=0 Then
    Begin
      Channels[Chn].FXStart:=0;
      Channels[Chn].Fx:=255;
      TemFx:=Patterns[Ptn]^[CurrentRow,Chn].Fx;
      TemFxData:=Patterns[Ptn]^[CurrentRow,Chn].FxData;
      Case TemFx of
        $0: If TemFxData>0 then
              Begin                          { Arpeggio: simulate the chords }
                Channels[Chn].Fx:=TemFx;
                Channels[Chn].Fxdata:=TemFxData;
                Channels[Chn].Arp1:=TemFxData shr 4;
                Channels[Chn].Arp2:=TemFxData and 15;
                Channels[Chn].ArpeggioCnt:=0;
              End;
        $1: Begin                                            { Portamento up }
              Channels[Chn].Fx:=TemFx;
              Channels[Chn].FxData:=TemFxdata;
              Channels[Chn].FXStart:=2;
            End;
        $2: Begin                                          { Portamento down }
              Channels[Chn].Fx:=TemFx;
              Channels[Chn].FxData:=TemFxdata;
              Channels[Chn].FXStart:=2;
            End;
        $3: Begin                                          { Tone Portamento }
              Channels[Chn].Fx:=TemFx;
              If TemFxData>0 Then
                Begin
                  Channels[Chn].FxData:=TemFxData;
                  Channels[Chn].FxSl2:=TemFxData;
                End
                Else Channels[Chn].FxData:=Channels[Chn].FxSl2;
              Channels[Chn].FXStart:=2;
            End;
        $4: Begin                                                  { Vibrato }
              Channels[Chn].Fx:=TemFx;
              B:=TemFxData And 15;
              If B=0 Then B:=Channels[Chn].FxVib And 15;
              W:=B;
              B:=TemFxData Shr 4;
              If B=0 Then B:=Channels[Chn].FxVib Shr 4;
              W:=W Or (B Shl 4);
              B:=W;
              Channels[Chn].FxData:=B;
              Channels[Chn].FxVib:=B;
            End;
        $5: Begin                           { Tone Portamento & Volume Slide }
              Channels[Chn].Fx:=TemFx;
              If TemFxdata And 15>0 Then TemFxData:=TemFxData And 15; { slide down }
              Channels[Chn].Fxdata:=TemFxData;
            End;
        $6: Begin                                   { Vibrato & Volume Slide }
              Channels[Chn].fx:=TemFx;
              Channels[Chn].fxdata:=TemFxdata;
            end;
        $7: begin                                                  { Tremolo }
              channels[chn].fx:=TemFx;
              channels[chn].fxdata:=TemFxdata;
            end;
        $8: begin                                              { Set panning }
              channels[chn].fx := TemFx;
              channels[chn].fxdata := TemFxdata;
            end;
        $9: Begin                                        { Set Sample offset }
              Channels[chn].fx:=TemFx;
              channels[chn].fxdata:=TemFxdata;
              w:=TemFxdata*256;
              b:=channels[chn].sample;
              if channels[chn].on=1 then gussetofs(chn+1,GusAdress[b]+w);
            End;
        $A: begin                                             { Volume slide }
              channels[chn].fx:=TemFx;
              if TemFxdata and 15>0 then TemFxdata:=TemFxdata and 15;   { up }
              channels[chn].fxdata := TemFxdata;
              channels[chn].FXStart := 2;
            end;
        $B: begin                                            { Position jump }
             if TemFxdata<MaxPatterns then
               begin
                 NewPattern:=TemFxdata;
                 NewRow:=0;
                 jump:=1;
               end;
            end;
        $C: begin                                               { Set volume }
              if TemFxdata>64 then TemFxdata:=64;
              channels[chn].fx:=TemFx;
              channels[chn].fxdata:=TemFxdata;
              channels[chn].vol:=TemFxdata;
              channels[chn].bar:=TemFxdata;
              w:=GusVolume[TemFxdata]*AmpliferVolume;
              if channels[chn].on=1 then GusSetVolume(chn+1,w);
            end;
        $D: Begin                                            { Break pattern }
              NewPattern:=CurrentPat;
              inc(NewPattern);
              NewRow:=((TemFxdata and $f0) shr 4)*10+TemFxdata and 15;
              jump:=1;
            end;
        $E: begin                                         { Extended effects }
              channels[chn].fx:=TemFx;
              channels[chn].fxdata:=TemFxdata;
              TemEFx:=TemFxdata shr 4;
              TemEFxdata:=TemFxdata and 15;
              case TemEFx of
                $1: begin                            { Unused AMiGA Filter ? }
                      per:=channels[chn].per;
                      inc(per,TemEFxdata);
                      if per>MaxPeroid then per:=MaxPeroid;
                      channels[chn].per:=per;
                      W:=periods[channels[chn].per];
                      gussetfreq(chn+1,w);
                    end;
                $2: begin
                      per:=channels[chn].per;
                      dec(per,TemEFxdata);
                      if per<MinPeroid then per:=MinPeroid;
                      channels[chn].per:=per;
                      w:=periods[channels[chn].per];
                      gussetfreq(chn+1,w);
                    end;
                $4: begin                             { Set Vibrato waveform }
                     channels[chn].VibWave:=TemEFxdata;
                    end;
                $5: begin                                     { Set finetune }
                      samples[channels[chn].sample].ftune := TemEFxdata;
                    end;
                $8: begin                                  { set MTM-Panning }
                      channels[chn].pan:=TemEFxdata;
                      gussetbalance(chn+1,TemEFxdata);
                    end;
                $9: if TemEFxdata>0 then                         { Retrigger }
                      begin
                        channels[chn].fx:=TemFx;
                        channels[chn].fxdata:=TemFxdata;
                        channels[chn].RetrigCnt:=TemEFxdata;
                      end;
                $A: begin                             { Fine volume slide up }
                      B:=Channels[chn].vol;
                      inc(b,TemEFxdata);
                      if b>64 then b:=64;
                      channels[chn].vol:=b;
                      w:=GusVolume[b]*AmpliferVolume;
                      if channels[chn].on=1 then gussetvolume(chn+1,w);
                      channels[chn].bar:=b;
                    end;
                $B: begin                           { Fine volume slide down }
                      b:=channels[chn].vol;
                      dec(b,TemEFxdata);
                      if b>128 then b:=0;
                      channels[chn].vol:=b;
                      w:=GusVolume[b]*AmpliferVolume;
                      if channels[chn].on=1 then gussetvolume(chn+1,w);
                      channels[chn].bar:=b;
                    end;
                $C: begin                                         { Note cut }
                      channels[chn].fx:=TemFx;
                      channels[chn].fxdata:=TemFxdata;
                    end;
                $D: if TemEFxdata>0 then
                    begin
                      channels[chn].fx:=TemFx;
                      channels[chn].fxdata:=TemFxdata;
                      channels[chn].FXStart:=1+TemEFxdata;
                    end
                    else channels[chn].fx:=255;
              End;
            End;
        $F: Begin                                                { Set speed }
              channels[chn].fx:=TemFx;
              channels[chn].fxdata:=TemFxdata;
              If (TemFxdata<=32) or vblank then
                Begin
                  nspeed:=TemFxdata;
                  speed:=TemFxdata;
                End
                Else
                Begin
                  tempo:=TemFxdata;
                  if tempo<20 then tempo:=20;       { Shall I put 50 there ? }
                  IntRate:=1193182 Div (tempo*4 div 10);
                  SetTimer(IntRate);
                End;
            End
            Else
            begin
              channels[chn].fx:=255;
              channels[chn].fxdata:=0;
            end;
      End;
    End
    Else channels[chn].NoFx := 0;
End;

Procedure DoTemFx;
Var
  Chn       : Byte;
  TemFx     : Byte;
  TemFxData : Byte;
  TemEFx    : Byte;
  TemEFxData: Byte;
  Per       : Word;
  B         : Byte;
  S         : ShortInt;
  W         : Word;

Begin
  For chn:=0 to header.chns-1 do if channels[chn].on = 1 then
    begin
      if channels[chn].FXStart>0 then dec(channels[chn].FXStart);
      TemFx:=channels[chn].fx;
      TemFxdata:=channels[chn].fxdata;
      if (channels[chn].on=1) and (channels[chn].FXStart=0) then
      case TemFx of
        $0: With channels[chn] Do                                 { Arpeggio }
              begin
                case (channels[chn].ArpeggioCnt mod 3) of
                  0: gussetfreq(chn+1,
                     periods[PeroidTable[samples[sample].ftune,note]]);
                  1: gussetfreq(chn+1,
                     periods[PeroidTable[samples[sample].ftune,note+arp1]]);
                  2: gussetfreq(chn+1,
                     periods[PeroidTable[samples[sample].ftune,note+arp2]]);
                end;
                inc(ArpeggioCnt);
              end;
        $1: begin                                                  { Port up }
              per:=channels[chn].per;
              dec(per,TemFxdata);
              if per<MinPeroid then per:=MinPeroid;
              channels[chn].per:=per;
              gussetfreq(chn+1,periods[per]);
            end;
        $2: begin                                                { port down }
              per:=channels[chn].per;
              inc(per,TemFxdata);
              if per>MaxPeroid then per:=MaxPeroid;
              channels[chn].per:=per;
              gussetfreq(chn+1,periods[per]);
            end;
        $3: begin                                                  { Port to }
              if channels[chn].per < channels[chn].DesPer then
                begin
                  w:=channels[chn].DesPer;
                  per:=channels[chn].per;
                  inc(per,channels[chn].FxSl2);
                  if per>w then per:=w;
                  if per>MaxPeroid then per:=MaxPeroid;
                  if per<MinPeroid then per:=MinPeroid;
                  channels[chn].per:=per;
                  gussetfreq(chn+1,periods[per]);
                end
                else
                begin
                  w:=channels[chn].DesPer;
                  per:=channels[chn].per;
                  if per-channels[chn].FxSl2 > per then per := MinPeroid
                    else dec(per,channels[chn].FxSl2);
                  if per<w then per:=w;
                  if per<MinPeroid then per:=MinPeroid;
                  if per>MaxPeroid then per:=MaxPeroid;
                  channels[chn].per:=per;
                  GusSetFreq(chn+1,periods[per]);
                End;
            end;
        $4: begin
              TemFxdata:=channels[chn].FxVib;
              b:=TemFxdata and 15;
              s:=VibratoTable[channels[chn].VibWave,channels[chn].VibCnt];
              s:=(s * b) div 64;
              w:=channels[chn].per+s;
              if w>MaxPeroid then w:=MaxPeroid;
              if w<MinPeroid then w:=MinPeroid;
              b:=TemFxdata shr 4;
              gussetfreq(chn+1,periods[w]);
              inc(channels[chn].VibCnt,b);
              if channels[chn].VibCnt > 63 then
              channels[chn].VibCnt:=channels[chn].VibCnt - 64;
            end;
        $5: begin                                             { volume slide }
              if TemFxdata and 15 > 0 then
                begin                                           { slide down }
                  b:=channels[chn].vol;
                  if b-TemFxdata>=0 then dec(b,TemFxdata)
                                  else b:=0;
                  if b>128 then b:=0;
                  channels[chn].vol := b;
                  channels[chn].bar := b;
                  w:=GusVolume[b]*AmpliferVolume;
                  gussetvolume(chn+1,w);
                end
                else
                begin                                             { slide up }
                  b:=channels[chn].vol;
                  inc(b,TemFxdata shr 4);
                  if b>64 then b:=64;
                  channels[chn].vol:=b;
                  channels[chn].bar:=b;
                  w:=GusVolume[b]*AmpliferVolume;
                  gussetvolume(chn+1,w);
                end;
              TemFxdata:=channels[chn].FxSl2;
              if channels[chn].per<channels[chn].DesPer then
                Begin                                              { port to }
                  w:=channels[chn].DesPer;
                  per:=channels[chn].per;
                  inc(per,TemFxdata);
                  if per>w then per:=w;
                  if per>MaxPeroid then per:=MaxPeroid;
                  if per<MinPeroid then per:=MinPeroid;
                  channels[chn].per:=per;
                  gussetfreq(chn+1,periods[per]);
                end
                else
                begin
                  w:=channels[chn].DesPer;
                  per:=channels[chn].per;
                  if per-TemFxdata>per then per:=MinPeroid
                                       else dec(per,TemFxdata);
                  if per<w then per:=w;
                  if per<MinPeroid then per:=MinPeroid;
                  if per>MaxPeroid then per:=MaxPeroid;
                  channels[chn].per := per;
                  gussetfreq(chn+1,periods[per]);
                end;
            End;
        $6: begin                                           { volume slide ? }
              b:=channels[chn].FxVib and 15;
              s:=VibratoTable[channels[chn].VibWave,channels[chn].VibCnt];
              s:=(s * b) div 64;
              w:=channels[chn].per+s;
              if w>MaxPeroid then w:=MaxPeroid;
              if w<MinPeroid then w:=MinPeroid;
              b:=channels[chn].FxVib shr 4;
              gussetfreq(chn+1,periods[w]);
              inc(channels[chn].VibCnt,b);
              if channels[chn].VibCnt > 63 then
                channels[chn].VibCnt:=channels[chn].VibCnt - 64;
              if TemFxdata and 15 > 0 then
                begin                                      { exp. slide down }
                  b:=channels[chn].vol;
                  if b-TemFxdata>=0 then dec(b,TemFxdata) else b:=0;
                  if b>128 then b:=0;
                  channels[chn].vol:=b;
                  channels[chn].bar:=b;
                  w:=GusVolume[b]*AmpliferVolume;
                  gussetvolume(chn+1,w);
                end
                else
                begin                                             { slide up }
                  b:=channels[chn].vol;
                  inc(b,TemFxdata shr 4);
                  if b>64 then b:=64;
                  channels[chn].vol:=b;
                  channels[chn].bar:=b;
                  w:=GusVolume[b]*AmpliferVolume;
                  gussetvolume(chn+1,w);
               end;
            end;
        $A: begin                                             { volume slide }
              if TemFxdata and 15 > 0 then
                begin                                           { slide down }
                  b:=channels[chn].vol;
                  if b<(TemFxdata and 15) then b:=0
                    else dec(b,TemFxdata and 15);
                  if b>64 then b:=0;
                  channels[chn].vol:=b;
                  channels[chn].bar:=b;
                  w:=GusVolume[b]*AmpliferVolume;
                  gussetvolume(chn+1,w);
                end
                else
                begin                                             { slide up }
                  b:=channels[chn].vol;
                  inc(b,TemFxdata shr 4);
                  if b>64 then b:=64;
                  channels[chn].vol:=b;
                  channels[chn].bar:=b;
                  w:=GusVolume[b]*AmpliferVolume;
                  gussetvolume(chn+1,w);
                end;
            end;
        $E: begin
              TemEFx:=TemFxdata shr 4;
              TemEFxdata:=TemFxdata and 15;
              case TemEFx of
                $9: begin
                      b:=channels[chn].sample;
                      dec(channels[chn].RetrigCnt);
                      if channels[chn].RetrigCnt = 0 then
                        begin
                          gussetofs(chn+1,GusAdress[b]+2);
                          channels[chn].RetrigCnt:=TemEFxdata;
                        end;
                    end;
                $C: if TemEFxdata = 0 then gussetvolume(chn+1,0) else
                      Begin
                        Dec(TemEFxdata);
                        b:=TemFxdata;
                        b:=b and $f0;
                        b:=b or TemEFxData;
                        channels[chn].fxdata := b;
                      End;
                $D: Begin
                      W:=channels[chn].sample;
                      if channels[chn].on = 1 then
                        begin
                          channels[chn].hit := 1;
                          gussetbalance(chn+1,channels[chn].pan);
                          if (samples[w].loopend > 2) then
                            gusplayall(chn+1,8,GusAdress[w]+2,
                               GusAdress[w]+samples[w].loopstart,
                               GusAdress[w]+samples[w].loopend,
                               periods[channels[chn].per],
                               GusVolume[channels[chn].vol]*AmpliferVolume)
                          else gusplayall(chn+1,0,GusAdress[w]+2,
                               GusAdress[w],
                               GusAdress[w]+samples[w].length+1,
                               periods[channels[chn].per],
                               GusVolume[channels[chn].vol]*AmpliferVolume);
                        End;
                    End;
              End;
            End;
      End;
    End;
End;

Procedure UpDateNotes;
Var
  N: Integer;
Begin
  if CurrentPat > header.length-1 then NewPattern:=0;
  CurrentPat:=NewPattern;
  CurrentRow:=NewRow;
  if (CurrentTick>=speed) and (speed>0) then
    begin
      speed:=nspeed;
      CurrentTick:=0;
      if jump = 0 then inc(CurrentRow);
      if CurrentRow > 63 then
        begin
          inc(CurrentPat);
          CurrentRow := 0;
          if CurrentPat > header.length-1 then
            begin
              NewPattern:=0;
              CurrentPat:=0;
            end;
        end;
    end;

  For N:=0 to MaxChannels-1 do
    if channels[n].bar>1 then dec(channels[n].bar,2) else channels[n].bar:=0;

  NewPattern:=CurrentPat;
  NewRow:=CurrentRow;
  If speed > 0 then
    begin
      inc(CurrentTick);
      if CurrentTick = 1 then
        begin
          geMODNotes;
          if port[$3da] and 8 = 8 then VrtFlag:=1;
          GetTemFx;
        End;
      if port[$3da] and 8 = 8 then VrtFlag:=1;
      doTemFx;
      if port[$3da] and 8 = 8 then VrtFlag:=1;
    end;
end;

procedure ModInt; interrupt;

{                                  WARNiNG:                                  }
{   This is interrupt procedure and it cycleing BPM*4/10 Times Per Second    }

Begin
  Asm
    sti
  End;
  if port[$3da] and 8 = 8 then VrtFlag:=1;
  inc(TimeCounter2);
  updatenotes;
  OldIntTick:=IntTick;
  IntTick:=IntTick + IntRate;
  if OldIntTick > IntTick then
    begin
      inc(TimeCounter);
      asm
        cli
        pushf
        call oldint
      end;
    end
    else
    asm
      mov  al,20h
      out  20h,al                                                 { send EOI }
    end;
end;

{$S+}

procedure InitModule;
Var
  N,I : Integer;
  L   : longInt;
Begin
  VrtFlag:=0;
  PTempBuff:=@TempBuff;
  for n:=10 to 1050 do
    begin
      L:=n;
      L:=586580935 div (L*7056);
      periods[N]:=L;                                { Hz = 7093789.2/(per*2) }
    end;
  FillChar(Orders[0],255,0);
  For N:=0 To MaxChannels-1 do
    begin
      Channels[n].vol:=64;
      Channels[n].per:=0;
      Channels[n].DesPer:=0;
      Channels[n].sample:=0;
      Channels[n].pan:=7;
      Channels[n].note:=0;
    end;
  For N:=0 to 31 do with samples[n] do
    begin
      addr:=0;
      for I:=0 to sizeof(name) do name[I]:=#0;
      length:=0;
      loopstart:=0;
      loopend:=0;
      ftune:=0;
      volume:=0;
    end;
  For N:=0 to 128 do patterns[n]:=NiL;
  For N:=1 to 14 do gussetvolume(N,0);
  For N:=1 to 14 do gusstopvoice(N);
  CurrentPat:=0;
  CurrentRow:=0;
  NewPattern:=0;
  NewRow:=0;
  CurrentTick:=0;
  for N:=0 to 31 do GusPoke(N,0);
  TopAdress:=16;
  GusBank:=0;
  VBlank:=False;
  GetIntVec(8,@oldint);
End;

Procedure LoadModule(S: String; Ofst: LongInt; Debug: Boolean);
Const
  TagStrs: Array [1..20] Of String [4] = ('M.K.','FLT4','4CHN','M!K!',
                                          'FLT8','8CHN','OCTA','6CHN',
                                          '10CH','12CH','14CH','16CH',
                                          '18CH','20CH','22CH','24CH',
                                          '26CH','28CH','30CH','32CH');
Var
  F: File;

Procedure Set_Up_ModHeader;
Var
  Chn  : Integer;
Begin
  header.samples:=31;
  header.name[0]:=#20;
  move(TempBuff[0],header.name[1],20);
  header.tag:='    ';
  move(TempBuff[1080],header.tag,4);
  Chn:=MaxChannels;

       if (header.tag = TagStrs[1] ) or
          (header.tag = TagStrs[2] ) or
          (header.tag = TagStrs[3] ) or
          (header.tag = TagStrs[4] ) Then Chn:=4
  else if (header.tag = TagStrs[5] ) or
          (header.tag = TagStrs[6] ) or
          (header.tag = TagStrs[7] ) Then Chn:=8
  else if (header.tag = TagStrs[8] ) Then Chn:=6
  else if (header.tag = TagStrs[9] ) Then Chn:=10
  else if (header.tag = TagStrs[10]) Then Chn:=12
  else if (header.tag = TagStrs[11]) Then Chn:=14
  else if (header.tag = TagStrs[12]) Then Chn:=16
  else if (header.tag = TagStrs[13]) Then Chn:=18
  else if (header.tag = TagStrs[14]) Then Chn:=20
  else if (header.tag = TagStrs[15]) Then Chn:=22
  else if (header.tag = TagStrs[16]) Then Chn:=24
  else if (header.tag = TagStrs[17]) Then Chn:=26
  else if (header.tag = TagStrs[18]) Then Chn:=28
  else if (header.tag = TagStrs[19]) Then Chn:=30
  else if (header.tag = TagStrs[20]) Then Chn:=32
  else
  Begin
    header.samples:=15;
    Chn:=4;
  end;
  if chn > maxchannels then
  begin
    moderror:=1;
    ErrorExit;
  end;
  if header.samples = 15 then
  begin
    move(TempBuff[472],orders[0],128);
    seek(f,600);
    header.length:=TempBuff[470];
    header.chns:=4;
  end
  else
  begin
    header.length:=TempBuff[950];
    move(TempBuff[952],orders[0],128);
  end;
  header.chns:=chn;
End;

Procedure Mod_Sample_Info;
Var
     N: Integer;
  Maxi: Integer;
begin
  for N:=0 to 31 do
  begin
    fillchar(samples[n].name,22,0);
    samples[n].length:=0;
    samples[n].ftune:=0;
    samples[n].volume:=0;
    samples[n].loopstart:=0;
    samples[n].loopend:=0;
  end;
  for N:=1 to header.samples do
  begin
    move(TempBuff[(n-1)*30+20],samples[n].name[1],22);
    samples[n].name[23]:=#0;
    samples[n].length:=2*swap(PTempBuff^[(n-1)*15+21]);            { N*30+42 }
    samples[n].ftune:=TempBuff[(n-1)*30+44];
    samples[n].volume:=TempBuff[(n-1)*30+45];
    samples[n].loopstart:=2*swap(PTempBuff^[(n-1)*15+23]);         { N*30+46 }
    samples[n].loopend:=2*swap(PTempBuff^[(n-1)*15+24]);           { N*30+48 }
    if samples[n].loopend < 3 then
    begin
      samples[n].loopend:=0;
      samples[n].loopstart:=0;
    end;
    inc(samples[n].loopend,samples[n].loopstart);
    if samples[n].loopend > samples[n].length then
      samples[n].loopend:=samples[n].length;
  end;
end;

Procedure Read_Ptn(N: Word);
Var
  Row    : Integer;
  Note   : Integer;
  W,W2,I : Word;
  B      : Byte;
  Mchn   : Byte;
Begin
  Mchn:=Header.Chns;
  BlockRead(F,TempBuff,256*Mchn);
  For Row:=0 to 63 do
    For Note:=0 to Mchn-1 do                            { 0000000000000000 ? }
      begin
        W:=PTempBuff^[row*(2*mchn)+note*2];
        W2:=PTempBuff^[row*(2*mchn)+note*2+1];
        asm
          mov  cx,w
          and  cl,15
          xchg cl,ch
          and  cx,0fffh
          mov  i,cx
        end;
        Patterns[N]^[Row,Note].Per:=I;
        asm
          mov  al,byte ptr w2
          shr  al,4
          mov  ah,byte ptr w
          and  ah,11110000b
          or   al,ah
          xor  ah,ah
          mov  i,ax
        end;
        Patterns[N]^[row][note].Sample:=i;
        Patterns[N]^[row][note].Fx:=lo(w2) and 15;
        Patterns[N]^[row][note].FxData:=hi(w2);
        I:=patterns[n]^[row,note].per;
        W:=0;
        B:=0;
        While B=0 do
          Begin
            Inc(W,1);
            if (W>48) or (I=PeroidTable[0,w]) then B:=1;
          End;
        If W<=48 Then Patterns[N]^[Row,Note].Note:=W
                 else Patterns[N]^[Row,Note].Note:=0;
      End;
End;

Procedure Load_Patterns;
Var
  Num_Ptn : LongInt;
  N       : Word;
  M_ptn   : Integer;
Begin
  If Debug Then Write('þ Loading Patterns data into memory... ');
  Num_Ptn:=0;
  For N:=0 To 127 Do If orders[N]>Num_Ptn Then
    Begin
      if orders[n] > 127 then
        begin
          ModError:=2;
          ErrorExit;
        end
        Else num_ptn:=orders[n];
    end;
  MaxPatterns:=num_ptn+1;
  For N:=0 to MaxPatterns-1 do
    begin
      if MAXavail<256*header.Chns then
        begin
          ModError:=3;
          if n >= MaxPatterns-2 then
            for n:=0 to MaxPatterns-2 do Dispose(Patterns[N]);
          ErrorExit;
        end;
      New(Patterns[N]);
      Read_Ptn(N);
    end;
  if Debug Then Writeln('done.');
end;

Procedure Load2Gus(Len: Word);
var
  L: LongInt;
Begin
  L:=TopAdress;
  Asm
    mov  di,len
    mov  si,offset TempBuff
    @@1:
    mov  ax,word ptr l
    mov  cx,ax                                              { CX = Adress LO }
    mov  ax,word ptr l+2
    and  ax,0ffh
    mov  bx,ax                                              { BX = Adress Hi }
    mov  dx,Command2
    mov  al,43h
    out  dx,al
    mov  dx,DataLow2
    mov  ax,cx
    out  dx,ax
    mov  dx,Command2
    mov  al,44h
    out  dx,al
    mov  dx,DataHigh2
    mov  ax,bx
    out  dx,ax
    add  word ptr l,1
    adc  word ptr l+2,0
    mov  dx,DramIo2
    outsb
    dec  di
    jnz  @@1
  end;
  Inc(TopAdress,Len);
end;

Procedure Load_Sample(Num: Word);
Const
  Block = 256*MaxChannels;
Var
  W    : Word;
  Fl,L : Word;
  Len  : Longint;
Begin
  GusPoke(TopAdress,0);
  inc(TopAdress);
  guspoke(TopAdress,0);
  inc(TopAdress);
  len:=samples[num].length+TopAdress;
  if (len>GusBank+$40000) and (TopAdress<GusBank+$40000) then
    begin
      GusBank:=GusBank+$40000;
      TopAdress:=GusBank;
    end;
  samples[num].addr:=TopAdress;
  GusAdress[num]:=TopAdress;
  if samples[num].length<1 then Exit;
  FL:=(samples[num].length) div block;
  L:=(samples[num].length) mod block;
  if fl>0 then for w:=1 to FL do
    begin
      blockread(f,TempBuff,block);
      load2gus(block);                                     { Block = 4kb's ? }
    end;
  if l>0 then
    begin
      blockread(f,TempBuff,l);
      load2gus(l);
    end;
  if samples[num].loopend>2 then
    begin
      guspoke(TopAdress,guspeek(GusAdress[num]+samples[num].loopstart));
      guspoke(GusAdress[num]+samples[num].loopend+1,
              guspeek(GusAdress[num]+samples[num].loopstart));
      inc(TopAdress);
    End;
End;

Var
  I: integer;
  L: longint;
Begin
  InitModule;
  GusBank:=0;
  Assign(f,S);
  {$I-}
  Reset(F,1);
  Seek(F,Ofst);
  Blockread(f,TempBuff,1084);                                   { MOD Header }
  I:=IOresult;
  if I<>0 then
    begin
      ModError:=4;
      ErrorExit;
    end;
  set_up_modheader;
  if ModError<>0 then ErrorExit;
  mod_sample_info;
  load_patterns;
  if ModError<>0 then ErrorExit;
  if debug then write('þ Loading Samples data into memory... ');
  for i:=0 to 31 do load_sample(I);
  if debug then writeln('done.');
  close(f);
  {$i+}
end;

Procedure FreeModule;
Var
  N,I: Word;
Begin
  For N:=MaxPatterns-1 Downto 0 Do Dispose(patterns[N]);
  TopAdress:=16;
  For N:= 0 to 31 do with samples[n] do
    Begin
      Addr:=0;
      For I:=0 To SizeOf(Name) do Name[I]:=#0;
      Length:=0;
      Loopstart:=0;
      Loopend:=0;
      Ftune:=0;
      Volume:=0;
    End;
  GusBank:=0;
End;

Procedure SetTimer(Ticks: Word); Assembler;
Asm
  cli
  mov dx,43h
  mov ax,36h
  out dx,ax
  mov dx,40h
  mov ax,Ticks
  out dx,al
  mov al,ah
  out dx,al
  sti
End;

Procedure StopPlaying;
Var
  N: Integer;
Begin
  IntRate:=65535;
  SetTimer(65535);
  SetIntVec(8,@oldint);
  For N:=1 to MaxChannels do GusStopVoice(n);
End;

Procedure StartPlaying;
Var
  N: Integer;
Begin
  For N:=0 To MaxChannels-1 Do
    Begin
      Channels[N].vol:=64;
      Channels[N].per:=428;
      Channels[N].sample:=0;
      Channels[N].pan:=7;
      Channels[N].on:=1;
      Channels[N].DesPer:=428;
      Channels[N].bar:=0;
      Channels[N].fx:=255;
      Channels[N].fxdata:=0;
      Channels[N].FxSl2:=0;
      Channels[N].FxVib:=0;
      Channels[N].VibCnt:=0;
      Channels[N].VibWave:=0;
      Channels[N].note:=0;
      Channels[N].hit:=0;
      Channels[N].NoFx:= 0;
      Channels[N].FXStart:=0;
      Channels[N].arp1:= 0;
      Channels[N].arp2:= 0;
      Channels[N].ArpeggioCnt:=0;
    End;
  Speed:=6;
  NSpeed:=6;
  Tempo:=125;
  Channels[0].Pan:=7-DefaultPanning;
  Channels[1].Pan:=7+DefaultPanning;
  Channels[2].Pan:=7+DefaultPanning;
  Channels[3].Pan:=7-DefaultPanning;
  If MaxChannels>4 then for n:=4 to MaxChannels-1 do
    channels[n].pan:=channels[n-4].pan;
  If MaxChannels>8 then for n:=8 to MaxChannels-1 do
    channels[n].pan:=channels[n-8].pan;
  Jump:=0;
  IntTick:=0;
  CurrentPat:=0;
  CurrentRow:=0;
  NewPattern:=0;
  NewRow:=0;
  CurrentTick:=0;
  TimeCounter:=0;
  TimeCounter2:=0;
  Asm
    cli
  End;
  SetIntVec(8,@modint);
  IntRate:=1193182 div 50;
  SetTimer(IntRate);
  Asm
    sti
  End;
End;

End.                                                               { of UnIT }
