/*
  --> The Protracker Player Driver
   -> Part of the SPLAYER pack for MikMod 3.0

  The protracker driver supports all base Protracker 3.x commands and fea-
  tures.
*/

#include <string.h>
#include <stdarg.h>
#include "mikmod.h"

//  Set forbid to 1 when you want to modify any of the pf->sngpos, pf->patpos etc.
//  variables and clear it when you're done. This prevents getting strange
//  results due to intermediate interrupts.

//BOOL   mp_extspd  = 1;       // extended speed flag, default enabled
//BOOL   mp_panning = 1;       // panning flag, default enabled
//BOOL   mp_loop    = 0;       // loop module ?

static UNIMOD   *pf;           // <- this modfile is being played
static SWORD     mp_channel;   // channel it's working on
static AUDNNA   *a;            // current AUDTMP it's working on
static int       isfirst;
static int       audpool=NULL; // NNA channel selector optimization
static AUDOUT 	 aout_dummy;

static UWORD mytab[12] =
{   1712*16,1616*16,1524*16,1440*16,1356*16,1280*16,
    1208*16,1140*16,1076*16,1016*16,960*16,907*16
};

static UBYTE VibratoTable[32] =
{   0,24,49,74,97,120,141,161,
    180,197,212,224,235,244,250,253,
    255,253,250,244,235,224,212,197,
    180,161,141,120,97,74,49,24
};


static UBYTE avibtab[128] =
{   0,1,3,4,6,7,9,10,12,14,15,17,18,20,21,23,
    24,25,27,28,30,31,32,34,35,36,38,39,40,41,42,44,
    45,46,47,48,49,50,51,52,53,54,54,55,56,57,57,58,
    59,59,60,60,61,61,62,62,62,63,63,63,63,63,63,63,
    64,63,63,63,63,63,63,63,62,62,62,61,61,60,60,59,
    59,58,57,57,56,55,54,54,53,52,51,50,49,48,47,46,
    45,44,42,41,40,39,38,36,35,34,32,31,30,28,27,25,
    24,23,21,20,18,17,15,14,12,10,9,7,6,4,3,1
};


// ** Triton's linear periods to frequency translation table (for
// ** Fast Tracker 2 [XM] modules):

static ULONG lintab[768] =
{   535232,534749,534266,533784,533303,532822,532341,531861,
    531381,530902,530423,529944,529466,528988,528511,528034,
    527558,527082,526607,526131,525657,525183,524709,524236,
    523763,523290,522818,522346,521875,521404,520934,520464,
    519994,519525,519057,518588,518121,517653,517186,516720,
    516253,515788,515322,514858,514393,513929,513465,513002,
    512539,512077,511615,511154,510692,510232,509771,509312,
    508852,508393,507934,507476,507018,506561,506104,505647,
    505191,504735,504280,503825,503371,502917,502463,502010,
    501557,501104,500652,500201,499749,499298,498848,498398,
    497948,497499,497050,496602,496154,495706,495259,494812,
    494366,493920,493474,493029,492585,492140,491696,491253,
    490809,490367,489924,489482,489041,488600,488159,487718,
    487278,486839,486400,485961,485522,485084,484647,484210,
    483773,483336,482900,482465,482029,481595,481160,480726,
    480292,479859,479426,478994,478562,478130,477699,477268,
    476837,476407,475977,475548,475119,474690,474262,473834,
    473407,472979,472553,472126,471701,471275,470850,470425,
    470001,469577,469153,468730,468307,467884,467462,467041,
    466619,466198,465778,465358,464938,464518,464099,463681,
    463262,462844,462427,462010,461593,461177,460760,460345,
    459930,459515,459100,458686,458272,457859,457446,457033,
    456621,456209,455797,455386,454975,454565,454155,453745,
    453336,452927,452518,452110,451702,451294,450887,450481,
    450074,449668,449262,448857,448452,448048,447644,447240,
    446836,446433,446030,445628,445226,444824,444423,444022,
    443622,443221,442821,442422,442023,441624,441226,440828,
    440430,440033,439636,439239,438843,438447,438051,437656,
    437261,436867,436473,436079,435686,435293,434900,434508,
    434116,433724,433333,432942,432551,432161,431771,431382,
    430992,430604,430215,429827,429439,429052,428665,428278,
    427892,427506,427120,426735,426350,425965,425581,425197,
    424813,424430,424047,423665,423283,422901,422519,422138,
    421757,421377,420997,420617,420237,419858,419479,419101,
    418723,418345,417968,417591,417214,416838,416462,416086,
    415711,415336,414961,414586,414212,413839,413465,413092,
    412720,412347,411975,411604,411232,410862,410491,410121,
    409751,409381,409012,408643,408274,407906,407538,407170,
    406803,406436,406069,405703,405337,404971,404606,404241,
    403876,403512,403148,402784,402421,402058,401695,401333,
    400970,400609,400247,399886,399525,399165,398805,398445,
    398086,397727,397368,397009,396651,396293,395936,395579,
    395222,394865,394509,394153,393798,393442,393087,392733,
    392378,392024,391671,391317,390964,390612,390259,389907,
    389556,389204,388853,388502,388152,387802,387452,387102,
    386753,386404,386056,385707,385359,385012,384664,384317,
    383971,383624,383278,382932,382587,382242,381897,381552,
    381208,380864,380521,380177,379834,379492,379149,378807,

    378466,378124,377783,377442,377102,376762,376422,376082,
    375743,375404,375065,374727,374389,374051,373714,373377,
    373040,372703,372367,372031,371695,371360,371025,370690,
    370356,370022,369688,369355,369021,368688,368356,368023,
    367691,367360,367028,366697,366366,366036,365706,365376,
    365046,364717,364388,364059,363731,363403,363075,362747,
    362420,362093,361766,361440,361114,360788,360463,360137,
    359813,359488,359164,358840,358516,358193,357869,357547,
    357224,356902,356580,356258,355937,355616,355295,354974,
    354654,354334,354014,353695,353376,353057,352739,352420,
    352103,351785,351468,351150,350834,350517,350201,349885,
    349569,349254,348939,348624,348310,347995,347682,347368,
    347055,346741,346429,346116,345804,345492,345180,344869,
    344558,344247,343936,343626,343316,343006,342697,342388,
    342079,341770,341462,341154,340846,340539,340231,339924,
    339618,339311,339005,338700,338394,338089,337784,337479,
    337175,336870,336566,336263,335959,335656,335354,335051,
    334749,334447,334145,333844,333542,333242,332941,332641,
    332341,332041,331741,331442,331143,330844,330546,330247,
    329950,329652,329355,329057,328761,328464,328168,327872,
    327576,327280,326985,326690,326395,326101,325807,325513,
    325219,324926,324633,324340,324047,323755,323463,323171,
    322879,322588,322297,322006,321716,321426,321136,320846,
    320557,320267,319978,319690,319401,319113,318825,318538,
    318250,317963,317676,317390,317103,316817,316532,316246,
    315961,315676,315391,315106,314822,314538,314254,313971,
    313688,313405,313122,312839,312557,312275,311994,311712,
    311431,311150,310869,310589,310309,310029,309749,309470,
    309190,308911,308633,308354,308076,307798,307521,307243,
    306966,306689,306412,306136,305860,305584,305308,305033,
    304758,304483,304208,303934,303659,303385,303112,302838,
    302565,302292,302019,301747,301475,301203,300931,300660,
    300388,300117,299847,299576,299306,299036,298766,298497,
    298227,297958,297689,297421,297153,296884,296617,296349,
    296082,295815,295548,295281,295015,294749,294483,294217,
    293952,293686,293421,293157,292892,292628,292364,292100,
    291837,291574,291311,291048,290785,290523,290261,289999,
    289737,289476,289215,288954,288693,288433,288173,287913,
    287653,287393,287134,286875,286616,286358,286099,285841,
    285583,285326,285068,284811,284554,284298,284041,283785,
    283529,283273,283017,282762,282507,282252,281998,281743,
    281489,281235,280981,280728,280475,280222,279969,279716,
    279464,279212,278960,278708,278457,278206,277955,277704,
    277453,277203,276953,276703,276453,276204,275955,275706,
    275457,275209,274960,274712,274465,274217,273970,273722,
    273476,273229,272982,272736,272490,272244,271999,271753,
    271508,271263,271018,270774,270530,270286,270042,269798,
    269555,269312,269069,268826,268583,268341,268099,267857
};


#define LOGFAC 2*16

static UWORD logtab[104] =
{   LOGFAC*907,LOGFAC*900,LOGFAC*894,LOGFAC*887,LOGFAC*881,LOGFAC*875,LOGFAC*868,LOGFAC*862,
    LOGFAC*856,LOGFAC*850,LOGFAC*844,LOGFAC*838,LOGFAC*832,LOGFAC*826,LOGFAC*820,LOGFAC*814,
    LOGFAC*808,LOGFAC*802,LOGFAC*796,LOGFAC*791,LOGFAC*785,LOGFAC*779,LOGFAC*774,LOGFAC*768,
    LOGFAC*762,LOGFAC*757,LOGFAC*752,LOGFAC*746,LOGFAC*741,LOGFAC*736,LOGFAC*730,LOGFAC*725,
    LOGFAC*720,LOGFAC*715,LOGFAC*709,LOGFAC*704,LOGFAC*699,LOGFAC*694,LOGFAC*689,LOGFAC*684,
    LOGFAC*678,LOGFAC*675,LOGFAC*670,LOGFAC*665,LOGFAC*660,LOGFAC*655,LOGFAC*651,LOGFAC*646,
    LOGFAC*640,LOGFAC*636,LOGFAC*632,LOGFAC*628,LOGFAC*623,LOGFAC*619,LOGFAC*614,LOGFAC*610,
    LOGFAC*604,LOGFAC*601,LOGFAC*597,LOGFAC*592,LOGFAC*588,LOGFAC*584,LOGFAC*580,LOGFAC*575,
    LOGFAC*570,LOGFAC*567,LOGFAC*563,LOGFAC*559,LOGFAC*555,LOGFAC*551,LOGFAC*547,LOGFAC*543,
    LOGFAC*538,LOGFAC*535,LOGFAC*532,LOGFAC*528,LOGFAC*524,LOGFAC*520,LOGFAC*516,LOGFAC*513,
    LOGFAC*508,LOGFAC*505,LOGFAC*502,LOGFAC*498,LOGFAC*494,LOGFAC*491,LOGFAC*487,LOGFAC*484,
    LOGFAC*480,LOGFAC*477,LOGFAC*474,LOGFAC*470,LOGFAC*467,LOGFAC*463,LOGFAC*460,LOGFAC*457,
    LOGFAC*453,LOGFAC*450,LOGFAC*447,LOGFAC*443,LOGFAC*440,LOGFAC*437,LOGFAC*434,LOGFAC*431
};

static SBYTE PanbrelloTable[256] =
{  0,2,3,5,6,8,9,11,12,14,16,17,19,20,22,23,
   24,26,27,29,30,32,33,34,36,37,38,39,41,42,43,44,
   45,46,47,48,49,50,51,52,53,54,55,56,56,57,58,59,
   59,60,60,61,61,62,62,62,63,63,63,64,64,64,64,64,
   64,64,64,64,64,64,63,63,63,62,62,62,61,61,60,60,
   59,59,58,57,56,56,55,54,53,52,51,50,49,48,47,46,
   45,44,43,42,41,39,38,37,36,34,33,32,30,29,27,26,
   24,23,22,20,19,17,16,14,12,11,9,8,6,5,3,2,
   0,-2,-3,-5,-6,-8,-9,-11,-12,-14,-16,-17,-19,-20,-22,-23,
   -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44,
   -45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59,
   -59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64,
   -64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60,
   -59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46,
   -45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26,
   -24,-23,-22,-20,-19,-17,-16,-14,-12,-11,-9,-8,-6,-5,-3,-2
};


/* New Note Action Scoring System:
  ---------------------------------
   1) total-volume (fadevol, chanvol, volume) is the main scorer.
   2) a looping sample is a bonus x2
   3) a forground channel is a bonus x4
   4) an active envelope with keyoff is a handicap -x2
*/
static int MP_FindEmptyChannel(int curchan)  // returns mp_audio index of free channel
{
   AUDOUT *a;
   ULONG t,k,tvol,p,pp;

   /*for(t=md_numchn; t; t--, audpool++)
   {   if(audpool == md_numchn) audpool = 0;
       if(!(pf->control[audpool].kick) && MD_VoiceStopped(audpool))
       {   audpool++;
           return audpool-1;
       }
   }*/

#ifndef __WATCOMC__
   for(t=0; t<md_numchn; t++)
   {   if(!(pf->control[t].kick) && MD_VoiceStopped(t))
       {   return t;
       }
   }
#else
   for(t=0; t<md_numchn; t++)
   {   if(!(pf->control[t].kick))
       {   if((k=MD_VoiceStopped(t)) == -1)
           {  return curchan;
           } else if(k)
           {  return t;
           }
       }
   }
#endif

   tvol = 0xffffffUL;  t = 0;  p = 0;  a = pf->control;
   for(k=0; k<md_numchn; k++, a++)
   {   if(!a->kick)
       {   pp = a->totalvol << ((a->s->flags & SF_LOOP) ? 1 : 0);
           if((a->master!=NULL) && (a==a->master->slave))
              pp <<= 2;

           /*if(a->volflg & EF_ON)
           {   if(a->volflg & (EF_SUSTAIN | EF_LOOP))
               {   if(!(a->keyon & KEY_OFF))
                   {   pp >>= 1;
                       if(a->venv.env[a->venv.end].val < 32) pp>>=1;
                   } else
                       pp <<= 1;
               } else pp <<= 1;
           }*/

           if(pp < tvol)
           {  tvol = pp;
              t    = k;
           }
       }
   }

   if(tvol>8000*7) return -1; //mp_channel;

   return t;
   //return -1;
}


static SWORD Interpolate(SWORD p,SWORD p1,SWORD p2,SWORD v1,SWORD v2)
{
    SWORD dp,dv,di;

    if(p1==p2) return v1;

    dv = v2-v1;
    dp = p2-p1;
    di = p-p1;

    return v1 + ((SLONG)(di*dv) / dp);
}


UWORD getlinearperiod(UBYTE note, ULONG fine)
{
    return((10L*12*16*4)-((ULONG)note*16*4)-(fine/2)+64);
}


static UWORD getlogperiod(UBYTE note,ULONG fine)
{
    UBYTE n,o;
    UWORD p1,p2;
    ULONG i;

    n = note%12;
    o = note/12;
    i = (n<<3) + (fine>>4);           // n*8 + fine/16

    p1 = logtab[i];
    p2 = logtab[i+1];

    return(Interpolate(fine/16,0,15,p1,p2)>>o);
}


static UWORD getoldperiod(UBYTE note,ULONG c2spd)
{
    UBYTE n,o;
    ULONG period;

    if(!c2spd) return 4242;         // <- prevent divide overflow.. (42 eheh)

    n = note%12;
    o = note/12;
    period = ((8363L*(ULONG)mytab[n]) >> o )/c2spd;
    return period;
}


static UWORD GetPeriod(UBYTE note, ULONG c2spd)
{
    if(pf->flags & UF_XMPERIODS)
       return (pf->flags & UF_LINEAR) ? getlinearperiod(note,c2spd) : getlogperiod(note,c2spd);
    return getoldperiod(note,c2spd);
}


static SWORD InterpolateEnv(SWORD p, ENVPT *a, ENVPT *b)
{
    return(Interpolate(p,a->pos,b->pos,a->val,b->val));
}


static SWORD DoPan(SWORD envpan, SWORD pan)
{
    return(pan + (((envpan-128)*(128-abs(pan-128)))/128));
}


static void StartEnvelope(ENVPR *t, UBYTE flg, UBYTE pts, UBYTE susbeg, UBYTE susend, UBYTE beg, UBYTE end, ENVPT *p)
{
    t->flg = flg;
    t->pts = pts;
    t->susbeg = susbeg;
    t->susend = susend;
    t->beg = beg;
    t->end = end;
    t->env = p;
    t->p = 0;
    t->a = 0;
    t->b = 1;
}


static SWORD ProcessEnvelope(ENVPR *t, SWORD v, UBYTE keyon)
{
    if(t->flg & EF_ON)
    {   // panning active? -> copy variables

        UBYTE a,b;
        UWORD p;

        a = t->a;
        b = t->b;
        p = t->p;

        // compute the envelope value between points a and b
        v = InterpolateEnv(p,&t->env[a],&t->env[b]);

        p++;

        // pointer reached point b?
        if(p >= t->env[b].pos)
        {   // shift points a and b 

            a = b; b++;

            if((t->flg & EF_LOOP) && (b > t->end))
            {   a = t->beg;
                if(t->beg == t->end) b=a; else b=a+1;
                p = t->env[a].pos;
            } else if((t->flg & EF_SUSTAIN) && (keyon & KEY_OFF) && (b > t->susend))
            {   a = t->susbeg;
                if(t->susbeg == t->susend) b=a; else b=a+1;
                p = t->env[a].pos;
            } else
            {   if(b >= t->pts)
                {   if(t->flg & EF_VOLENV && mp_channel!=-1)
                    {   pf->control[mp_channel].keyon &= ~KEY_FADE;
                        if(v==0)
                            pf->control[mp_channel].fadevol = 0;
                    }
                    b--;
                    p--;
                }
            }
        }
        t->a = a;
        t->b = b;
        t->p = p;
    }
    return v;
}


// XM linear period frequency conversion
ULONG getfrequency(UBYTE flags, ULONG period)
{
   ULONG result;

   if(flags & UF_LINEAR)
      result = lintab[period % 768] >> (period / 768);
   else
      result = (8363L*1712L)/period;

   return result;
}


static void DoEEffects(UBYTE dat)
{
    UBYTE nib;

    nib = dat&0xf;

    switch(dat>>4)
    {   case 0x0:       // filter toggle, not supported
        break;

        case 0x1:       // fineslide up
           if(!pf->vbtick) a->tmpperiod-=(nib<<2);
        break;

        case 0x2:       // fineslide dn
           if(!pf->vbtick) a->tmpperiod+=(nib<<2);
        break;

        case 0x3:       // glissando ctrl
           a->glissando=nib;
        break;

        case 0x4:       // set vibrato waveform
           a->wavecontrol&=0xf0;
           a->wavecontrol|=nib;
        break;

        case 0x5:       // set finetune
//            a->c2spd=finetune[nib];
//            a->tmpperiod=GetPeriod(a->note,pf->samples[a->sample].transpose,a->c2spd);
        break;

        case 0x6:       // set patternloop
           if(pf->vbtick) break;
           // hmm.. this one is a real kludge. But now it
           // works
           if(nib)                // set reppos or repcnt ?
           {  // set repcnt, so check if repcnt already is set,
              // which means we are already looping

              if(pf->pat_repcnt>0)
                 pf->pat_repcnt--;            // already looping, decrease counter
              else
                 pf->pat_repcnt=nib;          // not yet looping, so set repcnt

              if(pf->pat_repcnt)              // jump to reppos if repcnt>0
                 pf->patpos=pf->pat_reppos;
           } else
           {  pf->pat_reppos=pf->patpos-1;     // set reppos
           }
        break;


        case 0x7:       // set tremolo waveform
           a->wavecontrol&=0x0f;
           a->wavecontrol|=nib<<4;
        break;

        case 0x8:       // set panning
           if(pf->panflag)
           {   if(nib<=8) nib*=16; else nib*=17;
               a->panning = nib;
               pf->panning[mp_channel] = nib;
           }
           break;

        case 0x9:       // retrig note
           // only retrigger if
           // data nibble > 0 

           if(nib>0)
           {  if(a->retrig==0)
              {  // when retrig counter reaches 0,
                 // reset counter and restart the sample
                 a->kick = 1;
                 a->retrig = nib;
              }
              a->retrig--; // countdown 
           }
        break;

        case 0xa:       // fine volume slide up
           if(pf->vbtick) break;

           a->tmpvolume+=nib;
           if(a->tmpvolume>64) a->tmpvolume = 64;
        break;

        case 0xb:       // fine volume slide dn 
           if(pf->vbtick) break;

           a->tmpvolume-=nib;
           if(a->tmpvolume<0) a->tmpvolume = 0;
        break;

        case 0xc:       // cut note
           // When pf->vbtick reaches the cut-note value,
           // turn the volume to zero ( Just like
           // on the amiga)
           if(pf->vbtick>=nib)
              a->tmpvolume=0;     // just turn the volume down
        break;

        case 0xd:       // note delay
           // delay the start of the
           // sample until pf->vbtick==nib
           if(pf->vbtick==nib)
              a->kick = 1;
           else
              a->kick = 0;
        break;

        case 0xe:       // pattern delay
           if(pf->vbtick) break;
           if(!pf->patdly2) pf->patdly = nib+1;       // only once (when pf->vbtick=0)
        break;

        case 0xf:       // invert loop, not supported 
        break;
    }
}


static void DoVibrato(void)
{
    UBYTE q;
    UWORD temp;

    q = (a->vibpos>>2)&0x1f;

    switch(a->wavecontrol&3)
    {   case 0:     // sine
            temp = VibratoTable[q];
        break;

        case 1:     // ramp down
            q<<=3;
            if(a->vibpos<0) q = 255-q;
            temp = q;
        break;

        case 2:     // square wave
            temp = 255;
        break;

        case 3:     // Evil random wave
           temp = rand() & 255;
        break;
    }

    temp*=a->vibdepth;
    temp>>=7;
    temp<<=2;

    if(a->vibpos>=0)
        a->period = a->tmpperiod+temp;
    else
        a->period = a->tmpperiod-temp;

    if(pf->vbtick) a->vibpos+=a->vibspd;    // do not update when pf->vbtick==0
}


static void DoTremolo(void)
{
    UBYTE q;
    UWORD temp;

    q = (a->trmpos>>2)&0x1f;

    switch((a->wavecontrol>>4)&3)
    {   case 0:    // sine
            temp = VibratoTable[q];
        break;

        case 1:    // ramp down
            q<<=3;
            if(a->trmpos<0) q = 255-q;
            temp = q;
        break;

        case 2:    // square wave
            temp = 255;
        break;

        case 3:     // Evil random wave
           temp = rand() & 255;
        break;
    }

    temp*=a->trmdepth;
    temp>>=6;

    if(a->trmpos>=0)
    {   a->volume = a->tmpvolume+temp;
        if(a->volume>64) a->volume=64;
    } else
    {   a->volume = a->tmpvolume-temp;
        if(a->volume<0) a->volume=0;
    }

    if(pf->vbtick) a->trmpos+=a->trmspd;    // do not update when pf->vbtick==0 
}


static void DoVolSlide(UBYTE dat)
{
    if(!pf->vbtick) return;             // do not update when pf->vbtick==0

    a->tmpvolume+=dat>>4;           // volume slide
    a->tmpvolume-=dat&0xf;
    if(a->tmpvolume<0) a->tmpvolume = 0;
    if(a->tmpvolume>64) a->tmpvolume = 64;
}


static void DoToneSlide(void)
{
    int dist;

    if(a->period==0) return;

    if(!pf->vbtick)
    {   a->tmpperiod = a->period;
        return;
    }

    // We have to slide a->period towards a->wantedperiod, so
    // compute the difference between those two values

    dist = a->period-a->wantedperiod;

    if( dist==0 || a->portspeed>abs(dist) ) // if they are equal or if portamentospeed is too big
        a->period = a->wantedperiod;    // make tmpperiod equal tperiod
    else if(dist>0)                     // dist>0 ?
        a->period-=a->portspeed;        // then slide up
    else
        a->period+=a->portspeed;        // dist<0 -> slide down

    a->tmpperiod = a->period;
}


static void DoPTEffect0(UBYTE dat)
{
    UBYTE note;

    note = a->note;

    if(dat!=0)
    {   switch(pf->vbtick%3)
        {   case 1:
                note+=(dat>>4);  break;
            case 2:
                note+=(dat&0xf); break;
        }
        a->period = GetPeriod(note,a->c2spd);
        a->ownper = 1;
    }
}


// -----------------------------------------
// --> ScreamTreacker 3 Specific Effects <--
// -----------------------------------------

static void DoS3MVolSlide(UBYTE inf)
{
    UBYTE lo,hi;

    if(inf) a->s3mvolslide=inf;

    inf = a->s3mvolslide;
    lo  = inf&0xf;
    hi  = inf>>4;

    if(hi==0)       a->tmpvolume-=lo;
    else if(lo==0)  a->tmpvolume+=hi;
    else if(hi==0xf)
    {   if(!pf->vbtick) a->tmpvolume-=lo;
    } else if(lo==0xf)
    {   if(!pf->vbtick) a->tmpvolume+=hi;
    }
    if(a->tmpvolume<0)  a->tmpvolume = 0;
    if(a->tmpvolume>64) a->tmpvolume = 64;
}


static void DoS3MSlideDn(UBYTE inf)
{
    UBYTE hi,lo;

    if(inf!=0) a->slidespeed = inf;
    else inf = a->slidespeed;

    hi = inf>>4;
    lo = inf&0xf;

    if(hi==0xf)
    {   if(!pf->vbtick) a->tmpperiod+=(UWORD)lo<<2;
    } else if(hi==0xe)
    {   if(!pf->vbtick) a->tmpperiod+=lo;
    } else
    {   if(pf->vbtick) a->tmpperiod+=(UWORD)inf<<2;
    }
}


static void DoS3MSlideUp(UBYTE inf)
{
    UBYTE hi,lo;

    if(inf!=0) a->slidespeed = inf;
    else inf = a->slidespeed;

    hi = inf>>4;
    lo = inf&0xf;

    if(hi==0xf)
    {   if(!pf->vbtick) a->tmpperiod-=(UWORD)lo<<2;
    } else if(hi==0xe)
    {   if(!pf->vbtick) a->tmpperiod-=lo;
    } else
    {   if(pf->vbtick) a->tmpperiod-=(UWORD)inf<<2;
    }
}


static void DoS3MTremor(UBYTE inf)
{
    UBYTE on,off;

    if(inf!=0) a->s3mtronof = inf;
    else inf = a->s3mtronof;

    if(!pf->vbtick) return;

    on  = (inf>>4)+1;
    off = (inf&0xf)+1;

    a->s3mtremor %= (on+off);
    a->volume = (a->s3mtremor < on ) ? a->tmpvolume:0;
    a->s3mtremor++;
}


static void DoS3MRetrig(UBYTE inf)
{
    UBYTE hi,lo;

    hi = inf>>4;
    lo = inf&0xf;

    if(lo)
    {   a->s3mrtgslide = hi;
        a->s3mrtgspeed = lo;
    }

    if(hi) a->s3mrtgslide = hi;

    // only retrigger if
    // lo nibble > 0 

    if(a->s3mrtgspeed>0)
    {   if(a->retrig==0)
        {   // when retrig counter reaches 0,
            // reset counter and restart the sample

            a->kick = 1;
            a->retrig = a->s3mrtgspeed;

            if(pf->vbtick)                     // don't slide on first retrig
            {   switch(a->s3mrtgslide)
                {   case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                        a->tmpvolume-=(1<<(a->s3mrtgslide-1));
                        break;

                    case 6:
                        a->tmpvolume=(2*a->tmpvolume)/3;
                        break;

                    case 7:
                        a->tmpvolume=a->tmpvolume>>1;
                        break;

                    case 9:
                    case 0xa:
                    case 0xb:
                    case 0xc:
                    case 0xd:
                        a->tmpvolume+=(1<<(a->s3mrtgslide-9));
                        break;

                    case 0xe:
                        a->tmpvolume=(3*a->tmpvolume)/2;
                        break;

                    case 0xf:
                        a->tmpvolume=a->tmpvolume<<1;
                        break;
                }
                if(a->tmpvolume<0) a->tmpvolume=0;
                if(a->tmpvolume>64) a->tmpvolume=64;
            }
        }
        a->retrig--; // countdown 
    }
}


static void DoS3MSpeed(UBYTE speed)
{
    if(pf->vbtick || pf->patdly2) return;

    if(speed)
    {   pf->sngspd = speed;
        pf->vbtick    = 0;
    }
}


static void DoS3MTempo(UBYTE tempo)
{
    if(pf->vbtick || pf->patdly2) return;
    pf->bpm = tempo;
}


static void DoS3MFineVibrato(void)
{
    UBYTE q;
    UWORD temp;

    q = (a->vibpos>>2)&0x1f;

    switch(a->wavecontrol&3)
    {   case 0: // sine
            temp=VibratoTable[q];
            break;

        case 1: // ramp down
            q<<=3;
            if(a->vibpos<0) q=255-q;
            temp=q;
            break;

        case 2: // square wave
            temp=255;
            break;

        case 3: // evil random
            temp = rand() & 255;  // (range 0 to 255)
    }

    temp*=a->vibdepth;
    temp>>=8;

    if(a->vibpos>=0)
        a->period = a->tmpperiod+temp;
    else
        a->period = a->tmpperiod-temp;

    a->vibpos += a->vibspd;
}


static void DoS3MTremolo(void)
{
    UBYTE q;
    UWORD temp;

    q = (a->trmpos>>2)&0x1f;

    switch((a->wavecontrol>>4)&3)
    {   case 0: // sine
            temp = VibratoTable[q];
            break;

        case 1: // ramp down
            q<<=3;
            if(a->trmpos<0) q = 255-q;
            temp = q;
            break;

        case 2: // square wave
            temp=255;
            break;

        case 3: // evil random
            temp = rand() & 255;  // (range 0 to 255)
    }

    temp*=a->trmdepth;
    temp>>=7;

    if(a->trmpos>=0)
    {   a->volume = a->tmpvolume+temp;
        if(a->volume>64) a->volume = 64;
    } else
    {   a->volume = a->tmpvolume-temp;
        if(a->volume<0) a->volume = 0;
    }

    if(pf->vbtick) a->trmpos+=a->trmspd;        /* do not update when pf->vbtick==0 */
}


// --------------------------------------
// --> FastTracker 2 Specific Effects <--
// --------------------------------------

static void DoXMVolSlide(UBYTE inf)
{
    UBYTE lo,hi;

    if(inf)
       a->s3mvolslide=inf;

    inf=a->s3mvolslide;
    if(!pf->vbtick) return;

    lo=inf&0xf;
    hi=inf>>4;

    if(hi==0)
        a->tmpvolume-=lo;
    else
        a->tmpvolume+=hi;

    if(a->tmpvolume<0) a->tmpvolume=0;
    else if(a->tmpvolume>64) a->tmpvolume=64;
}


static void DoXMGlobalSlide(UBYTE inf)
{
    if(pf->vbtick)
    {  if(inf) pf->globalslide=inf; else inf=pf->globalslide;
       if(inf & 0xf0) inf &= 0xf0;
       pf->volume = pf->volume + ((inf >> 4) - (inf & 0xf))*2;

       if(pf->volume<0) pf->volume = 0;
       else if(pf->volume>128) pf->volume = 128;
    }
}


static void DoXMPanSlide(UBYTE inf)
{
    UBYTE lo,hi;
    SWORD pan;


    if(inf!=0) a->pansspd = inf;
    else inf = a->pansspd;

    if(!pf->vbtick) return;

    lo = inf&0xf;
    hi = inf>>4;

    // slide right has absolute priority:

    if(hi) lo = 0;

    pan = a->panning;

    pan-=lo;
    pan+=hi;

    if(pan<0) pan = 0;
    if(pan>255) pan = 255;

    a->panning = pan;
}


static void DoXMExtraFineSlideUp(UBYTE inf)
{
   if(!pf->vbtick)       
   {  if(inf) a->ffportupspd = inf; else inf = a->ffportupspd;
      a->period -= inf;
   }
   a->tmpperiod = a->period;
}


static void DoXMExtraFineSlideDown(UBYTE inf)
{
   if(!pf->vbtick)
   {  if(inf) a->ffportdnspd = inf; else inf = a->ffportdnspd;
      a->period += inf;
   }
   a->tmpperiod = a->period;
}


// ---------------------------------------
// --> ImpulseTracker Player Functions <--
// ---------------------------------------

static void DoITChanVolSlide(UBYTE inf)
{
    UBYTE lo, hi;

    if(inf) a->chanvolslide = inf;
    inf = a->chanvolslide;

    lo = inf&0xf;
    hi = inf>>4;

    if(hi==0)
    {   a->chanvol-=lo;
    } else if(lo==0)
    {   a->chanvol+=hi;
    } else if(hi==0xf)
    {   if(!pf->vbtick) a->chanvol-=lo;
    } else if(lo==0xf)
    {   if(!pf->vbtick) a->chanvol+=hi;
    }

    if(a->chanvol<0) a->chanvol = 0;
    if(a->chanvol>64) a->chanvol = 64;
}


static void DoITGlobalSlide(UBYTE inf)
{
    UBYTE lo,hi;

    if(inf) pf->globalslide = inf;
    inf = pf->globalslide;

    lo = inf&0xf;
    hi = inf>>4;

    if(lo==0)
    {   pf->volume+=hi;
    } else if(hi==0)
    {   pf->volume-=lo;
    } else if(lo==0xf)
    {   if(!pf->vbtick) pf->volume+=hi;
    } else if(hi==0xf)
    {   if(!pf->vbtick) pf->volume-=lo;
    }

    if(pf->volume<0) pf->volume = 0;
    if(pf->volume>128) pf->volume = 128;
}


static void DoITPanSlide(UBYTE inf)
{
    UBYTE lo,hi;
    SWORD pan;

    if(inf) a->pansspd = inf;
    inf = a->pansspd;

    lo = inf&0xf;
    hi = inf>>4;

    pan = a->panning;

    if(hi==0)
    {   pan+=lo<<2;
    } else if(lo==0)
    {   pan-=hi<<2;
    } else if(hi==0xf)
    {   if(!pf->vbtick) pan+=lo<<2;
    } else if(lo==0xf)
    {   if(!pf->vbtick) pan-=hi<<2;
    }
    if(pan > 255) pan = 255;
    if(pan < 0) pan = 0;
    a->panning = /*pf->panning[mp_channel] =*/ pan;
}


static void DoITVibrato(void)
{
    UBYTE q;
    UWORD temp;

    q = (a->vibpos>>2)&0x1f;

    switch(a->wavecontrol&3)
    {   case 0: // sine
           temp=VibratoTable[q];
        break;

        case 1: // ramp down
           q<<=3;
           if(a->vibpos<0) q=255-q;
           temp=q;
        break;

        case 2: // square wave
           temp=255;
        break;

        case 3: // evil random
           temp = rand() & 255;  // (range 0 to 255)
        break;
    }

    temp*=a->vibdepth;
    temp>>=8;
    temp<<=2;

    if(a->vibpos>=0)
       a->period = a->tmpperiod+temp;
    else
       a->period = a->tmpperiod-temp;

    a->vibpos+=a->vibspd;
}


static void DoITFineVibrato(void)
{
    UBYTE q;
    UWORD temp;

    q = (a->vibpos>>2)&0x1f;

    switch(a->wavecontrol&3)
    {   case 0: // sine
           temp=VibratoTable[q];
        break;

        case 1: // ramp down
           q<<=3;
           if(a->vibpos<0) q = 255-q;
           temp = q;
        break;

        case 2: // square wave
           temp = 255;
        break;

        case 3: // evil random
            temp = rand() & 255;  // (range 0 to 255)
        break;
    }

    temp*=a->vibdepth;
    temp>>=8;

    if(a->vibpos>=0)
       a->period = a->tmpperiod+temp;
    else
       a->period = a->tmpperiod-temp;

    a->vibpos+=a->vibspd;
}


static void DoITTremor(UBYTE inf)
{
    UBYTE on,off;

    if(inf!=0) a->s3mtronof=inf;
    else inf=a->s3mtronof;

    if(!pf->vbtick) return;

    on=(inf>>4);
    off=(inf&0xf);

    a->s3mtremor%=(on+off);
    a->volume=(a->s3mtremor < on ) ? a->tmpvolume:0;
    a->s3mtremor++;
}


static void DoITPanbrello(void)
{
    UBYTE q;
    static SLONG temp;

    q = a->panbpos;

    switch(a->panbwave)
    {   case 0: // sine
           temp = PanbrelloTable[q];
        break;

        // only sinewave is correctly supported right now

        case 1: // ramp down
           q<<=3;
           temp = q;
        break;

        case 2: // square wave
           temp = 64;
        break;

        case 3: // evil random
           if(a->panbpos >= a->panbspd)
           {   a->panbpos = 0;
               temp = rand() & 255;
           }
        }

    temp*=a->panbdepth;
    temp/=8;

    a->panning = pf->panning[mp_channel] + temp;
    a->panbpos += a->panbspd;
}


static void DoITToneSlide(void)
{
    int dist;

    if(a->period==0) return;

    if(!pf->vbtick)
    {  a->tmpperiod = a->period;
       return;
    }

    // We have to slide a->period towards a->wantedperiod,
    // compute the difference between those two values

    dist = a->period-a->wantedperiod;

    if( dist==0 ||                       // if they are equal
        a->slidespeed>abs(dist) )        // or if portamentospeed is too big
    {   a->period = a->wantedperiod;     // make tmpperiod equal tperiod
    } else if(dist>0)                    // dist>0 ?
    {   a->period-=a->slidespeed;        // then slide up
    } else
    {   a->period+=a->slidespeed;        // dist<0 -> slide down
    }
    a->tmpperiod = a->period;
}


static void DoNNAEffects(UBYTE dat)
{
    int t;
    AUDOUT *aout;
    
    dat &= 0xf;
    aout = (a->slave==NULL) ? &aout_dummy : a->slave;

/*#ifdef _MONITOR_       
   monitor[mp_channel].unieffect = (dat) + UNI_LAST + 32;
#endif*/

    switch(dat)
    {   case 0x0:       // Past note cut
           for(t=0; t<md_numchn; t++)
              if(pf->control[t].master == a)
                 pf->control[t].fadevol = 0;
        break;

        case 0x1:       // Past note off
           for(t=0; t<md_numchn; t++)
              if(pf->control[t].master == a)
              {  pf->control[t].keyon &= ~KEY_OFF;
                 if(!(pf->control[t].venv.flg & EF_ON))
                    pf->control[t].keyon = 0;
              }
        break;

        case 0x2:       // Past note fade
           for(t=0; t<md_numchn; t++)
              if(pf->control[t].master == a)
                 pf->control[t].keyon &= ~KEY_FADE;
                break;

        case 0x3:       // set NNA note cut
           a->nna = (a->nna & ~0x3f) | NNA_CUT;
        break;

        case 0x4:       // set NNA note continue
           a->nna = (a->nna & ~0x3f) | NNA_CONTINUE;
        break;

        case 0x5:       // set NNA note off
           a->nna = (a->nna & ~0x3f) | NNA_OFF;
        break;   

        case 0x6:       // set NNA note fade
           a->nna = (a->nna & ~0x3f) | NNA_FADE;
        break;

        case 0x7:       // disable volume envelope
           aout->volflg  &= ~EF_ON;
        break;

        case 0x8:       // enable volume envelope 
           aout->volflg  |= EF_ON;
        break;

        case 0x9:       // disable panning envelope
           aout->panflg  &= ~EF_ON;
        break;    

        case 0xa:       // enable panning envelope
           aout->panflg  |= EF_ON;
        break;

        case 0xb:       // disable pitch envelope
           aout->pitflg  &= ~EF_ON;
        break;

        case 0xc:       // enable pitch envelope
           aout->pitflg  |= EF_ON;
        break;
    }
}


static void DoSSEffects(UBYTE c)

//  Impulse/Scream Tracker Sxx effects.
//  All Sxx effects share the same memory space.

{
    UBYTE inf;

    inf = UniGetByte();

    if(c==0 && inf==0)
    {   c   = a->sseffect;
        inf = a->ssdata;
    } else
    {   a->sseffect = c;
        a->ssdata = inf;
    }

/*#ifdef _MONITOR_
   monitor[mp_channel].unieffect = c + UNI_LAST + 16;
#endif*/

    switch(c)
    {   case 0: break;          // do nothing
        case SS_GLISSANDO:      // S1x set glissando control
           DoEEffects(0x30|inf);
        break;              

        case SS_FINETUNE:       // S2x set finetune
           DoEEffects(0x50|inf);
        break;

        case SS_VIBWAVE:        // S3x set vibrato waveform
           DoEEffects(0x40|inf);
        break;   
                          
        case SS_TREMWAVE:       // S4x set tremolo waveform
           DoEEffects(0x70|inf);
        break;

        case SS_PANWAVE:        // The Satanic Panbrello waveform
           a->panbwave = (UniGetByte());
        break;
                  
        case SS_FRAMEDELAY:     // S6x Delay x number of frames (patdly)
           DoEEffects(0xe0|inf);
        break;
        
        case SS_S7EFFECTS:      // S7x Instrument / NNA commands
           DoNNAEffects(UniGetByte());
        break;

        case SS_PANNING:        // S8x set panning position
           DoEEffects(0x80|inf);
        break;

        case SS_SURROUND:       // S9x Set Surround Sound
           a->panning = pf->panning[mp_channel] = PAN_SURROUND;
        break;    

        case SS_HIOFFSET:       // SAy Set high order sample offset yxx00h
           a->hioffset |= UniGetByte() << 16;
        break;

        case SS_PATLOOP:        // SBx pattern loop
           DoEEffects(0x60|inf);
        break;

        case SS_NOTECUT:        // SCx notecut
           DoEEffects(0xC0|inf);
        break;

        case SS_NOTEDELAY:      // SDx notedelay
           DoEEffects(0xD0|inf);
        break;

        case SS_PATDELAY:       // SEx patterndelay
           DoEEffects(0xE0|inf);
        break;
    }
}    


static void DoVolEffects(UBYTE c)

//  Impulse Tracker Volume/Pan Column effects.
//  All volume/pan column effects share the same memory space.

{
    UBYTE inf;

    inf = UniGetByte();

    if(c==0 && inf==0)
    {   c   = a->voleffect;
        inf = a->voldata;
    } else
    {   a->voleffect = c;
        a->voldata = inf;
    }

    switch(c)
    {   case 0: break;          // do nothing
        case VOL_VOLUME:
            if(pf->vbtick) break;
            if(inf>64) inf = 64;
            a->tmpvolume = inf;
        break;

        case VOL_PANNING:
           if(pf->panflag)
           {   a->panning = inf;
               pf->panning[mp_channel] = inf;
           }
        break;

        case VOL_VOLSLIDE:
            DoS3MVolSlide(inf);
        break;
           
        case VOL_PITCHSLIDEDN:
                DoS3MSlideDn(UniGetByte());
        break;

        case VOL_PITCHSLIDEUP:
                DoS3MSlideUp(UniGetByte());
        break;

        case VOL_PORTAMENTO:
            if(!(pf->vbtick==pf->sngspd-1) && (a->newsamp))
            {   a->kick  = 1;
                a->start = 0;
                a->period *= a->c2spd * a->newsamp;
            } else
            {   a->kick  = 0;
            }
            if(inf!=0)
            {   a->slidespeed = inf;
                a->slidespeed<<=2;
            }
                DoITToneSlide();
            a->ownper = 1;
        break;

        case VOL_VIBRATO:
            if(inf&0x0f) a->vibdepth = inf&0xf;
            if(inf&0xf0) a->vibspd   = (inf&0xf0)>>2;
                DoITVibrato();
            a->ownper = 1;
        break;
    }
}



// --------------------------------
// --> General Player Functions <--
// --------------------------------

static void pt_playeffects(void)
{
   UBYTE dat,c;
   
   while(c = UniGetByte())
    switch(c)
    {   case UNI_NOTE:
        case UNI_INSTRUMENT:
            UniSkipOpcode(c);
        break;

        case UNI_PTEFFECT0:
            DoPTEffect0(UniGetByte());
        break;

        case UNI_PTEFFECT1:
            dat = UniGetByte();
            if(dat!=0) a->slidespeed = (UWORD)dat<<2;
            if(pf->vbtick) a->tmpperiod-=a->slidespeed;
        break;

        case UNI_PTEFFECT2:
            dat = UniGetByte();
            if(dat!=0) a->slidespeed = (UWORD)dat<<2;
            if(pf->vbtick) a->tmpperiod+=a->slidespeed;
        break;

        case UNI_PTEFFECT3:
            dat = UniGetByte();
            a->kick = 0;                   // temp XM fix
            if(dat!=0)
            {   a->portspeed = dat;
                a->portspeed<<=2;
            }
            DoToneSlide();
            a->ownper = 1;
        break;

        case UNI_PTEFFECT4:
            dat = UniGetByte();
            if(dat&0x0f) a->vibdepth = dat&0xf;
            if(dat&0xf0) a->vibspd = (dat&0xf0)>>2;
            DoVibrato();
            a->ownper = 1;
        break;

        case UNI_PTEFFECT5:
            dat = UniGetByte();
            a->kick = 0;
            DoToneSlide();
            DoVolSlide(dat);
            a->ownper = 1;
        break;

        case UNI_PTEFFECT6:
            dat = UniGetByte();
            DoVibrato();
            DoVolSlide(dat);
            a->ownper = 1;
        break;

        case UNI_PTEFFECT7:
            dat = UniGetByte();
            if(dat&0x0f) a->trmdepth = dat&0xf;
            if(dat&0xf0) a->trmspd = (dat&0xf0)>>2;
            DoTremolo();
            a->ownvol = 1;
        break;

        case UNI_PTEFFECT8:
            dat = UniGetByte();
            if(pf->panflag)
            {   a->panning = dat;
                pf->panning[mp_channel] = dat;
            }
        break;

        case UNI_PTEFFECT9:
            dat = UniGetByte();
            if(dat) a->soffset = (UWORD)dat<<8;
            a->start = a->hioffset | a->soffset;
            if(a->start > a->s->length) a->start = a->s->loopstart;
        break;

        case UNI_PTEFFECTA:
            DoVolSlide(UniGetByte());
        break;

        case UNI_PTEFFECTB:
            dat = UniGetByte();
            if(pf->patdly2) break;
            if(md_noloopeffect) break;
            pf->patbrk = 0;
            pf->sngpos = dat-1;
            pf->posjmp = 3;
        break;

        case UNI_PTEFFECTC:
            dat = UniGetByte();
            if(pf->vbtick) break;
            if(dat>64) dat = 64;
            a->tmpvolume = dat;
        break;

        case UNI_PTEFFECTD:
            dat = UniGetByte();
            if(pf->patdly2) break;
            pf->patbrk = dat;
            if(pf->patbrk>pf->pattrows[mp_channel])
                pf->patbrk = pf->pattrows[mp_channel];
            pf->posjmp = 3;
        break;

        case UNI_PTEFFECTE:
            DoEEffects(UniGetByte());
        break;

        case UNI_PTEFFECTF:
            dat = UniGetByte();

            if(pf->vbtick || pf->patdly2) break;

            if(pf->extspd && dat>=0x20)
                pf->bpm = dat;
            else
            {   if(dat)
                {   pf->sngspd = dat;
                    pf->vbtick = 0;
                }
            }
        break;

        case UNI_S3MEFFECTA:
            DoS3MSpeed(UniGetByte());
        break;

        case UNI_S3MEFFECTD:
            DoS3MVolSlide(UniGetByte());
        break;

        case UNI_S3MEFFECTE:
               DoS3MSlideDn(UniGetByte());
        break;

        case UNI_S3MEFFECTF:
               DoS3MSlideUp(UniGetByte());
        break;

        case UNI_S3MEFFECTI:
            DoS3MTremor(UniGetByte());
            a->ownvol = 1;
        break;

        case UNI_S3MEFFECTQ:
             DoS3MRetrig(UniGetByte());
        break;

        case UNI_S3MEFFECTR:
            dat = UniGetByte();
            if(dat&0x0f) a->trmdepth = dat&0xf;
            if(dat&0xf0) a->trmspd   = (dat&0xf0)>>2;
            DoS3MTremolo();
            a->ownvol = 1;
        break;
                    
        case UNI_S3MEFFECTT:
            DoS3MTempo(UniGetByte());
        break;

        case UNI_S3MEFFECTU:
            dat = UniGetByte();
            if(dat&0x0f) a->vibdepth = dat&0xf;
            if(dat&0xf0) a->vibspd   = (dat&0xf0)>>2;
            DoS3MFineVibrato();
            a->ownper = 1;
        break;

        case UNI_KEYOFF:
            a->keyon &= ~KEY_OFF;
            if(a->i)
            {   if(!(a->i->volflg & EF_ON) || (a->i->volflg & EF_LOOP))
                    a->keyon = 0;
            }
        break;

        case UNI_KEYFADE:
            if(pf->vbtick >= UniGetByte())
            {   a->keyon = 0;
                if(a->i && !(a->i->volflg & EF_ON))
                    a->fadevol = 0;
            }
        break;
        
        case UNI_VOLEFFECTS:
            DoVolEffects(UniGetByte());
        break;

        case UNI_XMEFFECT4:
           dat = UniGetByte();
           if(pf->vbtick)
           {  if(dat&0x0f) a->vibdepth = dat&0xf;
              if(dat&0xf0) a->vibspd   = (dat&0xf0)>>2;
           }
           DoVibrato();
           a->ownper=1;
        break;

        case UNI_XMEFFECTA:
           DoXMVolSlide(UniGetByte());
        break;

        case UNI_XMEFFECTE1:       // xm fineslide up
           dat = UniGetByte();
           if(!pf->vbtick)
           {  if(dat) a->fportupspd = dat; else dat = a->fportupspd;
              a->tmpperiod-=(dat<<2);
           }
         break;

         case UNI_XMEFFECTE2:       // xm fineslide dn
            dat = UniGetByte();
            if(!pf->vbtick)
            {  if(dat) a->fportdnspd = dat; else dat = a->fportdnspd;
               a->tmpperiod+=(dat<<2);
            }
        break;

        case UNI_XMEFFECTEA:       // fine volume slide up
           dat = UniGetByte();
           if(pf->vbtick) break;
           if(dat) a->fslideupspd = dat; else dat = a->fslideupspd;
           a->tmpvolume+=dat;
           if(a->tmpvolume>64) a->tmpvolume = 64;
        break;

        case UNI_XMEFFECTEB:       // fine volume slide dn
           dat = UniGetByte();
           if(pf->vbtick) break;
           if(dat) a->fslidednspd = dat; else dat = a->fslidednspd;
           a->tmpvolume-=dat;
           if(a->tmpvolume<0) a->tmpvolume = 0;
        break;

        case UNI_XMEFFECTG:
           pf->volume = UniGetByte();
        break;

        case UNI_XMEFFECTH:
           DoXMGlobalSlide(UniGetByte());
        break;

        case UNI_XMEFFECTL:
           dat = UniGetByte();
           if(!pf->vbtick && a->i)
           {  UWORD points;
              INSTRUMENT *i = a->i;
              AUDOUT *aout;

              if((aout = a->slave) != NULL)
              {  points = i->volenv[i->volpts-1].pos;
                 aout->venv.p = aout->venv.env[(dat>points) ? points : dat].pos;

                 points = i->panenv[i->panpts-1].pos;
                 aout->penv.p = aout->penv.env[(dat>points) ? points : dat].pos;
              }
           }
        break;
                
        case UNI_XMEFFECTP:
           DoXMPanSlide(UniGetByte());
        break;

        case UNI_XMEFFECTX1:
           DoXMExtraFineSlideUp(UniGetByte());
           a->ownper=1;
        break;

        case UNI_XMEFFECTX2:
           DoXMExtraFineSlideDown(UniGetByte());
           a->ownper = 1;
        break;

        case UNI_ITEFFECTG:
           dat = UniGetByte();
           
           if(!(pf->vbtick==pf->sngspd-1) && (a->newsamp))
           {   a->kick  = 1;
               a->start = 0;
               a->period *= a->c2spd * a->newsamp;
           } else
           {   a->kick  = 0;
           }
           if(dat!=0)
           {   a->slidespeed = dat;
               a->slidespeed<<=2;
           }
               DoITToneSlide();
           a->ownper = 1;
        break;

        case UNI_ITEFFECTH:  // it vibrato
           dat = UniGetByte();
           if(dat&0x0f) a->vibdepth = dat&0xf;
           if(dat&0xf0) a->vibspd   = (dat&0xf0)>>2;
               DoITVibrato();
           a->ownper = 1;
        break;

        case UNI_ITEFFECTI:  // it tremor 
           DoITTremor(UniGetByte());
           a->ownvol = 1;
        break;

        case UNI_ITEFFECTM:
           a->chanvol = UniGetByte();
           if(a->chanvol>64) a->chanvol = 64;
           else if(a->chanvol<0) a->chanvol = 0;
        break;

        case UNI_ITEFFECTN:   // Slide / Fineslide Channel Volume
           DoITChanVolSlide(UniGetByte());
        break;

        case UNI_ITEFFECTO:
            dat = UniGetByte();
            if(dat) a->soffset = (UWORD)dat<<8;
            a->start = a->hioffset | a->soffset;
            if(a->start > a->s->length) a->start = a->s->length;
        break;

        case UNI_ITEFFECTP:  // slide / fineslide channel panning
           DoITPanSlide(UniGetByte());
        break;
                     
        case UNI_ITEFFECTU:  // fine vibrato
           dat = UniGetByte();
           if(dat&0x0f) a->vibdepth = dat&0xf;
           if(dat&0xf0) a->vibspd   = (dat&0xf0)>>2;
               DoITFineVibrato();
           a->ownper = 1;
        break;

        case UNI_ITEFFECTW:  // Slide / Fineslide Global volume
           DoITGlobalSlide(UniGetByte());
        break;

        case UNI_ITEFFECTY:  // The Satanic Panbrello
           dat = UniGetByte();
           if(dat&0x0f) a->panbdepth = (dat&0xf);
           if(dat&0xf0) a->panbspd   = (dat&0xf0)>>4;
           DoITPanbrello();
        break;

        case UNI_ITEFFECTS0:
           DoSSEffects(UniGetByte());
        break;

        default:
           UniSkipOpcode(c);
        break;
    }
}


void MP_HandleTick(void)
{
    AUDOUT     *aout;         // current audout (slave of audtmp) it's working on
    int        t, tr, t2, k;
    ULONG      tmpvol;
    ULONG      period;
    UBYTE      c;
    BOOL       funky;
    SAMPLE     *s;
    INSTRUMENT *i;

    if(isfirst)
    {   // don't handle the very first ticks, this allows the
        // other hardware to settle down so we don't loose any 
        // starting notes
        isfirst--;
        return;
    }

    if(pf==NULL || pf->forbid) return;

    if(++pf->vbtick>=pf->sngspd)
    {   pf->patpos++;
        pf->vbtick = 0;

        // process pattern-delay.  pf->patdly2 is the counter and pf->patdly
        // is the command memory.
        if(pf->patdly)
        {   pf->patdly2 = pf->patdly;
            pf->patdly  = 0;
        }

        if(pf->patdly2)
        {   // patterndelay active
            if(--pf->patdly2) pf->patpos--;    // so turn back pf->patpos by 1
        }

        // Do we have to get a new patternpointer ?
        //  (when pf->patpos reaches 64 or when
        //  a patternbreak is active)
        if( pf->patpos == pf->numrow ) pf->posjmp = 3;

        if( pf->posjmp )
        {   pf->patpos = pf->patbrk;
            pf->sngpos+=(pf->posjmp-2);
            pf->patbrk = pf->posjmp = 0;
            if(pf->sngpos>=pf->numpos)
            {   if(!pf->loop) return;
                pf->sngpos = pf->reppos;
                pf->volume = 128;
            }
            if(pf->sngpos<0) pf->sngpos = pf->numpos-1;
        }

        if(!pf->patdly2)
        {   for(t=0; t<pf->numchn; t++)
            {   UBYTE  inst;
                  
                tr = pf->patterns[(pf->positions[pf->sngpos]*pf->numchn)+t];
                pf->numrow = pf->pattrows[pf->positions[pf->sngpos]];

                mp_channel = t;
                a = &pf->audio[t];
                a->row = (tr<pf->numtrk) ? UniFindRow(pf->tracks[tr],pf->patpos) : NULL;
                a->newsamp = 0;

                if(a->row==NULL) continue;
                UniSetRow(a->row);
                funky = 0;          // Funky is set to indicate note or inst change

                while(c = UniGetByte())
                {   switch(c)
                    {   case UNI_NOTE:
                           funky     |= 1;
                           a->anote   = UniGetByte();
                           a->kick    = 1;
                           a->start   = 0;
                           // retrig tremolo and vibrato waves ?
                           if(!(a->wavecontrol&0x80)) a->trmpos = 0;
                           if(!(a->wavecontrol&0x08)) a->vibpos = 0;
                           if(!a->panbwave) a->panbpos = 0;                                  
                        break;

                        case UNI_INSTRUMENT:
                           funky |= 2;
                           inst   = UniGetByte();
                           if(inst>=pf->numins) break;    // <- safety valve

                           if(a->sample != inst)
                           {   a->newsamp   = a->tmpperiod;
                               a->sample    = inst;
                           }
                           a->i = &pf->instruments[inst];

                           a->retrig    = 0;
                           a->s3mtremor = 0;
                        break;

                        default:
                           UniSkipOpcode(c);
                        break;                        
                    }
                }

                if(funky)
                {   i = a->i;
                	if(i) 
                	{   if(i->samplenumber[a->anote]>=pf->numsmp) continue;
	                    if(a->s != (s = &pf->samples[i->samplenumber[a->anote]]))
	                    {   a->s = s;
	                        a->newsamp = a->period;
	                    }
	
	                    // channel or instrument determined panning ?
	                    a->panning = pf->panning[mp_channel];
	                    if(s->flags & SF_OWNPAN)
	                        a->panning = s->panning;
	                    else if(i->flags & IF_OWNPAN)
	                        a->panning = i->panning;
	
	                    if(i->flags & IF_PITCHPAN)
	                        a->panning = ((a->anote-i->pitpancenter)*i->pitpansep)/8 + a->panning;
	
	                    //a->panning += (a->panning * ((i->rpanvar * (rand() & 255)) / 256));
	                    a->handle   = s->handle;
	                    a->c2spd    = s->c2spd;
	                    a->pitflg   = i->pitflg;
	                    a->volflg   = i->volflg;
	                    a->panflg   = i->panflg;
	
	                    a->nna      = i->nnatype;
	                    a->dca      = i->dca;
	                    a->dct      = i->dct;
	                    
	                    // set some stuff only set if UNI_INSTRUMENT was actually specified
	                    if(funky & 2)
	                    {  // IT's random volume variations:  0:8 bit fixed, and one bit for sign.
	                       a->volume = a->tmpvolume = s->volume + ((s->volume * ((SLONG)i->rvolvar * (SLONG)((rand() & 511)-255))) / 25600);
	                    }
	
	                    a->note = i->samplenote[a->anote];
	                    period  = GetPeriod(a->note,a->c2spd);
	                    a->wantedperiod = period;
	                    a->tmpperiod    = period;
					}
                }
            }
        }
    }

    // Update effects

    for(t=0; t<pf->numchn; t++)
    {   mp_channel = t;
        a = &pf->audio[t];

        //if(a->i==NULL && a->s==NULL) continue;

        if((aout = a->slave) != NULL)
        {   a->fadevol = aout->fadevol;
            a->keyon   = aout->keyon;
            a->period  = aout->period;
        }

        if(a->row==NULL) continue;
        UniSetRow(a->row);

        a->ownper = 0;  a->ownvol = 0;
        pt_playeffects();
        if(!a->ownper) a->period = a->tmpperiod;
        if(!a->ownvol) a->volume = a->tmpvolume;

		if(a->i && a->s)
		  	a->outvolume = (a->volume * a->s->globvol * a->i->globvol) / 1024;  // max val: 256
        else
          	a->outvolume = 0;
   
        if(a->outvolume > 256) a->outvolume = 256;
    }


    a = pf->audio;
    for(t=0; t<pf->numchn; t++, a++)
    {   if(a->kick)
        {
            if(a->slave!=NULL)
            {   aout = a->slave;
                if(aout->nna & 0x3f)
                {   // oh boy, we have to do an NNA
                    // Make sure the old AUDOUT channel knows it has no master now!
                    a->slave = NULL;  // assume the channel is taken by NNA
                    aout->mflag = 0;

                    switch(aout->nna)
                    {   case  NNA_CONTINUE:
                         break;  // continue note, do nothing

                        case  NNA_OFF:               // note off
                           aout->keyon &= ~KEY_OFF;
                           if(!(aout->volflg & EF_ON) || (aout->volflg & EF_LOOP))
                               aout->keyon = 0;
                        break;
    
                        case  NNA_FADE:
                           aout->keyon &= ~KEY_FADE;
                        break;
                    }
                }
            }

            k = 0;
            if(a->dct != DCT_OFF)
            {   for(t2=0; t2<md_numchn; t2++)
                {   if(!(MD_VoiceStopped(t2)) && (pf->control[t2].masterchn == t) && (a->sample == pf->control[t2].sample))
                    {   switch(a->dct)
                        {   case DCT_NOTE:
                               if(a->note == pf->control[t2].note)
                                   k = 1;
                            break;
            
                            case DCT_SAMPLE:
                               if(a->handle == pf->control[t2].handle)
                                   k = 1;
                            break;

                            case DCT_INST:
                                   k = 1;
                            break;
                        }

                        if(k==1)
                        {   k = 0;
                            switch(a->dca)
                            {   case DCA_CUT :
                                   pf->control[t2].fadevol = 0;
                                   a->slave = &pf->control[t2];
                                break;
            
                                case DCA_OFF :
                                   //a->slave = &pf->control[newchn];
                                   pf->control[t2].keyon &= ~KEY_OFF;
                                   if(!(pf->control[t2].volflg & EF_ON) || (pf->control[t2].volflg & EF_LOOP))
                                       pf->control[t2].keyon = 0;
                                break;
            
                                case DCA_FADE:
                                   //a->slave = &pf->control[newchn];
                                   pf->control[t2].keyon &= ~KEY_FADE;
                                break;
                            }
                        }
                    }
                }
            }  // DCT checking
        }  // if a->kick
    }  // for loop

    a = pf->audio;
    for(t=0; t<pf->numchn; t++, a++)
    {   int newchn;

        if(a->kick)
        {   // If no channel was cut above, find an empty or quiet channel here
            if(a->slave==NULL)
            {   if((newchn = MP_FindEmptyChannel(t)) != -1)
                    a->slave = &pf->control[newchn];
            }

            // Assign parts of AUDOUT only done for a KICK!

            if((aout = a->slave) != NULL)
            {   if(aout->mflag && aout->master!=NULL) aout->master->slave = NULL;
                a->slave = aout;
                aout->master    = a;
                aout->masterchn = t;
                aout->mflag     = 1;
                aout->kick      = a->kick;
            }
            a->kick = 0;

        } else
            aout = a->slave;

        if(aout == NULL) continue;
        aout->i         = a->i;
        aout->s         = a->s;
        aout->sample    = a->sample;
        aout->handle    = a->handle;
        aout->period    = a->period;
        aout->panning   = a->panning;
        aout->chanvol   = a->chanvol;
        aout->fadevol   = a->fadevol;
        aout->start     = a->start;
        aout->volflg    = a->volflg;
        aout->panflg    = a->panflg;
        aout->pitflg    = a->pitflg;
        aout->outvolume = a->outvolume;
        aout->keyon     = a->keyon;
        aout->note      = a->note;
        aout->nna       = a->nna;
    }

    // Now set up the actual hardware channel playback information

    for(t=0; t<md_numchn; t++)
    {   SWORD envpan,envvol,envpit;
        SLONG vibval,vibdpt;

        aout = &pf->control[mp_channel = t];
        //a    = &pf->audio[mp_channel = aout->masterchn];
        i = aout->i;
        s = aout->s;

        if(i==NULL || s==NULL) continue;

        if(aout->period<40)    aout->period = 40;
        if(aout->period>50000) aout->period = 50000;
        
        // From here down, it should be driver dependant
        if(aout->kick)
        {   MD_VoicePlay(t,s,aout->start);
            aout->kick    = 0;
            aout->keyon   = KEY_KICK;
            aout->fadevol = 32768;
            aout->aswppos = 0;

            StartEnvelope(&aout->venv, aout->volflg, i->volpts, i->volsusbeg, i->volsusend, i->volbeg, i->volend, i->volenv);
            StartEnvelope(&aout->penv, aout->panflg, i->panpts, i->pansusbeg, i->pansusend, i->panbeg, i->panend, i->panenv);
            StartEnvelope(&aout->cenv, aout->pitflg, i->pitpts, i->pitsusbeg, i->pitsusend, i->pitbeg, i->pitend, i->pitenv);
        }

        envvol = ProcessEnvelope(&aout->venv,256,aout->keyon);
        envpan = ProcessEnvelope(&aout->penv,128,aout->keyon);
        envpit = ProcessEnvelope(&aout->cenv,32,aout->keyon);

        tmpvol=aout->fadevol;         // max 32768
        tmpvol*=aout->chanvol;        // * max 64
        tmpvol*=aout->outvolume;      // * max 256
        tmpvol/=16384L;               // tmpvol is max 32768
        aout->totalvol = tmpvol>>2;   // totalvolume used to determine samplevolume
        tmpvol*=envvol;               // * max 256
        tmpvol*=pf->volume;           // * max 128
        tmpvol/=4194304UL;

        if(aout->masterchn!=-1 && pf->audio[aout->masterchn].muted)   // Channel Muting Line
            MD_VoiceSetVolume(t,0);
        else
            MD_VoiceSetVolume(t,tmpvol);


        if(aout->panning == PAN_SURROUND)
            MD_VoiceSetPanning(t, PAN_SURROUND);
        else
        {   if(aout->penv.flg & EF_ON)
                MD_VoiceSetPanning(t,DoPan(envpan,aout->panning));
            else
                MD_VoiceSetPanning(t,aout->panning);
        }

        if(aout->period && s->vibdepth && (aout->master!=NULL && aout->master->slave==aout))   // Only execute if NNA control
        {   switch(s->vibtype)
            {  case 0:
                   vibval = avibtab[s->avibpos & 127];
                   if(s->avibpos & 0x80) vibval=-vibval;
               break;

               case 1:
                   vibval = 64;
                   if(s->avibpos & 0x80) vibval=-vibval;
               break;

               case 2:
                   vibval = 63-(((s->avibpos + 128) & 255) >> 1);
               break;

               case 3:
                   vibval = (((s->avibpos + 128) & 255) >> 1) - 64;
               break;
            }
        }

        if(s->vibflags & AV_IT)
        {   if((aout->aswppos >> 8) < s->vibdepth)
            {   aout->aswppos += s->vibsweep;
                vibdpt = aout->aswppos;
            } else
                vibdpt = s->vibdepth<<8;
            vibval = (vibval*vibdpt)>>17;

            if(vibval > 0)
                aout->period += vibval;
            else if(vibval < 0)
                aout->period -= vibval;
        } else   // do XM style auto-vibrato
        {   if(aout->keyon & KEY_OFF)
            {   if(aout->aswppos < s->vibsweep)
                {   vibdpt = (aout->aswppos*s->vibdepth) / s->vibsweep;
                    aout->aswppos++;
                } else
                {   vibdpt = s->vibdepth;
                }
            } else
            {   // key-off -> depth becomes 0 if final depth wasn't reached
                // or stays at final level if depth WAS reached
                if(aout->aswppos>=s->vibsweep)
                   vibdpt = s->vibdepth;
                else
                   vibdpt = 0;
             }
             vibval        = (vibval*vibdpt)>>8;
             aout->period -= vibval;
         }
        // update vibrato position
        s->avibpos = (s->avibpos+s->vibrate)&0xff;

        if(aout->cenv.flg & EF_ON)                             
        {   envpit = -32+envpit;
            if(envpit > 0)
                aout->period += envpit;
            else if(envpit < 0)
                aout->period -= envpit;
        }

        MD_VoiceSetFrequency(t,getfrequency(pf->flags,aout->period));
        MD_SetBPM(pf->bpm);

        if(aout->fadevol == 0)     // check for a dead note (fadevol = 0)
            MD_VoiceStop(t);
        
        // if keyfade, start substracting
        // fadeoutspeed from fadevol:
        if(!(aout->keyon & KEY_FADE))
        {   if(aout->fadevol>=i->volfade)
                aout->fadevol-=i->volfade;
            else
                aout->fadevol = 0;
        }
    }
}


BOOL MP_Init(UNIMOD *mf)
{
    int t;

    pf = mf;

    pf->pat_reppos = 0;
    pf->pat_repcnt = 0;
    pf->sngpos     = 0;
    pf->sngspd     = pf->initspeed;

    pf->vbtick  = pf->sngspd;
    pf->patdly  = 0;
    pf->patdly2 = 0;
    pf->bpm     = pf->inittempo;

    pf->patpos = 0;
    pf->posjmp = 2;          // <- make sure the player fetches the first note
    pf->patbrk = 0;

    // Make sure the player doesn't start with garbage:
    if((pf->audio=(AUDNNA *)_mm_calloc(pf->numchn,sizeof(AUDNNA)))==NULL) return 1;
    if((pf->control=(AUDOUT *)_mm_calloc(md_numchn,sizeof(AUDOUT)))==NULL) return 1;

    for(t=0; t<pf->numchn; t++)
    {   pf->audio[t].chanvol = pf->chanvol[t];
        pf->audio[t].panning = pf->panning[t];
    }

    audpool = 0;
    return 0;
}


void MP_Exit(UNIMOD *mf)
{
    if(mf->audio!=NULL)   free(mf->audio);
    if(mf->control!=NULL) free(mf->control);
    mf->audio   = NULL;
    mf->control = NULL;
}


BOOL MP_Ready(UNIMOD *mf)
{
    return(mf->sngpos>=mf->numpos);
}


void MP_NextPosition(UNIMOD *mf)
{
    int t=0;

    mf->forbid = 1;
    mf->posjmp = 3;
    mf->patbrk = 0;
    mf->vbtick = mf->sngspd;
    for(; t<md_numchn; t++) MD_VoiceStop(t);
    for(t=0; t<md_numchn; t++)
    {   mf->control[t].i = NULL;
        mf->control[t].s = NULL;
    }

    for(t=0; t<mf->numchn; t++)
    {   mf->audio[t].i = NULL;
        mf->audio[t].s = NULL;
    }
    mf->forbid = 0;
}


void MP_PrevPosition(UNIMOD *mf)
{
    int t=0;

    mf->forbid = 1;
    mf->posjmp = 1;
    mf->patbrk = 0;
    mf->vbtick = mf->sngspd;
    for(; t<md_numchn; t++) MD_VoiceStop(t);
    //memset(mf->audio,0,sizeof(AUDNNA) * mf->numchn);

    for(t=0; t<md_numchn; t++)
    {   mf->control[t].i = NULL;
        mf->control[t].s = NULL;
    }

    for(t=0; t<mf->numchn; t++)
    {   mf->audio[t].i = NULL;
        mf->audio[t].s = NULL;
    }
    mf->forbid = 0;
}


void MP_SetPosition(UNIMOD *mf, UWORD pos)
{
    int t=0;

    mf->forbid = 1;
    if(pos>=mf->numpos) pos = mf->numpos;
    mf->posjmp = 2;
    mf->patbrk = 0;
    mf->sngpos = pos;
    mf->vbtick = mf->sngspd;
    for(; t<md_numchn; t++) MD_VoiceStop(t);
    for(t=0; t<md_numchn; t++)
    {   mf->control[t].i = NULL;
        mf->control[t].s = NULL;
    }

    for(t=0; t<mf->numchn; t++)
    {   mf->audio[t].i = NULL;
        mf->audio[t].s = NULL;
    }
    mf->forbid = 0;
}    


void MP_UnMute(UNIMOD *mf, SLONG arg1, ...)
{
    va_list ap;
    SLONG   t, arg2, arg3;

    va_start(ap,arg1);

    switch (arg1)
    {   case MUTE_INCLUSIVE:
           if ((!(arg2=va_arg(ap,SLONG))) && (!(arg3=va_arg(ap,SLONG))) || (arg2 > arg3) || (arg3 >= mf->numchn))
           {  va_end(ap);
               return;
           }
           for (;arg2<mf->numchn && arg2<=arg3;arg2++)
              pf->audio[arg2].muted = 0;
        break;

        case MUTE_EXCLUSIVE:
           if ((!(arg2=va_arg(ap,SLONG))) && (!(arg3=va_arg(ap,SLONG))) || (arg2 > arg3) || (arg3 >= mf->numchn))
           {  va_end(ap);
              return;
           }
           for (t=0;t<mf->numchn;t++)
           {  if ((t>=arg2) && (t<=arg3)) continue;
              pf->audio[t].muted = 0;
           }
        break;

        default:
           if (arg1<mf->numchn) pf->audio[arg1].muted = 0;
        break;
    }
    va_end(ap);
    return;
}


void MP_Mute(UNIMOD *mf, SLONG arg1, ...)
{
    va_list ap;
    SLONG t, arg2, arg3;

    va_start(ap,arg1);
    switch (arg1)
    {   case MUTE_INCLUSIVE:
           if ((!(arg2=va_arg(ap,SLONG))) && (!(arg3=va_arg(ap,SLONG))) || (arg2 > arg3) || (arg3 >= mf->numchn))
           {   va_end(ap);
               return;
           }
           for(;arg2<mf->numchn && arg2<=arg3;arg2++)
               pf->audio[arg2].muted = 1;
        break;

        case MUTE_EXCLUSIVE:
           if ((!(arg2=va_arg(ap,SLONG))) && (!(arg3=va_arg(ap,SLONG))) || (arg2 > arg3) || (arg3 >= mf->numchn))
           {   va_end(ap);
               return;
           }
           for (t=0; t<mf->numchn; t++)
           {   if ((t>=arg2) && (t<=arg3)) continue;
               pf->audio[t].muted = 1;
           }
        break;

        default:
           if(arg1<mf->numchn)
               pf->audio[arg1].muted = 1;
        break;
    }
    va_end(ap);
    return;
}


BOOL MP_Muted(UNIMOD *mf, int chan)
{
    return (chan<mf->numchn) ? mf->audio[chan].muted : 1;
}


void MP_PlayStart(UNIMOD *mf)
{
    int t;

    mf->forbid = 0;
    MD_PlayStart();
    if(pf!=mf)
    {   // new song is being started, so completely stop out the old one.
        pf->forbid = 1;
        for(t=0; t<md_numchn; t++) MD_VoiceStop(t);
    }
    pf = mf;
    isfirst = 2;
}


void MP_PlayStop(UNIMOD *mf)
{
    mf->forbid = 1;
    if(md_sfxchn==0) MD_PlayStop();
}


void MP_TogglePause(UNIMOD *mf)
{
    if(mf->forbid == 1)
    {   MD_PlayStart();
        pf = mf;
        isfirst = 2;
        mf->forbid = 0;
    } else
    {   MD_PlayStop();
        mf->forbid = 1;
        pf = NULL;
    }
}


void MP_ToggleMute(UNIMOD *mf, SLONG arg1, ...)
{
    va_list ap;
    SLONG arg2, arg3;
    ULONG t;
       
    va_start(ap,arg1);
    switch (arg1)
    {   case MUTE_INCLUSIVE:
          if ((!(arg2=va_arg(ap,SLONG))) && (!(arg3=va_arg(ap,SLONG))) || (arg2 > arg3) || (arg3 >= mf->numchn))
          {   va_end(ap);
              return;
          }
          for(; arg2<mf->numchn && arg2<=arg3; arg2++)
              pf->audio[arg2].muted = (pf->audio[arg2].muted) ? 0 : 1;
     
        break;

        case MUTE_EXCLUSIVE:
           if ((!(arg2=va_arg(ap,SLONG))) && (!(arg3=va_arg(ap,SLONG))) || (arg2 > arg3) || (arg3 >= mf->numchn))
           {   va_end(ap);
               return;
           }    
           for (t=0;t<mf->numchn;t++)
           {   if((t>=arg2) && (t<=arg3)) continue;
               pf->audio[t].muted = (pf->audio[t].muted) ? 0 : 1;
           }
        break;

        default:
           if(arg1<mf->numchn) 
               pf->audio[arg1].muted = (pf->audio[arg1].muted) ? 0 : 1;
        break;
    }
    va_end(ap);
    return;
}


// --> The following procedures were taken from UNITRK because they
//  -> are ProTracker format specific.

void UniInstrument(UBYTE ins)
// Appends UNI_INSTRUMENT opcode to the unitrk stream.
{
    UniWrite(UNI_INSTRUMENT);
    UniWrite(ins);
}

void UniNote(UBYTE note)
// Appends UNI_NOTE opcode to the unitrk stream.
{
    UniWrite(UNI_NOTE);
    UniWrite(note);
}

void UniPTEffect(UBYTE eff, UBYTE dat)
//  Appends UNI_PTEFFECTX opcode to the unitrk stream.
{
    if(eff!=0 || dat!=0)               // don't write empty effect
    {   UniWrite(UNI_PTEFFECT0+eff);
        UniWrite(dat);
    }
}

void UniVolEffect(UWORD eff, UBYTE dat)
// Appends UNI_VOLEFFECT + effect/dat to unistream.
{
    if(eff!=0 || dat!=0)               // don't write empty effect
    {   UniWrite(UNI_VOLEFFECTS);
        UniWrite(eff); UniWrite(dat);
    }
}

