view playercode/mplayer.c @ 7:de95ce2eacfd

Initial revision
author darius
date Fri, 23 Jan 1998 16:05:09 +0000
parents
children 437e8455d862
line wrap: on
line source

/*
  --> 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"


static void DoNNAEffects(UBYTE dat);

//  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.

static UNIMOD     *pf;           // <- this modfile is being played
static SWORD      mp_channel;    // channel it's working on
static MP_CONTROL *a;            // current AUDTMP it's working on
static int        isfirst;

static MP_VOICE 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_control index of free channel
{
   MP_VOICE *a;
   ULONG t,k,tvol,p,pp;

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

   for(t=0; t<md_sngchn; t++)
   {   if(!(pf->voice[t].kick) && Voice_Stopped(t))
       {   return t;
       }
   }

   tvol = 0xffffffUL;  t = 0;  p = 0;  a = pf->voice;
   for(k=0; k<md_sngchn; 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->keyoff & 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;
}


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 speed)
{
    UBYTE n, o;
    ULONG period;

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

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


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

    return getoldperiod(note,speed);
}


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, UBYTE keyoff)
{
    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 = ((t->flg & EF_SUSTAIN) && !(keyoff & KEY_OFF)) ? 0 : 1;
}


static SWORD ProcessEnvelope(ENVPR *t, SWORD v, UBYTE keyoff)

// This procedure processes all envelope types, include volume, pitch, and
// panning.  Envelopes are defined by a set of points, each with a magnitude
// [relating either to volume, panniong position, or pitch modifier] and a
// tick position.
//
//  Envelopes work in the following manner:
//
// (a) Each tick the envelope is moved a point further in its progression.
//   1. For an accurate progression, magnitudes between two envelope points
//      are interpolated.
//
// (b) When progression reaches a defined point on the envelope, values
//     are shifted to interpolate between this point and the next,
//     and checks for loops or envelope end are done.
//
// Misc:
//   Sustain loops are loops that are only active as long as the keyoff
//   flag is clear.  When a volume envelope terminates, so does the current
//   fadeout.

{
    if(t->flg & EF_ON)
    {   UBYTE  a, b;        // actual points in the envelope
        UWORD  p;           // the 'tick counter' - real point being played

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

        // compute the current envelope value between points a and b

        if(a == b)
            v = t->env[a].val;
        else
            v = InterpolateEnv(p, &t->env[a], &t->env[b]);

        p++;

        // pointer reached point b?

        if(p >= t->env[b].pos)
        {   a = b++;            // shift points a and b

            // Check for loops, sustain loops, or end of envelope.
            
            if((t->flg & EF_SUSTAIN) && !(keyoff & 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((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(b >= t->pts)
                {   if((t->flg & EF_VOLENV) && (mp_channel != -1))
                    {   pf->voice[mp_channel].keyoff |= KEY_FADE;
                        if(v==0)
                            pf->voice[mp_channel].fadevol = 0;
                    }
                    b--;  p--;
                }
            }
        }
        t->a = a;
        t->b = b;
        t->p = p;
    }
    return v;
}


ULONG getfrequency(UBYTE flags, ULONG period)

// XM linear period to frequency conversion

{
   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->speed=finetune[nib];
//            a->tmpperiod=GetPeriod(a->note,pf->samples[a->sample].transpose,a->speed);
        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;
               a->notedelay = 0;
           } else if(pf->vbtick==0)
           {   //a->kick = 0;
               a->notedelay = 1;
           }
        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->speed);
        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(inf)
    {   a->s3mrtgslide = hi;
        a->s3mrtgspeed = lo;
    }

    // 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

            if(!a->kick) a->kick = 2;
            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_SURROUND) ? 128 : 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 == PAN_SURROUND) ? 128 : 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<<2) > 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 << 2;   // then slide up
    } else
    {   a->period += a->slidespeed << 2;   // dist<0 -> slide down
    }
    a->tmpperiod = a->period;
}


static void DoSSEffects(UBYTE dat)

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

{
    UBYTE inf,c;

    inf = dat&0xf;
    c   = dat>>4;

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

    switch(c)
    {   case SS_GLISSANDO:      // S1x set glissando voice
           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(inf != 0) a->slidespeed   = inf;

            if(a->period != 0)
            {   if(!(pf->vbtick==pf->sngspd-1) && (a->newsamp))
                {   a->kick  = 1;
                    a->start = -1;
                    //a->period *= a->speed * a->newsamp;
                } else
                    a->kick  = 0;
    
                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();

            if(dat!=0)
            {   a->portspeed   = dat;
                a->portspeed <<= 2;
            }

            if(a->period != 0)
            {   a->kick = 0;                   // temp XM fix
                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->s != NULL) && (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;
            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->keyoff |= KEY_OFF;
            if(a->i != NULL)
            {   if(!(a->i->volflg & EF_ON) || (a->i->volflg & EF_LOOP))
                    a->keyoff = KEY_KILL;
            }
        break;

        case UNI_KEYFADE:
            if(pf->vbtick >= UniGetByte())
            {   a->keyoff = KEY_KILL;
                if((a->i != NULL) && !(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!=NULL)
           {   UWORD points;
               INSTRUMENT *i = a->i;
               MP_VOICE *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(dat != 0) a->slidespeed = dat;

           if(a->period != 0)
           {   if((pf->vbtick < 1) && (a->newsamp))
               {   a->kick  = 1;
                   a->start = -1;
                   //a->period *= a->speed * a->newsamp;
               } else
                   a->kick = 0;

               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_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;
    }
}


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

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

        case 0x1:       // Past note off
           for(t=0; t<md_sngchn; t++)
              if(pf->voice[t].master == a)
              {  pf->voice[t].keyoff |= KEY_OFF;
                 if(!(pf->voice[t].venv.flg & EF_ON))
                    pf->voice[t].keyoff = KEY_KILL;
              }
        break;

        case 0x2:       // Past note fade
           for(t=0; t<md_sngchn; t++)
              if(pf->voice[t].master == a)
                 pf->voice[t].keyoff |= 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;
    }
}


void Player_HandleTick(void)
{
    MP_VOICE   *aout;         // current audout (slave of audtmp) it's working on
    int        t, tr, t2, k;
    ULONG      tmpvol, 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;
                if((pf->sngpos = pf->reppos) == 0)
                {   pf->volume = pf->initvolume;
                    pf->sngspd = pf->initspeed;
                    pf->bpm    = pf->inittempo;
                }
            }
            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->control[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   = -1;

                           // 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

                           a->i = (pf->flags & UF_INST) ? &pf->instruments[inst] : NULL;
                           a->retrig    = 0;
                           a->s3mtremor = 0;
                           a->sample    = inst;
                        break;

                        default:
                           UniSkipOpcode(c);
                        break;
                    }
                }

                if(funky)
                {   i = a->i;
                    if(i != NULL)
                    {   if(i->samplenumber[a->anote] >= pf->numsmp) continue;
                        s = &pf->samples[i->samplenumber[a->anote]];
                        a->note = i->samplenote[a->anote];
                    } else
                    {   a->note = a->anote;
                        s = &pf->samples[a->sample];
                    }

                    if(a->s != s)
                    {   a->s = s;
                        a->newsamp = a->period;
                    }

                    // channel or instrument determined panning ?

                    a->panning = pf->panning[t];
                    if(s->flags & SF_OWNPAN)
                        a->panning = s->panning;
                    else if((i != NULL) && (i->flags & IF_OWNPAN))
                        a->panning = i->panning;

                    a->handle    = s->handle;
                    a->speed     = s->speed;

                    if(i != NULL)
                    {   if(i->flags & IF_PITCHPAN)
                            a->panning += ((a->anote-i->pitpancenter) * i->pitpansep) / 8;
                        a->pitflg   = i->pitflg;
                        a->volflg   = i->volflg;
                        a->panflg   = i->panflg;
                        a->nna      = i->nnatype;
                        a->dca      = i->dca;
                        a->dct      = i->dct;
                    } else
                    {   a->pitflg   = 0;
                        a->volflg   = 0;
                        a->panflg   = 0;
                        a->nna      = 0;
                        a->dca      = 0;
                        a->dct      = 0;
                    }

                    if(funky & 2)
                    {   // IT's random volume variations:  0:8 bit fixed, and one bit for sign.
                        a->volume    = s->volume;
                        a->tmpvolume = s->volume;
                        if((s != NULL) && (i != NULL))
                        {   a->volume = a->tmpvolume = s->volume + ((s->volume * ((SLONG)i->rvolvar * (SLONG)((rand() & 511)-255))) / 25600);
                            if(a->panning != PAN_SURROUND) a->panning += ((a->panning * ((SLONG)i->rpanvar * (SLONG)((rand() & 511)-255))) / 25600);
                        }
                    }

                    period          = GetPeriod(a->note, a->speed);
                    a->wantedperiod = period;
                    a->tmpperiod    = period;
                    a->keyoff       = KEY_KICK;
                }
            }
        }
    }

    // Update effects

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

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

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

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

        if(a->s != NULL)
        {   if(a->i != NULL)
                a->outvolume = (a->volume * a->s->globvol * a->i->globvol) / 1024;  // max val: 256
            else
                a->outvolume = (a->volume * a->s->globvol) / 16;  // max val: 256
            if(a->outvolume > 256) a->volume = 256;
        }
    }


    a = pf->control;
    if(pf->flags & UF_NNA)
    {   for(t=0; t<pf->numchn; t++, a++)
        {   if(a->kick == 1)
            {   if(a->slave != NULL)
                {   aout = a->slave;
                    if(aout->nna & 0x3f)
                    {   // oh boy, we have to do an NNA
                        // Make sure the old MP_VOICE 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->keyoff |= KEY_OFF;
                               if(!(aout->volflg & EF_ON) || (aout->volflg & EF_LOOP))
                                   aout->keyoff = KEY_KILL;
                            break;
    
                            case  NNA_FADE:
                               aout->keyoff |= KEY_FADE;
                            break;
                        }
                    }
                }
    
                k = 0;
                if(a->dct != DCT_OFF)
                {   for(t2=0; t2<md_sngchn; t2++)
                    {   if(!(Voice_Stopped(t2)) && (pf->voice[t2].masterchn == t) && (a->sample == pf->voice[t2].sample))
                        {   switch(a->dct)
                            {   case DCT_NOTE:
                                   if(a->note == pf->voice[t2].note)
                                       k = 1;
                                break;
                
                                case DCT_SAMPLE:
                                   if(a->handle == pf->voice[t2].handle)
                                       k = 1;
                                break;
    
                                case DCT_INST:
                                       k = 1;
                                break;
                            }
    
                            if(k==1)
                            {   k = 0;
                                switch(a->dca)
                                {   case DCA_CUT :
                                       pf->voice[t2].fadevol = 0;
                                       a->slave = &pf->voice[a->slavechn=t2];
                                    break;
    
                                    case DCA_OFF :
                                       //a->slave = &pf->voice[newchn];
                                       pf->voice[t2].keyoff |= KEY_OFF;
                                       if(!(pf->voice[t2].volflg & EF_ON) || (pf->voice[t2].volflg & EF_LOOP))
                                           pf->voice[t2].keyoff = KEY_KILL;
                                    break;
    
                                    case DCA_FADE:
                                       //a->slave = &pf->voice[newchn];
                                       pf->voice[t2].keyoff |= KEY_FADE;
                                    break;
                                }
                            }
                        }
                    }
                }  // DCT checking
            }  // if a->kick
        }  // for loop
    }

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

        if(a->notedelay) continue;

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

            // Assign parts of MP_VOICE 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;
            }
        } else
        {   aout = a->slave;
        }

        if(aout != NULL)
        {   aout->kick      = a->kick;
            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->volume    = a->outvolume;
            aout->keyoff    = a->keyoff;
            aout->note      = a->note;
            aout->nna       = a->nna;
        }
        a->kick = 0;

    }

    // Now set up the actual hardware channel playback information

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

        aout = &pf->voice[mp_channel = t];
        i = aout->i;
        s = aout->s;

        if(s==NULL) continue;

        if(aout->period < 40)    aout->period = 40;
        if(aout->period > 50000) aout->period = 50000;
        
        if(aout->kick)
        {   Voice_Play(t,s,(aout->start == -1) ? ((s->flags & SF_UST_LOOP) ? s->loopstart : 0) : aout->start);
            //aout->keyoff   = KEY_KICK;
            aout->fadevol  = 32768;
            aout->aswppos  = 0;

            if((i != NULL) && (aout->kick != 2))
            {   StartEnvelope(&aout->venv, aout->volflg, i->volpts, i->volsusbeg, i->volsusend, i->volbeg, i->volend, i->volenv, aout->keyoff);
                StartEnvelope(&aout->penv, aout->panflg, i->panpts, i->pansusbeg, i->pansusend, i->panbeg, i->panend, i->panenv, aout->keyoff);
                StartEnvelope(&aout->cenv, aout->pitflg, i->pitpts, i->pitsusbeg, i->pitsusend, i->pitbeg, i->pitend, i->pitenv, aout->keyoff);
            }
            aout->kick    = 0;
        }

        if(i != NULL)
        {   envvol = ProcessEnvelope(&aout->venv,256,aout->keyoff);
            envpan = ProcessEnvelope(&aout->penv,128,aout->keyoff);
            envpit = ProcessEnvelope(&aout->cenv,32,aout->keyoff);
        }

        tmpvol  = aout->fadevol;        // max 32768
        tmpvol *= aout->chanvol;        // * max 64
        tmpvol *= aout->volume;         // * 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->control[aout->masterchn].muted)   // Channel Muting Line
            Voice_SetVolume(t,0);
        else
            Voice_SetVolume(t,tmpvol);


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

        if(aout->period && s->vibdepth)
        {   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) >> 16;
            if(aout->mflag)
            {   if(!(pf->flags & UF_LINEAR)) vibval>>=1;
                aout->period -= vibval;
            }
        } else   // do XM style auto-vibrato
        {   if(!(aout->keyoff & 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 = envpit-32;
            aout->period -= envpit;
        }

        Voice_SetFrequency(t,getfrequency(pf->flags,aout->period));

        if(aout->fadevol == 0)     // check for a dead note (fadevol = 0)
            Voice_Stop(t);
        else
        {   // if keyfade, start substracting
            // fadeoutspeed from fadevol:

            if((i != NULL) && (aout->keyoff & KEY_FADE))
            {   if(aout->fadevol >= i->volfade)
                    aout->fadevol -= i->volfade;
                else
                    aout->fadevol = 0;
            }
        }

        MD_SetBPM(pf->bpm);
    }
}


BOOL Player_Init(UNIMOD *mf)
{
    int  t;

    mf->extspd     = 1;
    mf->panflag    = 1;
    mf->loop       = 0;

    mf->pat_reppos = 0;
    mf->pat_repcnt = 0;
    mf->sngpos     = 0;
    mf->sngspd     = mf->initspeed;
    mf->volume     = mf->initvolume;

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

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

    // Make sure the player doesn't start with garbage:

    if((mf->control=(MP_CONTROL *)_mm_calloc(mf->numchn,sizeof(MP_CONTROL)))==NULL) return 1;
    if((mf->voice=(MP_VOICE *)_mm_calloc(md_sngchn,sizeof(MP_VOICE)))==NULL) return 1;

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

    return 0;
}


void Player_Exit(UNIMOD *mf)
{
    if(mf==NULL) return;
    if(mf==pf)
    {   Player_Stop();
        pf = NULL;
    }
    if(mf->control!=NULL) free(mf->control);
    if(mf->voice!=NULL) free(mf->voice);
    mf->control = NULL;
    mf->voice = NULL;

}


void Player_SetVolume(int volume)
{
    if(pf==NULL) return;

    if(volume > 128) volume = 128;
    if(volume < 0) volume = 0;

    pf->volume = volume;
}


UNIMOD *Player_GetUnimod(void)
{
    return pf;
}


void Player_Start(UNIMOD *mf)
{
    int t;

    if(!MikMod_Active())
    {   isfirst = 2;
        MikMod_EnableOutput();
    }

    if(mf==NULL) return;

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

    pf = mf;
}


void Player_Stop(void)
{
    if(md_sfxchn==0) MikMod_DisableOutput();
    if(pf != NULL) pf->forbid = 1;
    pf = NULL;
}


BOOL MP_Playing(UNIMOD *mf)
{
    if((mf==NULL) || (mf!=pf)) return 0;
    return(!(mf->sngpos>=mf->numpos));
}


BOOL Player_Active(void)
{
    if(pf==NULL) return 0;
    return(!(pf->sngpos>=pf->numpos));
}


void MP_NextPosition(UNIMOD *mf)
{
    int t;

    if(mf==NULL) return;
    mf->forbid = 1;
    mf->posjmp = 3;
    mf->patbrk = 0;
    mf->vbtick = mf->sngspd;

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

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


void Player_NextPosition(void)
{
    MP_NextPosition(pf);
}


void MP_PrevPosition(UNIMOD *mf)
{
    int t;

    if(mf==NULL) return;
    mf->forbid = 1;
    mf->posjmp = 1;
    mf->patbrk = 0;
    mf->vbtick = mf->sngspd;

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

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

    mf->forbid = 0;
}


void Player_PrevPosition(void)
{
    MP_PrevPosition(pf);
}


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

    if(mf==NULL) return;
    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=0; t<md_sngchn; t++)
    {   Voice_Stop(t);
        mf->voice[t].i = NULL;
        mf->voice[t].s = NULL;
    }

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

    mf->forbid = 0;
}    


void Player_SetPosition(UWORD pos)
{
    MP_SetPosition(pf,pos);
}


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

    va_start(ap,arg1);

    if(mf != NULL)
    {   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++)
                   mf->control[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;
                   mf->control[t].muted = 0;
               }
            break;
    
            default:
               if(arg1<mf->numchn) mf->control[arg1].muted = 0;
            break;
        }
    }
    va_end(ap);

    return;
}


void Player_Unmute(SLONG arg1, ...)
{
   va_list argptr;
   MP_Unmute(pf,arg1, argptr);
   va_end(argptr);
}


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

    va_start(ap,arg1);

    if(mf != NULL)
    {   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++)
                   mf->control[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;
                   mf->control[t].muted = 1;
               }
            break;
    
            default:
               if(arg1<mf->numchn)
                   mf->control[arg1].muted = 1;
            break;
        }
    }
    va_end(ap);

    return;
}


void Player_Mute(SLONG arg1, ...)
{
   va_list argptr;
   MP_Mute(pf,arg1, argptr);
   va_end(argptr);
}


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

    if(mf != NULL)
    {   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++)
                  mf->control[arg2].muted = (mf->control[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;
                   mf->control[t].muted = (mf->control[t].muted) ? 0 : 1;
               }
            break;
    
            default:
               if(arg1<mf->numchn) 
                   mf->control[arg1].muted = (mf->control[arg1].muted) ? 0 : 1;
            break;
        }
    }
    va_end(ap);

    return;
}


void Player_ToggleMute(SLONG arg1, ...)
{
   va_list argptr;
   MP_ToggleMute(pf,arg1, argptr);
   va_end(argptr);
}


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


BOOL Player_Muted(int chan)
{
    if(pf==NULL) return 1;
    return (chan<pf->numchn) ? pf->control[chan].muted : 1;
}


int MP_GetChannelVoice(UNIMOD *mf, int chan)
{
    if(mf==NULL) return 0;
    return mf->control[chan].slavechn;
}


int Player_GetChannelVoice(int chan)
{
    if(pf==NULL) return 0;
    return pf->control[chan].slavechn;
}


void Player_TogglePause(void)
{
    if(pf==NULL) return;
    if(pf->forbid == 1)
        pf->forbid = 0;
    else
        pf->forbid = 1;
}


// --> 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);
    }
}