Pascal Mod unit

Mod unit

{$A+,B+,D-,E-,F-,G+,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{AdnMod 0.2 by Beta/Adrenalin.
 GUS only
 Thanks to:
    flap / Capacala for sending me "some" info
    Mark Feldham for PCGPE
    Mark Dixon for his GUS669 source
    Thunder for excellent info about MODs
    Tran & Joshua C. Jensen for releasing ultradox

 Greets:
    Wihannes / Nordic vision
    Solar / Hysteria
    Psyko / Acidface software
    TOP4.ZIP
    All users of Metropoli & Starport
}
unit modunit;
interface
uses dos;

const
maxchn = 8;   {max # of channels in mod. Lower this, if you run out
               of memory}
amp_vol : byte = 14;  {amplifying volume. Increasing by one doubles
                       the volume}

def_pan : byte = 4;        {default panning. 0-7}

max_per = 1000;          {Max & min period for Amiga limits}
min_per = 20;            {not implemented anymore coz of extra octaves}
Base : word = $200;       {GUS address}

mod_error : word = 0;
{0 = no error
 1 = wrong number of channels
 2 = load error
 3 = out of pattern memory
 255 = other error}

per_table : array[0..15,1..48 ] of word = (
   (856,808,762,720,678,640,604,570,538,508,480,453,
   428,404,381,360,339,320,302,285,269,254,240,226,
   214,202,190,180,170,160,151,143,135,127,120,113,
   107,101,95,90,85,80,75,71,67,63,60,56),

(850,802,757,715,674,637,601,567,535,505,477,450,{ : C-1 to B-1 Finetune +1}
425,401,379,357,337,318,300,284,268,253,239,225, { : C-2 to B-2 Finetune +1}
213,201,189,179,169,159,150,142,134,126,119,113, { : C-3 to B-3 Finetune +1}
106,100,94,89,84,79,75,71,67,83,59,56),          { : C-4 to B-4 Finetune +1}


(844,796,752,709,670,632,597,563,532,502,474,447,{ : C-1 to B-1 Finetune +2}
422,398,376,355,335,316,298,282,266,251,237,224, { : C-2 to B-2 Finetune +2}
211,199,188,177,167,158,149,141,133,125,118,112, { : C-3 to B-3 Finetune +2}
105, 99, 94, 88, 83, 79, 74, 70, 66, 62, 59, 56),{ : C-4 to B-4 Finetune +2}

(838,791,746,704,665,628,592,559,528,498,470,444,{ : C-1 to B-1 Finetune +3}
419,395,373,352,332,314,296,280,264,249,235,222, { : C-2 to B-2 Finetune +3}
209,198,187,176,166,157,148,140,132,125,118,111, { : C-3 to B-3 Finetune +3}
104, 99, 93, 88, 83, 78, 74, 70, 66, 62, 59, 55),{ : C-4 to B-4 Finetune +3}

(832,785,741,699,660,623,588,555,524,495,467,441,{ : C-1 to B-1 Finetune +4}
416,392,370,350,330,312,294,278,262,247,233,220, { : C-2 to B-2 Finetune +4}
208,196,185,175,165,156,147,139,131,124,117,110, { : C-3 to B-3 Finetune +4}
104, 98, 92, 87, 82, 78, 73, 69, 65, 62, 58, 55), {; C-4 to B-4 Finetune +4}

(826,779,736,694,655,619,584,551,520,491,463,437,{ : C-1 to B-1 Finetune +5}
413,390,368,347,328,309,292,276,260,245,232,219, { : C-2 to B-2 Finetune +5}
206,195,184,174,164,155,146,138,130,123,116,109, { : C-3 to B-3 Finetune +5}
103, 97, 92, 87, 82, 77, 73, 69, 65, 61, 58, 54),{ ; C-4 to B-4 Finetune +5}

(820,774,730,689,651,614,580,547,516,487,460,434,{ : C-1 to B-1 Finetune +6}
410,387,365,345,325,307,290,274,258,244,230,217, { : C-2 to B-2 Finetune +6}
205,193,183,172,163,154,145,137,129,122,115,109, { : C-3 to B-3 Finetune +6}
102, 96, 91, 86, 81, 77, 72, 68, 64, 61, 57, 54),{ : C-4 to B-4 Finetune +6}

(814,768,725,684,646,610,575,543,513,484,457,431,{ : C-1 to B-1 Finetune +7}
407,384,363,342,323,305,288,272,256,242,228,216, { : C-2 to B-2 Finetune +7}
204,192,181,171,161,152,144,136,128,121,114,108, { : C-3 to B-3 Finetune +7}
102, 96, 90, 85, 80, 76, 72, 68, 64, 60, 57, 54),{ : C-4 to B-4 Finetune +7}

(907,856,808,762,720,678,640,604,570,538,504,480,{ : C-1 to B-1 Finetune -8 }
453,428,404,381,360,339,320,302,285,269,254,240, { : C-2 to B-2 Finetune -8  }
226,214,202,190,180,170,160,151,143,135,127,120, { : C-3 to B-3 Finetune -8 }
113,107,101, 95, 90, 85, 80, 75, 71, 67, 63, 60),{ : C-4 to B-4 Finetune -8}


(900,850,802,757,715,675,636,601,567,535,505,477,{ : C-1 to B-1 Finetune -7 }
450,425,401,379,357,337,318,300,284,268,253,238, { : C-2 to B-2 Finetune -7  }
225,212,200,189,179,169,159,150,142,134,126,119, { : C-3 to B-3 Finetune -7 }
112,106,100, 94, 89, 84, 79, 75, 71, 67, 63, 59),{ : C-4 to B-4 Finetune -7}

(894,844,796,752,709,670,632,597,563,532,502,474,{ : C-1 to B-1 Finetune -6 }
447,422,398,376,355,335,316,298,282,266,251,237, { : C-2 to B-2 Finetune -6  }
223,211,199,188,177,167,158,149,141,133,125,118, { : C-3 to B-3 Finetune -6 }
111,105, 99, 94, 88, 83, 79, 74, 70, 66, 62, 59),{ : C-4 to B-4 Finetune -6}

(887,838,791,746,704,665,628,592,559,528,498,470,{ : C-1 to B-1 Finetune -5 }
444,419,395,373,352,332,314,296,280,264,249,235, { : C-2 to B-2 Finetune -5  }
222,209,198,187,176,166,157,148,140,132,125,118, { : C-3 to B-3 Finetune -5  }
111,104, 99, 93, 88, 83, 78, 74, 70, 66, 62, 59),{ : C-4 to B-4 Finetune -5}

(881,832,785,741,699,660,623,588,555,524,494,467,{ : C-1 to B-1 Finetune -4 }
441,416,392,370,350,330,312,294,278,262,247,233, { : C-2 to B-2 Finetune -4  }
220,208,196,185,175,165,156,147,139,131,123,117, { : C-3 to B-3 Finetune -4 }
110,104, 98, 92, 87, 82, 78, 73, 69, 65, 61, 58),{   C-4 to H-4 Finetune -4}

(875,826,779,736,694,655,619,584,551,520,491,463,{ : C-1 to B-1 Finetune -3 }
437,413,390,368,347,338,309,292,276,260,245,232, { : C-2 to B-2 Finetune -3  }
219,206,195,184,174,164,155,146,138,130,123,116, { : C-3 to B-3 Finetune -3 }
109,103, 97, 92, 87, 82, 77, 73, 69, 65, 61, 58),{   C-4 to H-4 Finetune -3}

(868,820,774,730,689,651,614,580,547,516,487,460,{ : C-1 to B-1 Finetune -2 }
434,410,387,365,345,325,307,290,274,258,244,230, { : C-2 to B-2 Finetune -2  }
217,205,193,183,172,163,154,145,137,129,122,115, { : C-3 to B-3 Finetune -2 }
108,102, 96, 91, 86, 81, 77, 72, 68, 64, 61, 57),{   C-4 to H-4 Finetune -2}

(862,814,768,725,684,646,610,575,543,513,484,457,{ : C-1 to B-1 Finetune -1 }
431,407,384,363,342,323,305,288,272,256,242,228, { : C-2 to B-2 Finetune -1  }
216,203,192,181,171,161,152,144,136,128,121,114, { : C-3 to B-3 Finetune -1}
108,101, 96, 90, 85, 80, 76, 72, 68, 64, 60, 57));{  C-4 to H-4 Finetune -1}

gusvol : array[0..64] of word =     {volume table}

  (0,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);

vib_tbl : array[0..2,0..63] of shortint =
((0,6,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,6,
0,-6,-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,-6),
(-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,-9,-7,-5,-3,-1,
1,3,5,7,9,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
  t_memarray = array[0..2000] of word;
  t_channel = record
                Vol : byte;       {current volume 0-64}
                note : byte;      {current note 1(C-1) to 48(B-4)}
                Per,dper : word;  {period & dest. period for tone portamentos}
                Sample : byte;    {current sample}
                Pan : byte;       {panning}
                fx,fxdata : byte;
                fx_sl2,fx_vib : byte;  {slide to & vibrato fx-data}
                vib_wave : byte;    {vibrato waveform}
                vib_cnt : byte;     {vibrato counter}
                trig_cnt : byte;    {retrig counter}
                arp1,arp2,         {arpeggio params}
                arp_cnt : byte;     {arpeggio counter}
                start_fx : byte;    {tick to start do_fx for channel}
                on : byte;        {0 = channel is muted}
                bar : byte;       {volume bar}
                hit : byte;
                no_fx : byte  {1 = do not get new fx}
              end;
  t_sample = record
               Name : array[1..40] of char;
               Addr : longint;  {address in GUS mem}
               Length : word;
               LoopStart,
               LoopEnd : word;
               ftune : byte;
               Volume : byte;
             end;
  t_note = record
             per : word;
             note,
             sample,
             fx,
             fxdata : byte;
           end;
  t_row = array[0..maxchn-1] of t_note;
  t_pattern = array[0..63] of t_row;
  p_pattern = ^t_pattern;

  mod_header = record
                 name : string[20];
                 Length : byte;
                 tag : array[0..3] of char;  {M.K.}
                 chns : byte;  {4..12}
                 samples : byte; {15 / 31}
               end;

var
  gus_addr : array[0..32] of longint;
  periods : array[0..1100] of word;
  channels : array[0..maxchn-1] of t_channel;
  samples : array[0..32] of t_sample;
  patterns : array[0..128] of p_pattern;
  orders : array[0..255] of byte;   {order list}
  max_ptn : word;                 {# patterns in mod}
  cur_ptn,cur_row,cur_tick : byte;
  new_ptn,new_row,jump : byte;   {used in jumps}
  speed,nspeed,tempo : byte;
  vblank : boolean;               {true = do not use bpm tempos}

  header : mod_header;
  top_addr : longint;         {Next free address in GUS mem}

  time_counter : longint;      {For syncing with demos. Increments
                                every 1/18.2 seconds}
  time_counter2 : longint;    {Increments every tick}
  vrt_flag : byte;   {if 1 then vertical retrace happened}

Procedure GUSDelay;
Function VoicePos( V : Byte) : Longint;
Function  GUSPeek(Loc : Longint) : Byte;
Procedure GUSPoke(Loc : Longint; B : Byte);
Function GUSProbe(adr : word) : Boolean;
Procedure GUSFind;
Function  GUSFindMem : Longint;
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 start_playing;
procedure stop_playing;
procedure set_timer(ticks : word);
procedure init_mod;
procedure free_mod;
procedure load_mod(s : string;debug : boolean);


implementation

var
  oldint : procedure;
  int_tick,o_int_tick : word;
  int_rate : word;

  gus_bank : longint;

  misc_buf : array[0..5000] of byte;    {buffer used while loading mod}
  misc_buf2 : ^t_memarray;      {points to misc_buf}

{$i gus.inc}

{$s-}
procedure get_notes;
var
  chn : byte;
  ptn : byte;
  org_sam,sam,note : byte;
  st_ofs : longint;
  per,dper,vol,freq : word;
  _fx,_fxdata : byte;
  mute: byte;
  _ptn : p_pattern;

procedure prefx;
var
w : word;
_efxdata : byte;
begin
  case _fx of
    9 : begin
          w := _fxdata*$100;
          st_ofs := w;
          channels[chn].no_fx := 1;
          channels[chn].fx := _fx;
          channels[chn].fxdata := _fxdata;
        end;
    $c : begin
           if _fxdata > 64 then _fxdata := 64;
           vol := _fxdata;
         end;
    $e : begin
           _efxdata := _fxdata and 15;
           case _fxdata shr 4 of
           4 : begin
                 channels[chn].fx := _fx;
                 channels[chn].fxdata := _fxdata;
                 if _efxdata and 3 < 3 then channels[chn].vib_wave := _efxdata
                 else channels[chn].vib_wave := 0 or (_efxdata and 4);
               end;
               $c : if _efxdata and 15 = 0 then begin
                 mute := 1;
                 gusstopvoice(chn+1);
               end;
               $d : if _efxdata > 0 then mute := 2
                    else mute := 0;
           end;
    end;
  end;
end;

begin
  ptn := orders[cur_ptn];
  for chn := 0 to header.chns-1 do begin
    if channels[chn].fx = 0 then begin
      sam := channels[chn].sample;
      per := per_table[samples[sam].ftune,
                       channels[chn].note];
      gussetfreq(chn+1,periods[per]);
    end;
    channels[chn].hit := 0;
    if ((patterns[ptn]^[cur_row,chn].per > 0) or
    (patterns[ptn]^[cur_row,chn].sample > 0)) then begin
      mute := 1;
      vol := channels[chn].vol;
      per := channels[chn].per;
      note := channels[chn].note;
      freq := periods[channels[chn].per];
      _fx := patterns[ptn]^[cur_row,chn].fx;
      _fxdata := patterns[ptn]^[cur_row,chn].fxdata;
      org_sam := patterns[ptn]^[cur_row,chn].sample;
      channels[chn].start_fx := 0;
      channels[chn].trig_cnt := 0;
      if org_sam = 0 then begin
        sam := channels[chn].sample;
      end
      else begin
        sam := org_sam;
      end;
      if (_fx = 3) or (_fx = 5) then begin {port to/port to&vol slide}
        mute := 1; {dont restart sample}
        if patterns[ptn]^[cur_row,chn].note > 0 then begin
          note := patterns[ptn]^[cur_row,chn].note;
          dper := per_table[samples[sam].ftune,note];
          if dper > max_per then dper := max_per;
          if dper < min_per then dper := min_per;
          channels[chn].dper := dper;
        end;
      end
      else if patterns[ptn]^[cur_row,chn].per > 0 then begin
        if patterns[ptn]^[cur_row,chn].note > 0 then begin
          note := patterns[ptn]^[cur_row,chn].note;
          per := per_table[samples[sam].ftune,note];
        end
        else if patterns[ptn]^[cur_row,chn].per > 0 then
          per := patterns[ptn]^[cur_row,chn].per;

        if per > max_per then per := max_per;
        if per < min_per then per := min_per;
        channels[chn].dper := per;
        channels[chn].per := per;
        freq := periods[per];
        mute := 0;
      end;
      if org_sam > 0 then begin {should I reset volume}
        vol := samples[sam].volume;
        if channels[chn].sample <> org_sam then mute := 0;
      end;
      if samples[sam].length > 0 then st_ofs := 2;
        {coz first 2 bytes = amiga loopinfo, discard them}
      channels[chn].no_fx := 0;
      prefx;
      channels[chn].vol := vol;
      channels[chn].note := note;
      if channels[chn].vib_wave and 4 = 0 then channels[chn].vib_cnt := 0;
      channels[chn].sample := sam;
      channels[chn].bar := channels[chn].vol;
      vol := gusvol[vol]*amp_vol;
      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,gus_addr[sam]+st_ofs,
                               gus_addr[sam]+samples[sam].loopstart,
                               gus_addr[sam]+samples[sam].loopend,freq,vol)
        else gusplayall(chn+1,0,gus_addr[sam]+st_ofs,
                             gus_addr[sam]+st_ofs,
                             gus_addr[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 get_fx;
var
chn,ptn : byte;
_fx,_fxdata : byte;
_efx,_efxdata : byte;
per : word;
b : byte;
w : word;

begin
  ptn := orders[cur_ptn];
  new_ptn := cur_ptn;
  new_row := cur_row;
  jump := 0;
  for chn := 0 to header.chns-1 do
  if channels[chn].no_fx = 0 then begin
    channels[chn].start_fx := 0;
    channels[chn].fx := 255;
    _fx := patterns[ptn]^[cur_row,chn].fx;
    _fxdata := patterns[ptn]^[cur_row,chn].fxdata;
    case _fx of
      0 : if _fxdata > 0 then begin {Arpeggio}
            channels[chn].fx := _fx;
            channels[chn].fxdata := _fxdata;
            channels[chn].arp1 := _fxdata shr 4;
            channels[chn].arp2 := _fxdata and 15;
            channels[chn].arp_cnt := 0;
          end;
      1 : begin  {port up}
            channels[chn].fx := _fx;
            channels[chn].fxdata := _fxdata;
            channels[chn].start_fx := 2;
          end;
      2 : begin  {port down}
            channels[chn].fx := _fx;
            channels[chn].fxdata := _fxdata;
            channels[chn].start_fx := 2;
          end;
      3 : begin   {port to}
            channels[chn].fx := _fx;
            if _fxdata > 0 then begin
              channels[chn].fxdata := _fxdata;
              channels[chn].fx_sl2 := _fxdata;
            end
            else channels[chn].fxdata := channels[chn].fx_sl2;
            channels[chn].start_fx := 2;
          end;
      4 : begin    {vibrato}
            channels[chn].fx := _fx;
            b := _fxdata and 15;
            if b = 0 then b := channels[chn].fx_vib and 15;
            w := b;
            b := _fxdata shr 4;
            if b = 0 then b := channels[chn].fx_vib shr 4;
            w := w or (b shl 4);
            b := w;
            channels[chn].fxdata := b;
            channels[chn].fx_vib := b;
          end;
      5 : begin    {port to & vol slide}
             channels[chn].fx := _fx;
             if _fxdata and 15 > 0 then
               _fxdata := _fxdata and 15; {if both ways, then slide down}
             channels[chn].fxdata := _fxdata;
          end;
      6 : begin      {Vibrato & vol slide}
            channels[chn].fx := _fx;
            channels[chn].fxdata := _fxdata;
          end;
      7 : begin      {Tremolo}
            channels[chn].fx := _fx;
            channels[chn].fxdata := _fxdata;
          end;
      8 : begin       {Set panning}
            channels[chn].fx := _fx;
            channels[chn].fxdata := _fxdata;
          end;
      9 : begin   {set sample offset}
            channels[chn].fx := _fx;
            channels[chn].fxdata := _fxdata;
            w := _fxdata * 256;
            b := channels[chn].sample;
            if channels[chn].on = 1 then gussetofs(chn+1,gus_addr[b]+w);
          end;
      $a : begin   {volume slide}
             channels[chn].fx := _fx;
             if _fxdata and 15 > 0 then
               _fxdata := _fxdata and 15; {if both ways, then slide up}
             channels[chn].fxdata := _fxdata;
             channels[chn].start_fx := 2;
           end;
      $b : begin   {position jump}
             if _fxdata < max_ptn then begin
               new_ptn := _fxdata;
               new_row := 0;
               jump := 1;
             end;
           end;
      $c : begin  {Set volume}
             if _fxdata > 64 then _fxdata := 64;
             channels[chn].fx := _fx;
             channels[chn].fxdata := _fxdata;
             channels[chn].vol := _fxdata;
             channels[chn].bar := _fxdata;
             w := gusvol[_fxdata {* main_vol}]*amp_vol;
             if channels[chn].on = 1 then gussetvolume(chn+1,w);
           end;
      $d : begin   {break pattern}
             new_ptn := cur_ptn;
             inc(new_ptn);
             new_row := ((_fxdata and $f0) shr 4)*10+_fxdata and 15;
             jump := 1;
           end;
      $e : begin        {extended effect}
             channels[chn].fx := _fx;
             channels[chn].fxdata := _fxdata;
             _efx := _fxdata shr 4;
             _efxdata := _fxdata and 15;
             case _efx of
               1 : begin
                     per := channels[chn].per;
                     inc(per,_efxdata);
                     if per > max_per then per := max_per;
                     channels[chn].per := per;
                     w := periods[channels[chn].per];
                     gussetfreq(chn+1,w);
                   end;
               2 : begin
                     per := channels[chn].per;
                     dec(per,_efxdata);
                     if per < min_per then per := min_per;
                     channels[chn].per := per;
                     w := periods[channels[chn].per];
                     gussetfreq(chn+1,w);
                   end;
               4 : begin {set vibrato waveform}
                     channels[chn].vib_wave := _efxdata;
                   end;
               5 : begin  {set finetune}
                     samples[channels[chn].sample].ftune := _efxdata;
                   end;
               8 : begin  {set mtm-pan}
                     channels[chn].pan := _efxdata;
                     gussetbalance(chn+1,_efxdata);
                   end;
               9 : if _efxdata > 0 then begin   {retrigger}
                     channels[chn].fx := _fx;
                     channels[chn].fxdata := _fxdata;
                     channels[chn].trig_cnt := _efxdata;
                   end;
               $a : begin   {fine vol slide up}
                      b := channels[chn].vol;
                      inc(b,_efxdata);
                      if b > 64 then b := 64;
                      channels[chn].vol := b;
                      w := gusvol[b{*main_vol}]*amp_vol;
                      if channels[chn].on = 1 then gussetvolume(chn+1,w);
                      channels[chn].bar := b;
                    end;
               $b : begin   {fine vol slide down}
                      b := channels[chn].vol;
                      dec(b,_efxdata);
                      if b > 128 then b := 0;
                      channels[chn].vol := b;
                      w := gusvol[b{*main_vol}]*amp_vol;
                      if channels[chn].on = 1 then gussetvolume(chn+1,w);
                      channels[chn].bar := b;
                    end;
               $c : begin  {cut note}
                      channels[chn].fx := _fx;
                      channels[chn].fxdata := _fxdata;
                    end;
               $d : if _efxdata > 0 then begin
                      channels[chn].fx := _fx;
                      channels[chn].fxdata := _fxdata;
                      channels[chn].start_fx := 1+_efxdata;
                    end
                    else channels[chn].fx := 255;
             end;
           end;
      $f : begin  {set speed}
            channels[chn].fx := _fx;
            channels[chn].fxdata := _fxdata;
             if (_fxdata <= 32) or vblank then begin    {SPEED not tempo}
               nspeed := _fxdata;
               speed := _fxdata;
             end
             else begin
               tempo := _fxdata;
               if tempo < 50 then tempo := 50;
               int_rate := 1193182 div (tempo*4 div 10);
               set_timer(int_rate);
             end;
           end
      else begin
        channels[chn].fx := 255;
        channels[chn].fxdata := 0;
      end;
    end;
  end
  else channels[chn].no_fx := 0;
end;

procedure do_fx;
var
chn : byte;
_fx,_fxdata : byte;
_efx,_efxdata : 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].start_fx > 0 then dec(channels[chn].start_fx);
    _fx := channels[chn].fx;
    _fxdata := channels[chn].fxdata;
    if (channels[chn].on = 1) and (channels[chn].start_fx = 0)
    then case _fx of
      0 : with channels[chn] do begin
            case channels[chn].arp_cnt mod 3 of
              0 : gussetfreq(chn+1,
                    periods[per_table[samples[sample].ftune,note]]);
              1 : gussetfreq(chn+1,
                    periods[per_table[samples[sample].ftune,note+arp1]]);
              2 : gussetfreq(chn+1,
                    periods[per_table[samples[sample].ftune,note+arp2]]);
            end;
            inc(arp_cnt);
          end;
      1 : begin   {port up}
            per := channels[chn].per;
            dec(per,_fxdata);
            if per < min_per then per := min_per;
            channels[chn].per := per;
            gussetfreq(chn+1,periods[per]);
          end;
      2 : begin  {port down}
            per := channels[chn].per;
            inc(per,_fxdata);
            if per > max_per then per := max_per;
            channels[chn].per := per;
            gussetfreq(chn+1,periods[per]);
          end;
      3 : begin   {Port to}
            if channels[chn].per < channels[chn].dper then begin
              w := channels[chn].dper;
              per := channels[chn].per;
              inc(per,channels[chn].fx_sl2);
              if per > w then per := w;
              if per > max_per then per := max_per;
              if per < min_per then per := min_per;
              channels[chn].per := per;
              gussetfreq(chn+1,periods[per]);
            end
            else begin
              w := channels[chn].dper;
              per := channels[chn].per;
              if per-channels[chn].fx_sl2 > per then per := min_per
              else dec(per,channels[chn].fx_sl2);
              if per < w then per := w;
              if per < min_per then per := min_per;
              if per > max_per then per := max_per;
              channels[chn].per := per;
              gussetfreq(chn+1,periods[per]);
            end;
          end;
      4 : begin
            _fxdata := channels[chn].fx_vib;
            b := _fxdata and 15;
            s := vib_tbl[channels[chn].vib_wave,channels[chn].vib_cnt];
            s := (s * b) div 64;
            w := channels[chn].per+s;
            if w > max_per then w := max_per;
            if w < min_per then w := min_per;
            b := _fxdata shr 4;
            gussetfreq(chn+1,periods[w]);
            inc(channels[chn].vib_cnt,b);
            if channels[chn].vib_cnt > 63 then
              channels[chn].vib_cnt := channels[chn].vib_cnt - 64;
          end;
      5 : begin
            {volume slide}
            if _fxdata and 15 > 0 then begin  {slide down}
              b := channels[chn].vol;
              if b-_fxdata >= 0 then dec(b,_fxdata)
              else b := 0;
              if b > 128 then b := 0;
              channels[chn].vol := b;
              channels[chn].bar := b;
              w := gusvol[b {* main_vol}]*amp_vol;
              gussetvolume(chn+1,w);
            end
            else begin   {slide up}
              b := channels[chn].vol;
              inc(b,_fxdata shr 4);
              if b > 64 then b := 64;
              channels[chn].vol := b;
              channels[chn].bar := b;
              w := gusvol[b {* main_vol}]*amp_vol;
              gussetvolume(chn+1,w);
            end;
            _fxdata := channels[chn].fx_sl2;
            if channels[chn].per < channels[chn].dper then begin {port to}
              w := channels[chn].dper;
              per := channels[chn].per;
              inc(per,_fxdata);
              if per > w then per := w;
              if per > max_per then per := max_per;
              if per < min_per then per := min_per;
              channels[chn].per := per;
              gussetfreq(chn+1,periods[per]);
            end
            else begin
              w := channels[chn].dper;
              per := channels[chn].per;
              if per-_fxdata > per then per := min_per
              else dec(per,_fxdata);
              if per < w then per := w;
              if per < min_per then per := min_per;
              if per > max_per then per := max_per;
              channels[chn].per := per;
              gussetfreq(chn+1,periods[per]);
            end;
          end;
      6 : begin
            begin
              b := channels[chn].fx_vib and 15;
              s := vib_tbl[channels[chn].vib_wave,channels[chn].vib_cnt];
              s := (s * b) div 64;
              w := channels[chn].per+s;
              if w > max_per then w := max_per;
              if w < min_per then w := min_per;
              b := channels[chn].fx_vib shr 4;
              gussetfreq(chn+1,periods[w]);
              inc(channels[chn].vib_cnt,b);
              if channels[chn].vib_cnt > 63 then
                channels[chn].vib_cnt := channels[chn].vib_cnt - 64;
            end;
            {volume slide}
            if _fxdata and 15 > 0 then begin  {slide down}
              b := channels[chn].vol;
              if b-_fxdata >= 0 then dec(b,_fxdata)
              else b := 0;
              if b > 128 then b := 0;
              channels[chn].vol := b;
              channels[chn].bar := b;
              w := gusvol[b {* main_vol}]*amp_vol;
              gussetvolume(chn+1,w);
            end
            else begin   {slide up}
              b := channels[chn].vol;
              inc(b,_fxdata shr 4);
              if b > 64 then b := 64;
              channels[chn].vol := b;
              channels[chn].bar := b;
              w := gusvol[b {* main_vol}]*amp_vol;
              gussetvolume(chn+1,w);
            end;
          end;
      $a : begin  {volume slide}
             if _fxdata and 15 > 0 then begin  {slide down}
               b := channels[chn].vol;
               if b < (_fxdata and 15) then b := 0
               else dec(b,_fxdata and 15);
               if b > 64 then b := 0;
               channels[chn].vol := b;
               channels[chn].bar := b;
               w := gusvol[b]*amp_vol;
               gussetvolume(chn+1,w);
             end
             else begin   {slide up}
               b := channels[chn].vol;
               inc(b,_fxdata shr 4);
               if b > 64 then b := 64;
               channels[chn].vol := b;
               channels[chn].bar := b;
               w := gusvol[b {* main_vol}]*amp_vol;
               gussetvolume(chn+1,w);
             end;
           end;
      $e : begin
             _efx := _fxdata shr 4;
             _efxdata := _fxdata and 15;
             case _efx of
               9 : begin
                     b := channels[chn].sample;
                     dec(channels[chn].trig_cnt);
                     if channels[chn].trig_cnt = 0 then begin
                       gussetofs(chn+1,gus_addr[b]+2);
                       channels[chn].trig_cnt := _efxdata;
                     end;
                   end;
               $c : if _efxdata = 0 then begin
                      gussetvolume(chn+1,0);
                    end
                    else begin
                      dec(_efxdata);
                      b := _fxdata;
                      b := b and $f0;
                      b := b or _efxdata;
                      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,gus_addr[w]+2,
                               gus_addr[w]+samples[w].loopstart,
                               gus_addr[w]+samples[w].loopend,
                               periods[channels[chn].per],
                               gusvol[channels[chn].vol]*amp_vol)
                        else  gusplayall(chn+1,0,gus_addr[w]+2,
                             gus_addr[w],
                             gus_addr[w]+samples[w].length+1,
                             periods[channels[chn].per],
                             gusvol[channels[chn].vol]*amp_vol);
                      end;
                    end;
             end;
           end;
    end;
  end;
end;

procedure updatenotes;
var
n : integer;
begin
  if cur_ptn > header.length-1 then new_ptn := 0;
  cur_ptn := new_ptn;
  cur_row := new_row;
  if (cur_tick >= speed) and (speed > 0) then begin
    speed := nspeed;
    cur_tick := 0;
    if jump = 0 then inc(cur_row);
    if cur_row > 63 then begin
      inc(cur_ptn);
      cur_row := 0;
      if cur_ptn > header.length-1 then begin
        new_ptn := 0;
        cur_ptn := 0;
      end;
    end;
  end;
  for n := 0 to maxchn-1 do begin
    if channels[n].bar > 1 then dec(channels[n].bar,2)
    else channels[n].bar := 0;
  end;
  new_ptn := cur_ptn;
  new_row := cur_row;
  if speed > 0 then begin
    inc(cur_tick);
    if cur_tick = 1 then begin
      get_notes;
      if port[$3da] and 8 = 8 then vrt_flag := 1;
      get_fx;
    end;
    if port[$3da] and 8 = 8 then vrt_flag := 1;
    do_fx;
    if port[$3da] and 8 = 8 then vrt_flag := 1;
  end;
end;

procedure modint; interrupt;
{This happens bpm*4/10 times per second (50hz if vblank).}
begin
  asm sti end;
  if port[$3da] and 8 = 8 then vrt_flag := 1;
  inc(time_counter2);
  updatenotes;
  o_int_tick := int_tick;
  int_tick := int_tick + int_rate;
  if o_int_tick > int_tick then begin
    inc(time_counter);
    asm
      cli
      pushf
      call oldint
    end;
  end
  else
    asm
      mov  al,20h
      out  20h,al  {send EOI}
    end;
end;

{$s+}
procedure load_MOD(s : string;debug : boolean);
var
f : file;

procedure set_up_modheader;
var
chn : integer;
begin
  header.samples := 31;
  header.name[0] := #20;
  move(misc_buf[0],header.name[1],20);
  header.tag := '    ';
  move(misc_buf[1080],header.tag,4);
  chn := maxchn;
  if header.tag = 'M.K.' then chn := 4
  else if header.tag = 'M!K!' then chn := 4
  else if header.tag = '6CHN' then chn := 6
  else if header.tag = '8CHN' then chn := 8
  else if header.tag = '12CH' then chn := 12
  else begin
    header.samples := 15;
    chn := 4;
  end;
  if chn > maxchn then begin
    mod_error := 1;
    exit;
  end;
  if header.samples = 15 then begin
    move(misc_buf[472],orders[0],128);
    seek(f,600);
    header.length := misc_buf[470];
    header.chns := 4;
  end else begin
    header.length := misc_buf[950];
    move(misc_buf[952],orders[0],128);
    if debug then writeln('Tag: ',header.tag);
  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(misc_buf[(n-1)*30+20],samples[n].name[1],22);
    samples[n].name[23] := #0;
    samples[n].length := 2*swap(misc_buf2^[(n-1)*15+21]); {n*30+42}
    samples[n].ftune := misc_buf[(n-1)*30+44];
    samples[n].volume := misc_buf[(n-1)*30+45];
    samples[n].loopstart := 2*swap(misc_buf2^[(n-1)*15+23]);  {n*30+46}
    samples[n].loopend := 2*swap(misc_buf2^[(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,note : integer;
w,w2,i : word;
b : byte;
mchn : byte;

begin
  mchn := header.chns;
  blockread(f,misc_buf,256*mchn);
  for row := 0 to 63 do
    for note := 0 to mchn-1 do begin
      w := misc_buf2^[row*(2*mchn)+note*2];
      w2 := misc_buf2^[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);
        if (w > 48) or (i = per_table[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');
  num_ptn := 0;
  for n := 0 to 127 do if orders[n] > num_ptn then begin
    if orders[n] > 127 then begin
      mod_error := 2;
      exit;
    end else num_ptn := orders[n];
  end;
  max_ptn := num_ptn+1;
  for n := 0 to max_ptn-1 do begin
    if maxavail < 256*header.chns then begin
      mod_error := 3;          {if error then release memory allocated}
      if n >= max_ptn-2 then for n := 0 to max_ptn-2 do dispose(patterns[n]);
      exit;
    end;
    if debug then write('.');
    new(patterns[n]);
    read_ptn(n);
  end;
  if debug then writeln;
end;

procedure load2gus(len : word);
var
{n : word;
addlo,addhi : word;}
l : longint;

begin
  l := top_addr;
  asm
    mov  di,len
    mov  si,offset misc_buf
@@1:
    {AddLo := L AND $FFFF;
    AddHi := longint(L and $ff0000) shr 16;}
      mov  ax,word ptr l
      mov  cx,ax      {cx=addlo}
      mov  ax,word ptr l+2
      and  ax,0ffh
      mov  bx,ax   {bx=addhi}

      mov  dx,command   {Port [command] := $43;}
      mov  al,43h
      out  dx,al

      mov  dx,data_low  {Portw[data_low] := AddLo;}
      mov  ax,cx
      out  dx,ax

      mov  dx,command    {Port [command] := $44;}
      mov  al,44h
      out  dx,al

      mov  dx,data_high
      mov  ax,bx
      out  dx,ax        {Port [data_high] := AddHi;}

      add  word ptr l,1     {inc(l,1);}
      adc  word ptr l+2,0

    mov  dx,dram_io      {Port [dram_io] := misc_buf[n];}
    outsb

    dec  di
    jnz  @@1
  end;
  inc(top_addr,len);
end;

procedure load_sample(num : word);
const
block = 4096;
var
w : word;
fl,l : word;
len : longint;

begin
  if debug then write('.');
  guspoke(top_addr,0);
  inc(top_addr);
  guspoke(top_addr,0);
  inc(top_addr);
  len := samples[num].length+top_addr;
  if (len > gus_bank+$40000) and (top_addr < gus_bank+$40000) then begin
    gus_bank := gus_bank+$40000;
    top_addr := gus_bank;
  end;

  samples[num].addr := top_addr;
  gus_addr[num] := top_addr;
  if samples[num].length < 1 then exit;
  {blockread(f,w,2);}  {read amiga repeat bytes}
  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,misc_buf,block);
    load2gus(block);       {load in 4kb blocks}
  end;
  if l > 0 then begin
    blockread(f,misc_buf,l);
    load2gus(l);           {load remainder}
  end;
  if samples[num].loopend > 2 then begin
    guspoke(top_addr,guspeek(gus_addr[num]+samples[num].loopstart));
    guspoke(gus_addr[num]+samples[num].loopend+1,
            guspeek(gus_addr[num]+samples[num].loopstart));
    inc(top_addr);
  end;
end;

var
i : integer;
l : longint;

begin
  gus_bank := 0;
  assign(f,s);
  {$i-}
  reset(f,1);
  blockread(f,misc_buf,1084);  {read module header}
  i := ioresult;
  if i <> 0 then begin
    mod_error := 2;
    exit;
  end;
  set_up_modheader;
  if mod_error <> 0 then exit;
  mod_sample_info;
  load_patterns;
  if mod_error <> 0 then exit;
  if debug then write('Loading samples');
  for i := 0 to 31 do load_sample(i);
  if debug then writeln;
  close(f);
  {$i+}
end;

procedure free_mod;
var
n,i : word;
begin
  for n := max_ptn-1 downto 0 do dispose(patterns[n]);
  top_addr := 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;
  gus_bank := 0;
end;

procedure init_mod;
var
n,i : integer;
l : longint;

begin
  vrt_flag := 0;
  misc_buf2 := @misc_buf;
  for n := 10 to 1050 do begin
       {gusperiod:=586580935 div (amigaperiod * (divisor div 100 shl 4))}
       {divisor = 44100}
    {l := 7093789 div (n*2);
    l := l div 40;}
    l := n;
    l := 586580935 div (l * 7056);
    periods[n] := l;
    {hz = 7093789.2/(per*2)}
  end;
  for n := 0 to 255 do orders[n] := 0;
  for n := 0 to maxchn-1 do begin
    channels[n].vol := 64;
    channels[n].per := 0;
    channels[n].dper := 0;
    channels[n].sample := 0;
    channels[n].pan := 7;  {middle}
    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);
  cur_ptn := 0;
  cur_row := 0;
  new_ptn := 0;
  new_row := 0;
  cur_tick := 0;
  for n := 0 to 31 do guspoke(n,0);
  top_addr := 16;
  gus_bank := 0;
  vblank := false;
  getintvec(8,@oldint);
end;

procedure set_timer(ticks : word);
begin
  asm cli end;
  port[$43] := $36;
  port[$40] := lo(ticks);
  port[$40] := hi(ticks);
  asm sti end;
end;

procedure stop_playing;
var
n : integer;
begin
  int_rate := 65535;
  set_timer(65535);
  setintvec(8,@oldint);
  for n := 1 to maxchn do GusStopVoice(n);
end;

procedure start_playing;
var
n : integer;
begin
  for n := 0 to maxchn-1 do begin
    channels[n].vol := 0;
    channels[n].per := 428;
    channels[n].sample := 0;
    channels[n].pan := 7;  {middle}
    channels[n].on := 1;
    channels[n].dper := 428;
    channels[n].bar := 0;
    channels[n].fx := 255;
    channels[n].fxdata := 0;
    channels[n].fx_sl2 := 0;
    channels[n].fx_vib := 0;
    channels[n].vib_cnt := 0;
    channels[n].vib_wave := 0;
    channels[n].note := 0;
    channels[n].hit := 0;
    channels[n].no_fx := 0;
    channels[n].start_fx := 0;
    channels[n].arp1 := 0;
    channels[n].arp2 := 0;
    channels[n].arp_cnt := 0;
  end;
  speed := 6;
  nspeed := 6;
  tempo := 125;
  channels[0].pan := 7-def_pan;
  channels[1].pan := 7+def_pan;
  channels[2].pan := 7+def_pan;
  channels[3].pan := 7-def_pan;
  if maxchn > 4 then for n := 4 to maxchn-1 do
    channels[n].pan := channels[n-4].pan;
  if maxchn > 8 then for n := 8 to maxchn-1 do
    channels[n].pan := channels[n-8].pan;
  jump := 0;
  int_tick := 0;
  cur_ptn := 0;
  cur_row := 0;
  new_ptn := 0;
  new_row := 0;
  cur_tick := 0;
  time_counter := 0;
  time_counter2 := 0;
  asm cli end;
  setintvec(8,@modint);
  int_rate := 1193182 div 50;
  set_timer(int_rate);
  asm sti end;
end;

begin
end.