/*	MikMod sound library
	(c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS
	for complete list.

	This library is free software; you can redistribute it and/or modify
	it under the terms of the GNU Library General Public License as
	published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Library General Public License for more details.

	You should have received a copy of the GNU Library General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id$

  The Protracker Player Driver

  The protracker driver supports all base Protracker 3.x commands and features.

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <stdarg.h>
#ifdef SRANDOM_IN_MATH_H
#include <math.h>
#else
#include <stdlib.h>
#endif

#include "mikmod_internals.h"

#ifdef SUNOS
extern int fprintf(FILE *, const char *, ...);
extern long int random(void);
#endif

/* The currently playing module */
MODULE *pf = NULL;

#define NUMVOICES(mod) (md_sngchn < (mod)->numvoices ? md_sngchn : (mod)->numvoices)

#define	HIGH_OCTAVE		2	/* number of above-range octaves */

static	UWORD oldperiods[OCTAVE*2]={
	0x6b00, 0x6800, 0x6500, 0x6220, 0x5f50, 0x5c80,
	0x5a00, 0x5740, 0x54d0, 0x5260, 0x5010, 0x4dc0,
	0x4b90, 0x4960, 0x4750, 0x4540, 0x4350, 0x4160,
	0x3f90, 0x3dc0, 0x3c10, 0x3a40, 0x38b0, 0x3700
};

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

/* returns a random value between 0 and ceil-1, ceil must be a power of two */
static int getrandom(int ceilval)
{
#if defined(HAVE_SRANDOM) && !defined(_MIKMOD_AMIGA)
	return random()&(ceilval-1);
#else
	return (rand()*ceilval)/(RAND_MAX+1.0);
#endif
}

/*	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 foreground channel is a bonus x4
	4)	an active envelope with keyoff is a handicap -x2
*/
static int MP_FindEmptyChannel(MODULE *mod)
{
	MP_VOICE *a;
	ULONG t,k,tvol,pp;

	for (t=0;t<NUMVOICES(mod);t++)
		if (((mod->voice[t].main.kick==KICK_ABSENT)||
			 (mod->voice[t].main.kick==KICK_ENV))&&
		   Voice_Stopped_internal(t))
			return t;

	tvol=0xffffffUL;t=-1;a=mod->voice;
	for (k=0;k<NUMVOICES(mod);k++,a++) {
		/* allow us to take over a nonexisting sample */
		if (!a->main.s)
			return k;

		if ((a->main.kick==KICK_ABSENT)||(a->main.kick==KICK_ENV)) {
			pp=a->totalvol<<((a->main.s->flags&SF_LOOP)?1:0);
			if ((a->master)&&(a==a->master->slave))
				pp<<=2;

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

	if (tvol>8000*7) return -1;
	return t;
}

static SWORD Interpolate(SWORD p,SWORD p1,SWORD p2,SWORD v1,SWORD v2)
{
	if ((p1==p2)||(p==p1)) return v1;
	return v1+((SLONG)((p-p1)*(v2-v1))/(p2-p1));
}

UWORD getlinearperiod(UWORD note,ULONG fine)
{
	UWORD t;

	t=((20L+2*HIGH_OCTAVE)*OCTAVE+2-note)*32L-(fine>>1);
	return t;
}

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

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

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

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

static UWORD getoldperiod(UWORD note,ULONG speed)
{
	UWORD n,o;

	/* This happens sometimes on badly converted AMF, and old MOD */
	if (!speed) {
#ifdef MIKMOD_DEBUG
		fprintf(stderr,"\rmplayer: getoldperiod() called with note=%d, speed=0 !\n",note);
#endif
		return 4242; /* <- prevent divide overflow.. (42 hehe) */
	}

	n=note%(2*OCTAVE);
	o=note/(2*OCTAVE);
	return ((8363L*(ULONG)oldperiods[n])>>o)/speed;
}

static UWORD GetPeriod(UWORD flags, UWORD note, ULONG speed)
{
	if (flags & UF_XMPERIODS) {
		if (flags & UF_LINEAR)
			return getlinearperiod(note, speed);
		else
			return getlogperiod(note, speed);
	} else
		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)
{
	int newpan;

	newpan=pan+(((envpan-PAN_CENTER)*(128-abs(pan-PAN_CENTER)))/128);

	return (newpan<PAN_LEFT)?PAN_LEFT:(newpan>PAN_RIGHT?PAN_RIGHT:newpan);
}

static SWORD 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;

	/* Imago Orpheus sometimes stores an extra initial point in the envelope */
	if ((t->pts>=2)&&(t->env[0].pos==t->env[1].pos)) {
		t->a++;
		t->b++;
	}

	/* Fit in the envelope, still */
	if (t->a >= t->pts)
		t->a = t->pts - 1;
	if (t->b >= t->pts)
		t->b = t->pts-1;

	return t->env[t->a].val;
}

/* 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, panning 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. 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.
*/
static SWORD ProcessEnvelope(MP_VOICE *aout, ENVPR *t, SWORD v)
{
	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;

		/*
		 * Sustain loop on one point (XM type).
		 * Not processed if KEYOFF.
		 * Don't move and don't interpolate when the point is reached
		 */
		if ((t->flg & EF_SUSTAIN) && t->susbeg == t->susend &&
		   (!(aout->main.keyoff & KEY_OFF) && p == t->env[t->susbeg].pos)) {
			v = t->env[t->susbeg].val;
		} else {
			/*
			 * All following situations will require interpolation between
			 * two envelope points.
			 */

			/*
			 * Sustain loop between two points (IT type).
			 * Not processed if KEYOFF.
			 */
			/* if we were on a loop point, loop now */
			if ((t->flg & EF_SUSTAIN) && !(aout->main.keyoff & KEY_OFF) &&
			   a >= t->susend) {
				a = t->susbeg;
				b = (t->susbeg==t->susend)?a:a+1;
				p = t->env[a].pos;
				v = t->env[a].val;
			} else
			/*
			 * Regular loop.
			 * Be sure to correctly handle single point loops.
			 */
			if ((t->flg & EF_LOOP) && a >= t->end) {
				a = t->beg;
				b = t->beg == t->end ? a : a + 1;
				p = t->env[a].pos;
				v = t->env[a].val;
			} else
			/*
			 * Non looping situations.
			 */
			if (a != b)
				v = InterpolateEnv(p, &t->env[a], &t->env[b]);
			else
				v = t->env[a].val;

			/*
			 * Start to fade if the volume envelope is finished.
			 */
			if (p >= t->env[t->pts - 1].pos) {
				if (t->flg & EF_VOLENV) {
					aout->main.keyoff |= KEY_FADE;
					if (!v)
						aout->main.fadevol = 0;
				}
			} else {
				p++;
				/* did pointer reach point b? */
				if (p >= t->env[b].pos)
					a = b++; /* shift points a and b */
			}
			t->a = a;
			t->b = b;
			t->p = p;
		}
	}
	return v;
}

/* XM linear period to frequency conversion */
ULONG getfrequency(UWORD flags,ULONG period)
{
	if (flags & UF_LINEAR) {
		SLONG shift = ((SLONG)period / 768) - HIGH_OCTAVE;

		if (shift >= 0)
			return lintab[period % 768] >> shift;
		else
			return lintab[period % 768] << (-shift);
	} else
		return (8363L*1712L)/(period?period:1);
}

/*========== Protracker effects */

static void DoArpeggio(UWORD tick, UWORD flags, MP_CONTROL *a, UBYTE style)
{
	UBYTE note=a->main.note;

	if (a->arpmem) {
		switch (style) {
		case 0:		/* mod style: N, N+x, N+y */
			switch (tick % 3) {
			/* case 0: unchanged */
			case 1:
				note += (a->arpmem >> 4);
				break;
			case 2:
				note += (a->arpmem & 0xf);
				break;
			}
			break;
		case 3:		/* okt arpeggio 3: N-x, N, N+y */
			switch (tick % 3) {
			case 0:
				note -= (a->arpmem >> 4);
				break;
			/* case 1: unchanged */
			case 2:
				note += (a->arpmem & 0xf);
				break;
			}
			break;
		case 4:		/* okt arpeggio 4: N, N+y, N, N-x */
			switch (tick % 4) {
			/* case 0, case 2: unchanged */
			case 1:
				note += (a->arpmem & 0xf);
				break;
			case 3:
				note -= (a->arpmem >> 4);
				break;
			}
			break;
		case 5:		/* okt arpeggio 5: N-x, N+y, N, and nothing at tick 0 */
			if (!tick)
				break;
			switch (tick % 3) {
			/* case 0: unchanged */
			case 1:
				note -= (a->arpmem >> 4);
				break;
			case 2:
				note += (a->arpmem & 0xf);
				break;
			}
			break;
		}
		a->main.period = GetPeriod(flags, (UWORD)note << 1, a->speed);
		a->ownper = 1;
	}
}

static int DoPTEffect0(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat = UniGetByte();
	if (!tick) {
		if (!dat && (flags & UF_ARPMEM))
			dat=a->arpmem;
		else
			a->arpmem=dat;
	}
	if (a->main.period)
		DoArpeggio(tick, flags, a, 0);

	return 0;
}

static int DoPTEffect1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat = UniGetByte();
	if (!tick && dat)
		a->slidespeed = (UWORD)dat << 2;
	if (a->main.period)
		if (tick)
			a->tmpperiod -= a->slidespeed;

	return 0;
}

static int DoPTEffect2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat = UniGetByte();
	if (!tick && dat)
		a->slidespeed = (UWORD)dat << 2;
	if (a->main.period)
		if (tick)
			a->tmpperiod += a->slidespeed;

	return 0;
}

static void DoToneSlide(UWORD tick, MP_CONTROL *a)
{
	if (!a->main.fadevol)
		a->main.kick = (a->main.kick == KICK_NOTE)? KICK_NOTE : KICK_KEYOFF;
	else
		a->main.kick = (a->main.kick == KICK_NOTE)? KICK_ENV : KICK_ABSENT;

	if (tick != 0) {
		int dist;

		/* We have to slide a->main.period towards a->wantedperiod, so compute
		   the difference between those two values */
		dist=a->main.period-a->wantedperiod;

		/* if they are equal or if portamentospeed is too big ...*/
		if (dist == 0 || a->portspeed > abs(dist))
			/* ...make tmpperiod equal tperiod */
			a->tmpperiod=a->main.period=a->wantedperiod;
		else if (dist>0) {
			a->tmpperiod-=a->portspeed;
			a->main.period-=a->portspeed; /* dist>0, slide up */
		} else {
			a->tmpperiod+=a->portspeed;
			a->main.period+=a->portspeed; /* dist<0, slide down */
		}
	} else
		a->tmpperiod=a->main.period;
	a->ownper = 1;
}

static int DoPTEffect3(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if ((!tick)&&(dat)) a->portspeed=(UWORD)dat<<2;
	if (a->main.period)
		DoToneSlide(tick, a);

	return 0;
}

static void DoVibrato(UWORD tick, MP_CONTROL *a)
{
	UBYTE q;
	UWORD temp = 0;	/* silence warning */

	if (!tick)
		return;

	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: /* random wave */
		temp=getrandom(256);
		break;
	}

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

	if (a->vibpos>=0)
		a->main.period=a->tmpperiod+temp;
	else
		a->main.period=a->tmpperiod-temp;
	a->ownper = 1;

	if (tick != 0)
		a->vibpos+=a->vibspd;
}

static int DoPTEffect4(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (!tick) {
		if (dat&0x0f) a->vibdepth=dat&0xf;
		if (dat&0xf0) a->vibspd=(dat&0xf0)>>2;
	}
	if (a->main.period)
		DoVibrato(tick, a);

	return 0;
}

static void DoVolSlide(MP_CONTROL *a, UBYTE dat)
{
	if (dat&0xf) {
		a->tmpvolume-=(dat&0x0f);
		if (a->tmpvolume<0)
			a->tmpvolume=0;
	} else {
		a->tmpvolume+=(dat>>4);
		if (a->tmpvolume>64)
			a->tmpvolume=64;
	}
}

static int DoPTEffect5(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (a->main.period)
		DoToneSlide(tick, a);

	if (tick)
		DoVolSlide(a, dat);

	return 0;
}

/* DoPTEffect6 after DoPTEffectA */

static int DoPTEffect7(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;
	UBYTE q;
	UWORD temp = 0;	/* silence warning */

	dat=UniGetByte();
	if (!tick) {
		if (dat&0x0f) a->trmdepth=dat&0xf;
		if (dat&0xf0) a->trmspd=(dat&0xf0)>>2;
	}
	if (a->main.period) {
		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: /* random wave */
			temp=getrandom(256);
			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;
		}
		a->ownvol = 1;

		if (tick)
			a->trmpos+=a->trmspd;
	}

	return 0;
}

static int DoPTEffect8(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat = UniGetByte();
	if (mod->panflag)
		a->main.panning = mod->panning[channel] = dat;

	return 0;
}

static int DoPTEffect9(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (!tick) {
		if (dat) a->soffset=(UWORD)dat<<8;
		a->main.start=a->hioffset|a->soffset;

		if ((a->main.s)&&(a->main.start>a->main.s->length))
			a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)?
			    a->main.s->loopstart:a->main.s->length;
	}

	return 0;
}

static int DoPTEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (tick)
		DoVolSlide(a, dat);

	return 0;
}

static int DoPTEffect6(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	if (a->main.period)
		DoVibrato(tick, a);
	DoPTEffectA(tick, flags, a, mod, channel);

	return 0;
}

static int DoPTEffectB(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();

	if (tick || mod->patdly2)
		return 0;

	/* Vincent Voois uses a nasty trick in "Universal Bolero" */
	if (dat == mod->sngpos && mod->patbrk == mod->patpos)
		return 0;

	if (!mod->loop && !mod->patbrk &&
	    (dat < mod->sngpos ||
		 (mod->sngpos == (mod->numpos - 1) && !mod->patbrk) ||
	     (dat == mod->sngpos && (flags & UF_NOWRAP)) ) )
	{
		/* if we don't loop, better not to skip the end of the
		   pattern, after all... so:
		mod->patbrk=0; */
		mod->posjmp=3;
	} else {
		/* if we were fading, adjust... */
		if (mod->sngpos == (mod->numpos-1))
			mod->volume=mod->initvolume>128?128:mod->initvolume;
		mod->sngpos=dat;
		mod->posjmp=2;
		mod->patpos=0;
	}

	return 0;
}

static int DoPTEffectC(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (tick) return 0;
	if (dat==(UBYTE)-1) a->anote=dat=0; /* note cut */
	else if (dat>64) dat=64;
	a->tmpvolume=dat;

	return 0;
}

static int DoPTEffectD(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if ((tick)||(mod->patdly2)) return 0;
	if ((mod->positions[mod->sngpos]!=LAST_PATTERN)&&
	    (dat>mod->pattrows[mod->positions[mod->sngpos]])) {
		dat=mod->pattrows[mod->positions[mod->sngpos]];
	}
	mod->patbrk=dat;
	if (!mod->posjmp) {
		/* don't ask me to explain this code - it makes
		   backwards.s3m and children.xm (heretic's version) play
		   correctly, among others. Take that for granted, or write
		   the page of comments yourself... you might need some
		   aspirin - Miod */
		if ((mod->sngpos==mod->numpos-1)&&(dat)&&
		    ((mod->loop) || (mod->positions[mod->sngpos]==(mod->numpat-1) && !(flags&UF_NOWRAP)))) {
			mod->sngpos=0;
			mod->posjmp=2;
		} else
			mod->posjmp=3;
	}

	return 0;
}

static void DoEEffects(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod,
	SWORD channel, UBYTE dat)
{
	UBYTE nib = dat & 0xf;

	switch (dat>>4) {
	case 0x0: /* hardware filter toggle, not supported */
		break;
	case 0x1: /* fineslide up */
		if (a->main.period)
			if (!tick)
				a->tmpperiod-=(nib<<2);
		break;
	case 0x2: /* fineslide dn */
		if (a->main.period)
			if (!tick)
				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 */
		if (a->main.period) {
			if (flags&UF_XMPERIODS)
				a->speed=nib+128;
			else
				a->speed=finetune[nib];
			a->tmpperiod=GetPeriod(flags, (UWORD)a->main.note<<1,a->speed);
		}
		break;
	case 0x6: /* set patternloop */
		if (tick)
			break;
		if (nib) { /* set reppos or repcnt ? */
			/* set repcnt, so check if repcnt already is set, which means we
			   are already looping */
			if (a->pat_repcnt)
				a->pat_repcnt--; /* already looping, decrease counter */
			else {
#if 0
				/* this would make walker.xm, shipped with Xsoundtracker,
				   play correctly, but it's better to remain compatible
				   with FT2 */
				if ((!(flags&UF_NOWRAP))||(a->pat_reppos!=POS_NONE))
#endif
					a->pat_repcnt=nib; /* not yet looping, so set repcnt */
			}

			if (a->pat_repcnt) { /* jump to reppos if repcnt>0 */
				if (a->pat_reppos==POS_NONE)
					a->pat_reppos=mod->patpos-1;
				if (a->pat_reppos==-1) {
					mod->pat_repcrazy=1;
					mod->patpos=0;
				} else
					mod->patpos=a->pat_reppos;
			} else a->pat_reppos=POS_NONE;
		} else
			a->pat_reppos=mod->patpos-1; /* set reppos - can be (-1) */
		break;
	case 0x7: /* set tremolo waveform */
		a->wavecontrol&=0x0f;
		a->wavecontrol|=nib<<4;
		break;
	case 0x8: /* set panning */
		if (mod->panflag) {
			if (nib<=8) nib<<=4;
			else nib*=17;
			a->main.panning=mod->panning[channel]=nib;
		}
		break;
	case 0x9: /* retrig note */
		/* do not retrigger on tick 0, until we are emulating FT2 and effect
		   data is zero */
		if (!tick && !((flags & UF_FT2QUIRKS) && (!nib)))
			break;
		/* only retrigger if data nibble > 0, or if tick 0 (FT2 compat) */
		if (nib || !tick) {
			if (!a->retrig) {
				/* when retrig counter reaches 0, reset counter and restart
				   the sample */
				if (a->main.period) a->main.kick=KICK_NOTE;
				a->retrig=nib;
			}
			a->retrig--; /* countdown */
		}
		break;
	case 0xa: /* fine volume slide up */
		if (tick)
			break;
		a->tmpvolume+=nib;
		if (a->tmpvolume>64) a->tmpvolume=64;
		break;
	case 0xb: /* fine volume slide dn  */
		if (tick)
			break;
		a->tmpvolume-=nib;
		if (a->tmpvolume<0)a->tmpvolume=0;
		break;
	case 0xc: /* cut note */
		/* When tick reaches the cut-note value, turn the volume to
		   zero (just like on the amiga) */
		if (tick>=nib)
			a->tmpvolume=0; /* just turn the volume down */
		break;
	case 0xd: /* note delay */
		/* delay the start of the sample until tick==nib */
		if (!tick)
			a->main.notedelay=nib;
		else if (a->main.notedelay)
			a->main.notedelay--;
		break;
	case 0xe: /* pattern delay */
		if (!tick)
			if (!mod->patdly2)
				mod->patdly=nib+1; /* only once, when tick=0 */
		break;
	case 0xf: /* invert loop, not supported  */
		break;
	}
}

static int DoPTEffectE(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	DoEEffects(tick, flags, a, mod, channel, UniGetByte());

	return 0;
}

static int DoPTEffectF(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (tick||mod->patdly2) return 0;
	if (mod->extspd&&(dat>=mod->bpmlimit))
		mod->bpm=dat;
	else
	  if (dat) {
		mod->sngspd=(dat>=mod->bpmlimit)?mod->bpmlimit-1:dat;
		mod->vbtick=0;
	}

	return 0;
}

/*========== Scream Tracker effects */

static int DoS3MEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE speed;

	speed = UniGetByte();

	if (tick || mod->patdly2)
		return 0;

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

	return 0;
}

static void DoS3MVolSlide(UWORD tick, UWORD flags, MP_CONTROL *a, UBYTE inf)
{
	UBYTE lo, hi;

	if (inf)
		a->s3mvolslide=inf;
	else
		inf=a->s3mvolslide;

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

	if (!lo) {
		if ((tick)||(flags&UF_S3MSLIDES)) a->tmpvolume+=hi;
	} else
	  if (!hi) {
		if ((tick)||(flags&UF_S3MSLIDES)) a->tmpvolume-=lo;
	} else
	  if (lo==0xf) {
		if (!tick) a->tmpvolume+=(hi?hi:0xf);
	} else
	  if (hi==0xf) {
		if (!tick) a->tmpvolume-=(lo?lo:0xf);
	} else
	  return;

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

static int DoS3MEffectD(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	DoS3MVolSlide(tick, flags, a, UniGetByte());

	return 1;
}

static void DoS3MSlideDn(UWORD tick, MP_CONTROL *a, UBYTE inf)
{
	UBYTE hi,lo;

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

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

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

static int DoS3MEffectE(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (a->main.period)
		DoS3MSlideDn(tick, a,dat);

	return 0;
}

static void DoS3MSlideUp(UWORD tick, MP_CONTROL *a, UBYTE inf)
{
	UBYTE hi,lo;

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

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

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

static int DoS3MEffectF(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (a->main.period)
		DoS3MSlideUp(tick, a,dat);

	return 0;
}

static int DoS3MEffectI(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf, on, off;

	inf = UniGetByte();
	if (inf)
		a->s3mtronof = inf;
	else {
		inf = a->s3mtronof;
		if (!inf)
			return 0;
	}

	if (!tick)
		return 0;

	on=(inf>>4)+1;
	off=(inf&0xf)+1;
	a->s3mtremor%=(on+off);
	a->volume=(a->s3mtremor<on)?a->tmpvolume:0;
	a->ownvol=1;
	a->s3mtremor++;

	return 0;
}

static int DoS3MEffectQ(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf;

	inf = UniGetByte();
	if (a->main.period) {
		if (inf) {
			a->s3mrtgslide=inf>>4;
			a->s3mrtgspeed=inf&0xf;
		}

		/* only retrigger if low nibble > 0 */
		if (a->s3mrtgspeed>0) {
			if (!a->retrig) {
				/* when retrig counter reaches 0, reset counter and restart the
				   sample */
				if (a->main.kick!=KICK_NOTE) a->main.kick=KICK_KEYOFF;
				a->retrig=a->s3mrtgspeed;

				if ((tick)||(flags&UF_S3MSLIDES)) {
					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>>=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)>>1;
						break;
					case 0xf:
						a->tmpvolume=a->tmpvolume<<1;
						break;
					}
					if (a->tmpvolume<0)
						a->tmpvolume=0;
					else if (a->tmpvolume>64)
						a->tmpvolume=64;
				}
			}
			a->retrig--; /* countdown  */
		}
	}

	return 0;
}

static int DoS3MEffectR(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat, q;
	UWORD temp=0;	/* silence warning */

	dat = UniGetByte();
	if (!tick) {
		if (dat&0x0f) a->trmdepth=dat&0xf;
		if (dat&0xf0) a->trmspd=(dat&0xf0)>>2;
	}

	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: /* random */
		temp=getrandom(256);
		break;
	}

	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;
	}
	a->ownvol = 1;

	if (tick)
		a->trmpos+=a->trmspd;

	return 0;
}

static int DoS3MEffectT(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE tempo;

	tempo = UniGetByte();

	if (tick || mod->patdly2)
		return 0;

	mod->bpm = (tempo < 32) ? 32 : tempo;

	return 0;
}

static int DoS3MEffectU(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat, q;
	UWORD temp = 0;	/* silence warning */

	dat = UniGetByte();
	if (!tick) {
		if (dat&0x0f) a->vibdepth=dat&0xf;
		if (dat&0xf0) a->vibspd=(dat&0xf0)>>2;
	} else
		if (a->main.period) {
			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: /* random */
				temp=getrandom(256);
				break;
			}

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

			if (a->vibpos>=0)
				a->main.period=a->tmpperiod+temp;
			else
				a->main.period=a->tmpperiod-temp;
			a->ownper = 1;

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

	return 0;
}

/*========== Envelope helpers */

static int DoKeyOff(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	a->main.keyoff|=KEY_OFF;
	if ((!(a->main.volflg&EF_ON))||(a->main.volflg&EF_LOOP))
		a->main.keyoff=KEY_KILL;

	return 0;
}

static int DoKeyFade(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if ((tick>=dat)||(tick==mod->sngspd-1)) {
		a->main.keyoff=KEY_KILL;
		if (!(a->main.volflg&EF_ON))
			a->main.fadevol=0;
	}

	return 0;
}

/*========== Fast Tracker effects */

/* DoXMEffect6 after DoXMEffectA */

static int DoXMEffectA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf, lo, hi;

	inf = UniGetByte();
	if (inf)
		a->s3mvolslide = inf;
	else
		inf = a->s3mvolslide;

	if (tick) {
		lo=inf&0xf;
		hi=inf>>4;

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

	return 0;
}

static int DoXMEffect6(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	if (a->main.period)
		DoVibrato(tick, a);

	return DoXMEffectA(tick, flags, a, mod, channel);
}

static int DoXMEffectE1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (!tick) {
		if (dat) a->fportupspd=dat;
		if (a->main.period)
			a->tmpperiod-=(a->fportupspd<<2);
	}

	return 0;
}

static int DoXMEffectE2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (!tick) {
		if (dat) a->fportdnspd=dat;
		if (a->main.period)
			a->tmpperiod+=(a->fportdnspd<<2);
	}

	return 0;
}

static int DoXMEffectEA(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (!tick)
		if (dat) a->fslideupspd=dat;
	a->tmpvolume+=a->fslideupspd;
	if (a->tmpvolume>64) a->tmpvolume=64;

	return 0;
}

static int DoXMEffectEB(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if (!tick)
		if (dat) a->fslidednspd=dat;
	a->tmpvolume-=a->fslidednspd;
	if (a->tmpvolume<0) a->tmpvolume=0;

	return 0;
}

static int DoXMEffectG(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	mod->volume=UniGetByte()<<1;
	if (mod->volume>128) mod->volume=128;

	return 0;
}

static int DoXMEffectH(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf;

	inf = UniGetByte();

	if (tick) {
		if (inf) mod->globalslide=inf;
		else inf=mod->globalslide;
		if (inf & 0xf0) inf&=0xf0;
		mod->volume=mod->volume+((inf>>4)-(inf&0xf))*2;

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

	return 0;
}

static int DoXMEffectL(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat=UniGetByte();
	if ((!tick)&&(a->main.i)) {
		UWORD points;
		INSTRUMENT *i=a->main.i;
		MP_VOICE *aout;

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

	return 0;
}

static int DoXMEffectP(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf, lo, hi;
	SWORD pan;

	inf = UniGetByte();
	if (!mod->panflag)
		return 0;

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

	if (tick) {
		lo=inf&0xf;
		hi=inf>>4;

		/* slide right has absolute priority */
		if (hi)
			lo = 0;

		pan=((a->main.panning==PAN_SURROUND)?PAN_CENTER:a->main.panning)+hi-lo;
		a->main.panning=(pan<PAN_LEFT)?PAN_LEFT:(pan>PAN_RIGHT?PAN_RIGHT:pan);
	}

	return 0;
}

static int DoXMEffectX1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat = UniGetByte();
	if (dat)
		a->ffportupspd = dat;
	else
		dat = a->ffportupspd;

	if (a->main.period)
		if (!tick) {
			a->main.period-=dat;
			a->tmpperiod-=dat;
			a->ownper = 1;
		}

	return 0;
}

static int DoXMEffectX2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat;

	dat = UniGetByte();
	if (dat)
		a->ffportdnspd=dat;
	else
		dat = a->ffportdnspd;

	if (a->main.period)
		if (!tick) {
			a->main.period+=dat;
			a->tmpperiod+=dat;
			a->ownper = 1;
		}

	return 0;
}

/*========== Impulse Tracker effects */

static void DoITToneSlide(UWORD tick, MP_CONTROL *a, UBYTE dat)
{
	if (dat)
		a->portspeed = dat;

	/* if we don't come from another note, ignore the slide and play the note
	   as is */
	if (!a->oldnote || !a->main.period)
		return;

	if ((!tick)&&(a->newsamp)){
		a->main.kick=KICK_NOTE;
		a->main.start=-1;
	} else
		a->main.kick=(a->main.kick==KICK_NOTE)?KICK_ENV:KICK_ABSENT;

	if (tick) {
		int dist;

		/* We have to slide a->main.period towards a->wantedperiod, compute the
		   difference between those two values */
		dist=a->main.period-a->wantedperiod;

		/* if they are equal or if portamentospeed is too big... */
		if ((!dist)||((a->portspeed<<2)>abs(dist)))
			/* ... make tmpperiod equal tperiod */
			a->tmpperiod=a->main.period=a->wantedperiod;
		else
		  if (dist>0) {
			a->tmpperiod-=a->portspeed<<2;
			a->main.period-=a->portspeed<<2; /* dist>0 slide up */
		} else {
			a->tmpperiod+=a->portspeed<<2;
			a->main.period+=a->portspeed<<2; /* dist<0 slide down */
		}
	} else
		a->tmpperiod=a->main.period;
	a->ownper=1;
}

static int DoITEffectG(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	DoITToneSlide(tick, a, UniGetByte());

	return 0;
}

static void DoITVibrato(UWORD tick, MP_CONTROL *a, UBYTE dat)
{
	UBYTE q;
	UWORD temp=0;

	if (!tick) {
		if (dat&0x0f) a->vibdepth=dat&0xf;
		if (dat&0xf0) a->vibspd=(dat&0xf0)>>2;
	}
	if (!a->main.period)
		return;

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

	switch (a->wavecontrol&3) {
	case 0: /* sine */
		temp=VibratoTable[q];
		break;
	case 1: /* square wave */
		temp=255;
		break;
	case 2: /* ramp down */
		q<<=3;
		if (a->vibpos<0) q=255-q;
		temp=q;
		break;
	case 3: /* random */
		temp=getrandom(256);
		break;
	}

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

	if (a->vibpos>=0)
		a->main.period=a->tmpperiod+temp;
	else
		a->main.period=a->tmpperiod-temp;
	a->ownper=1;

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

static int DoITEffectH(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	DoITVibrato(tick, a, UniGetByte());

	return 0;
}

static int DoITEffectI(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf, on, off;

	inf = UniGetByte();
	if (inf)
		a->s3mtronof = inf;
	else {
		inf = a->s3mtronof;
		if (!inf)
			return 0;
	}

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

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

	return 0;
}

static int DoITEffectM(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	a->main.chanvol=UniGetByte();
	if (a->main.chanvol>64)
		a->main.chanvol=64;
	else if (a->main.chanvol<0)
		a->main.chanvol=0;

	return 0;
}

static int DoITEffectN(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf, lo, hi;

	inf = UniGetByte();

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

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

	if (!hi)
		a->main.chanvol-=lo;
	else
	  if (!lo) {
		a->main.chanvol+=hi;
	} else
	  if (hi==0xf) {
		if (!tick) a->main.chanvol-=lo;
	} else
	  if (lo==0xf) {
		if (!tick) a->main.chanvol+=hi;
	}

	if (a->main.chanvol<0)
		a->main.chanvol=0;
	else if (a->main.chanvol>64)
		a->main.chanvol=64;

	return 0;
}

static int DoITEffectP(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf, lo, hi;
	SWORD pan;

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

	if (!mod->panflag)
		return 0;

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

	pan=(a->main.panning==PAN_SURROUND)?PAN_CENTER:a->main.panning;

	if (!hi)
		pan+=lo<<2;
	else
	  if (!lo) {
		pan-=hi<<2;
	} else
	  if (hi==0xf) {
		if (!tick) pan+=lo<<2;
	} else
	  if (lo==0xf) {
		if (!tick) pan-=hi<<2;
	}
	a->main.panning=
	  (pan<PAN_LEFT)?PAN_LEFT:(pan>PAN_RIGHT?PAN_RIGHT:pan);

	return 0;
}

static int DoITEffectT(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE tempo;
	SWORD temp;

	tempo = UniGetByte();

	if (mod->patdly2)
		return 0;

	temp = mod->bpm;
	if (tempo & 0x10)
		temp += (tempo & 0x0f);
	else
		temp -= tempo;

	mod->bpm=(temp>255)?255:(temp<1?1:temp);

	return 0;
}

static int DoITEffectU(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat, q;
	UWORD temp = 0;	/* silence warning */

	dat = UniGetByte();
	if (!tick) {
		if (dat&0x0f) a->vibdepth=dat&0xf;
		if (dat&0xf0) a->vibspd=(dat&0xf0)>>2;
	}
	if (a->main.period) {
		q=(a->vibpos>>2)&0x1f;

		switch (a->wavecontrol&3) {
		case 0: /* sine */
			temp=VibratoTable[q];
			break;
		case 1: /* square wave */
			temp=255;
			break;
		case 2: /* ramp down */
			q<<=3;
			if (a->vibpos<0) q=255-q;
			temp=q;
			break;
		case 3: /* random */
			temp=getrandom(256);
			break;
		}

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

		if (a->vibpos>=0)
			a->main.period=a->tmpperiod+temp;
		else
			a->main.period=a->tmpperiod-temp;
		a->ownper = 1;

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

	return 0;
}

static int DoITEffectW(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE inf, lo, hi;

	inf = UniGetByte();

	if (inf)
		mod->globalslide = inf;
	else
		inf = mod->globalslide;

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

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

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

	return 0;
}

static int DoITEffectY(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat, q;
	SLONG temp = 0;	/* silence warning */


	dat=UniGetByte();
	if (!tick) {
		if (dat&0x0f) a->panbdepth=(dat&0xf);
		if (dat&0xf0) a->panbspd=(dat&0xf0)>>4;
	}
	if (mod->panflag) {
		q=a->panbpos;

		switch (a->panbwave) {
		case 0: /* sine */
			temp=PanbrelloTable[q];
			break;
		case 1: /* square wave */
			temp=(q<0x80)?64:0;
			break;
		case 2: /* ramp down */
			q<<=3;
			temp=q;
			break;
		case 3: /* random */
			temp=getrandom(256);
			break;
		}

		temp*=a->panbdepth;
		temp=(temp/8)+mod->panning[channel];

		a->main.panning=
			(temp<PAN_LEFT)?PAN_LEFT:(temp>PAN_RIGHT?PAN_RIGHT:temp);
		a->panbpos+=a->panbspd;

	}

	return 0;
}

static void DoNNAEffects(MODULE *, MP_CONTROL *, UBYTE);

/* Impulse/Scream Tracker Sxx effects.
   All Sxx effects share the same memory space. */
static int DoITEffectS0(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat, inf, c;

	dat = UniGetByte();
	inf=dat&0xf;
	c=dat>>4;

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

	switch (c) {
	case SS_GLISSANDO: /* S1x set glissando voice */
		DoEEffects(tick, flags, a, mod, channel, 0x30|inf);
		break;
	case SS_FINETUNE: /* S2x set finetune */
		DoEEffects(tick, flags, a, mod, channel, 0x50|inf);
		break;
	case SS_VIBWAVE: /* S3x set vibrato waveform */
		DoEEffects(tick, flags, a, mod, channel, 0x40|inf);
		break;
	case SS_TREMWAVE: /* S4x set tremolo waveform */
		DoEEffects(tick, flags, a, mod, channel, 0x70|inf);
		break;
	case SS_PANWAVE: /* S5x panbrello */
		a->panbwave=inf;
		break;
	case SS_FRAMEDELAY: /* S6x delay x number of frames (patdly) */
		DoEEffects(tick, flags, a, mod, channel, 0xe0|inf);
		break;
	case SS_S7EFFECTS: /* S7x instrument / NNA commands */
		DoNNAEffects(mod, a, inf);
		break;
	case SS_PANNING: /* S8x set panning position */
		DoEEffects(tick, flags, a, mod, channel, 0x80 | inf);
		break;
	case SS_SURROUND: /* S9x set surround sound */
		if (mod->panflag)
			a->main.panning = mod->panning[channel] = PAN_SURROUND;
		break;
	case SS_HIOFFSET: /* SAy set high order sample offset yxx00h */
		if (!tick) {
			a->hioffset=inf<<16;
			a->main.start=a->hioffset|a->soffset;

			if ((a->main.s)&&(a->main.start>a->main.s->length))
				a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)?
				    a->main.s->loopstart:a->main.s->length;
		}
		break;
	case SS_PATLOOP: /* SBx pattern loop */
		DoEEffects(tick, flags, a, mod, channel, 0x60|inf);
		break;
	case SS_NOTECUT: /* SCx notecut */
		if (!inf) inf = 1;
		DoEEffects(tick, flags, a, mod, channel, 0xC0|inf);
		break;
	case SS_NOTEDELAY: /* SDx notedelay */
		DoEEffects(tick, flags, a, mod, channel, 0xD0|inf);
		break;
	case SS_PATDELAY: /* SEx patterndelay */
		DoEEffects(tick, flags, a, mod, channel, 0xE0|inf);
		break;
	}

	return 0;
}

/*========== Impulse Tracker Volume/Pan Column effects */

/*
 * All volume/pan column effects share the same memory space.
 */

static int DoVolEffects(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE c, inf;

	c = UniGetByte();
	inf = UniGetByte();

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

	if (c)
		switch (c) {
		case VOL_VOLUME:
			if (tick) break;
			if (inf>64) inf=64;
			a->tmpvolume=inf;
			break;
		case VOL_PANNING:
			if (mod->panflag)
				a->main.panning=inf;
			break;
		case VOL_VOLSLIDE:
			DoS3MVolSlide(tick, flags, a, inf);
			return 1;
		case VOL_PITCHSLIDEDN:
			if (a->main.period)
				DoS3MSlideDn(tick, a, inf);
			break;
		case VOL_PITCHSLIDEUP:
			if (a->main.period)
				DoS3MSlideUp(tick, a, inf);
			break;
		case VOL_PORTAMENTO:
			DoITToneSlide(tick, a, inf);
			break;
		case VOL_VIBRATO:
			DoITVibrato(tick, a, inf);
			break;
	}

	return 0;
}

/*========== UltraTracker effects */

static int DoULTEffect9(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UWORD offset=UniGetWord();

	if (offset)
		a->ultoffset=offset;

	a->main.start=a->ultoffset<<2;
	if ((a->main.s)&&(a->main.start>a->main.s->length))
		a->main.start=a->main.s->flags&(SF_LOOP|SF_BIDI)?
		    a->main.s->loopstart:a->main.s->length;

	return 0;
}

/*========== OctaMED effects */

static int DoMEDSpeed(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UWORD speed=UniGetWord();

	mod->bpm=speed;

	return 0;
}

static int DoMEDEffectF1(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	DoEEffects(tick, flags, a, mod, channel, 0x90|(mod->sngspd/2));

	return 0;
}

static int DoMEDEffectF2(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	DoEEffects(tick, flags, a, mod, channel, 0xd0|(mod->sngspd/2));

	return 0;
}

static int DoMEDEffectF3(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	DoEEffects(tick, flags, a, mod, channel, 0x90|(mod->sngspd/3));

	return 0;
}

/*========== Oktalyzer effects */

static int DoOktArp(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UBYTE dat, dat2;

	dat2 = UniGetByte();	/* arpeggio style */
	dat = UniGetByte();
	if (!tick) {
		if (!dat && (flags & UF_ARPMEM))
			dat=a->arpmem;
		else
			a->arpmem=dat;
	}
	if (a->main.period)
		DoArpeggio(tick, flags, a, dat2);

	return 0;
}

/*========== General player functions */

static int DoNothing(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
{
	UniSkipOpcode();

	return 0;
}

typedef int (*effect_func) (UWORD, UWORD, MP_CONTROL *, MODULE *, SWORD);

static effect_func effects[UNI_LAST] = {
	DoNothing,		/* 0 */
	DoNothing,		/* UNI_NOTE */
	DoNothing,		/* UNI_INSTRUMENT */
	DoPTEffect0,	/* UNI_PTEFFECT0 */
	DoPTEffect1,	/* UNI_PTEFFECT1 */
	DoPTEffect2,	/* UNI_PTEFFECT2 */
	DoPTEffect3,	/* UNI_PTEFFECT3 */
	DoPTEffect4,	/* UNI_PTEFFECT4 */
	DoPTEffect5,	/* UNI_PTEFFECT5 */
	DoPTEffect6,	/* UNI_PTEFFECT6 */
	DoPTEffect7,	/* UNI_PTEFFECT7 */
	DoPTEffect8,	/* UNI_PTEFFECT8 */
	DoPTEffect9,	/* UNI_PTEFFECT9 */
	DoPTEffectA,	/* UNI_PTEFFECTA */
	DoPTEffectB,	/* UNI_PTEFFECTB */
	DoPTEffectC,	/* UNI_PTEFFECTC */
	DoPTEffectD,	/* UNI_PTEFFECTD */
	DoPTEffectE,	/* UNI_PTEFFECTE */
	DoPTEffectF,	/* UNI_PTEFFECTF */
	DoS3MEffectA,	/* UNI_S3MEFFECTA */
	DoS3MEffectD,	/* UNI_S3MEFFECTD */
	DoS3MEffectE,	/* UNI_S3MEFFECTE */
	DoS3MEffectF,	/* UNI_S3MEFFECTF */
	DoS3MEffectI,	/* UNI_S3MEFFECTI */
	DoS3MEffectQ,	/* UNI_S3MEFFECTQ */
	DoS3MEffectR,	/* UNI_S3MEFFECTR */
	DoS3MEffectT,	/* UNI_S3MEFFECTT */
	DoS3MEffectU,	/* UNI_S3MEFFECTU */
	DoKeyOff,	/* UNI_KEYOFF */
	DoKeyFade,	/* UNI_KEYFADE */
	DoVolEffects,	/* UNI_VOLEFFECTS */
	DoPTEffect4,	/* UNI_XMEFFECT4 */
	DoXMEffect6,	/* UNI_XMEFFECT6 */
	DoXMEffectA,	/* UNI_XMEFFECTA */
	DoXMEffectE1,	/* UNI_XMEFFECTE1 */
	DoXMEffectE2,	/* UNI_XMEFFECTE2 */
	DoXMEffectEA,	/* UNI_XMEFFECTEA */
	DoXMEffectEB,	/* UNI_XMEFFECTEB */
	DoXMEffectG,	/* UNI_XMEFFECTG */
	DoXMEffectH,	/* UNI_XMEFFECTH */
	DoXMEffectL,	/* UNI_XMEFFECTL */
	DoXMEffectP,	/* UNI_XMEFFECTP */
	DoXMEffectX1,	/* UNI_XMEFFECTX1 */
	DoXMEffectX2,	/* UNI_XMEFFECTX2 */
	DoITEffectG,	/* UNI_ITEFFECTG */
	DoITEffectH,	/* UNI_ITEFFECTH */
	DoITEffectI,	/* UNI_ITEFFECTI */
	DoITEffectM,	/* UNI_ITEFFECTM */
	DoITEffectN,	/* UNI_ITEFFECTN */
	DoITEffectP,	/* UNI_ITEFFECTP */
	DoITEffectT,	/* UNI_ITEFFECTT */
	DoITEffectU,	/* UNI_ITEFFECTU */
	DoITEffectW,	/* UNI_ITEFFECTW */
	DoITEffectY,	/* UNI_ITEFFECTY */
	DoNothing,	/* UNI_ITEFFECTZ */
	DoITEffectS0,	/* UNI_ITEFFECTS0 */
	DoULTEffect9,	/* UNI_ULTEFFECT9 */
	DoMEDSpeed,	/* UNI_MEDSPEED */
	DoMEDEffectF1,	/* UNI_MEDEFFECTF1 */
	DoMEDEffectF2,	/* UNI_MEDEFFECTF2 */
	DoMEDEffectF3,	/* UNI_MEDEFFECTF3 */
	DoOktArp,	/* UNI_OKTARP */
};

static int pt_playeffects(MODULE *mod, SWORD channel, MP_CONTROL *a)
{
	UWORD tick = mod->vbtick;
	UWORD flags = mod->flags;
	UBYTE c;
	int explicitslides = 0;
	effect_func f;

	while((c=UniGetByte())) {
#if 0 /* this doesn't normally happen unless things go fubar elsewhere */
		if (c >= UNI_LAST)
		    fprintf(stderr,"fubar'ed opcode %u\n",c);
#endif
		f = effects[c];
		if (f != DoNothing)
		    a->sliding = 0;
		explicitslides |= f(tick, flags, a, mod, channel);
	}
	return explicitslides;
}

static void DoNNAEffects(MODULE *mod, MP_CONTROL *a, UBYTE dat)
{
	int t;
	MP_VOICE *aout;

	dat&=0xf;
	aout=(a->slave)?a->slave:NULL;

	switch (dat) {
	case 0x0: /* past note cut */
		for (t=0;t<NUMVOICES(mod);t++)
			if (mod->voice[t].master==a)
				mod->voice[t].main.fadevol=0;
		break;
	case 0x1: /* past note off */
		for (t=0;t<NUMVOICES(mod);t++)
			if (mod->voice[t].master==a) {
				mod->voice[t].main.keyoff|=KEY_OFF;
				if ((!(mod->voice[t].venv.flg & EF_ON))||
				   (mod->voice[t].venv.flg & EF_LOOP))
					mod->voice[t].main.keyoff=KEY_KILL;
			}
		break;
	case 0x2: /* past note fade */
		for (t=0;t<NUMVOICES(mod);t++)
			if (mod->voice[t].master==a)
				mod->voice[t].main.keyoff|=KEY_FADE;
		break;
	case 0x3: /* set NNA note cut */
		a->main.nna=(a->main.nna&~NNA_MASK)|NNA_CUT;
		break;
	case 0x4: /* set NNA note continue */
		a->main.nna=(a->main.nna&~NNA_MASK)|NNA_CONTINUE;
		break;
	case 0x5: /* set NNA note off */
		a->main.nna=(a->main.nna&~NNA_MASK)|NNA_OFF;
		break;
	case 0x6: /* set NNA note fade */
		a->main.nna=(a->main.nna&~NNA_MASK)|NNA_FADE;
		break;
	case 0x7: /* disable volume envelope */
		if (aout)
			aout->main.volflg&=~EF_ON;
		break;
	case 0x8: /* enable volume envelope  */
		if (aout)
			aout->main.volflg|=EF_ON;
		break;
	case 0x9: /* disable panning envelope */
		if (aout)
			aout->main.panflg&=~EF_ON;
		break;
	case 0xa: /* enable panning envelope */
		if (aout)
			aout->main.panflg|=EF_ON;
		break;
	case 0xb: /* disable pitch envelope */
		if (aout)
			aout->main.pitflg&=~EF_ON;
		break;
	case 0xc: /* enable pitch envelope */
		if (aout)
			aout->main.pitflg|=EF_ON;
		break;
	}
}

static void pt_UpdateVoices(MODULE *mod, int max_volume)
{
	SWORD envpan,envvol,envpit,channel;
	UWORD playperiod;
	SLONG vibval,vibdpt;
	ULONG tmpvol;

	MP_VOICE *aout;
	INSTRUMENT *i;
	SAMPLE *s;

	mod->totalchn=mod->realchn=0;
	for (channel=0;channel<NUMVOICES(mod);channel++) {
		aout=&mod->voice[channel];
		i=aout->main.i;
		s=aout->main.s;

		if (!s || !s->length) continue;

		if (aout->main.period<40)
			aout->main.period=40;
		else if (aout->main.period>50000)
			aout->main.period=50000;

		if ((aout->main.kick==KICK_NOTE)||(aout->main.kick==KICK_KEYOFF)) {
			Voice_Play_internal(channel,s,(aout->main.start==-1)?
			    ((s->flags&SF_UST_LOOP)?s->loopstart:0):aout->main.start);
			aout->main.fadevol=32768;
			aout->aswppos=0;
		}

		envvol = 256;
		envpan = PAN_CENTER;
		envpit = 32;
		if (i && ((aout->main.kick==KICK_NOTE)||(aout->main.kick==KICK_ENV))) {
			if (aout->main.volflg & EF_ON)
				envvol = StartEnvelope(&aout->venv,aout->main.volflg,
				  i->volpts,i->volsusbeg,i->volsusend,
				  i->volbeg,i->volend,i->volenv,aout->main.keyoff);
			if (aout->main.panflg & EF_ON)
				envpan = StartEnvelope(&aout->penv,aout->main.panflg,
				  i->panpts,i->pansusbeg,i->pansusend,
				  i->panbeg,i->panend,i->panenv,aout->main.keyoff);
			if (aout->main.pitflg & EF_ON)
				envpit = StartEnvelope(&aout->cenv,aout->main.pitflg,
				  i->pitpts,i->pitsusbeg,i->pitsusend,
				  i->pitbeg,i->pitend,i->pitenv,aout->main.keyoff);

			if (aout->cenv.flg & EF_ON)
				aout->masterperiod=GetPeriod(mod->flags,
				  (UWORD)aout->main.note<<1, aout->master->speed);
		} else {
			if (aout->main.volflg & EF_ON)
				envvol = ProcessEnvelope(aout,&aout->venv,256);
			if (aout->main.panflg & EF_ON)
				envpan = ProcessEnvelope(aout,&aout->penv,PAN_CENTER);
			if (aout->main.pitflg & EF_ON)
				envpit = ProcessEnvelope(aout,&aout->cenv,32);
		}
		if (aout->main.kick == KICK_NOTE) {
			aout->main.kick_flag = 1;
		}
		aout->main.kick=KICK_ABSENT;

		tmpvol = aout->main.fadevol;	/* max 32768 */
		tmpvol *= aout->main.chanvol;	/* * max 64 */
		tmpvol *= aout->main.outvolume;	/* * max 256 */
		tmpvol /= (256 * 64);			/* tmpvol is max 32768 again */
		aout->totalvol = tmpvol >> 2;	/* used to determine samplevolume */
		tmpvol *= envvol;				/* * max 256 */
		tmpvol *= mod->volume;			/* * max 128 */
		tmpvol /= (128 * 256 * 128);

		/* fade out */
		if (mod->sngpos>=mod->numpos)
			tmpvol=0;
		else
			tmpvol=(tmpvol*max_volume)/128;

		if ((aout->masterchn!=-1)&& mod->control[aout->masterchn].muted)
			Voice_SetVolume_internal(channel,0);
		else {
			Voice_SetVolume_internal(channel,tmpvol);
			if ((tmpvol)&&(aout->master)&&(aout->master->slave==aout))
				mod->realchn++;
			mod->totalchn++;
		}

		if (aout->main.panning==PAN_SURROUND)
			Voice_SetPanning_internal(channel,PAN_SURROUND);
		else
			if ((mod->panflag)&&(aout->penv.flg & EF_ON))
				Voice_SetPanning_internal(channel,
				    DoPan(envpan,aout->main.panning));
			else
				Voice_SetPanning_internal(channel,aout->main.panning);

		if (aout->main.period && s->vibdepth)
			switch (s->vibtype) {
			case 0:
				vibval=avibtab[s->avibpos&127];
				if (aout->avibpos & 0x80) vibval=-vibval;
				break;
			case 1:
				vibval=64;
				if (aout->avibpos & 0x80) vibval=-vibval;
				break;
			case 2:
				vibval=63-(((aout->avibpos+128)&255)>>1);
				break;
			default:
				vibval=(((aout->avibpos+128)&255)>>1)-64;
				break;
			}
		else
			vibval=0;

		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 (!(mod->flags&UF_LINEAR)) vibval>>=1;
				aout->main.period-=vibval;
			}
		} else {
			/* do XM style auto-vibrato */
			if (!(aout->main.keyoff & KEY_OFF)) {
				if (aout->aswppos<s->vibsweep) {
					vibdpt=(aout->aswppos*s->vibdepth)/s->vibsweep;
					aout->aswppos++;
				} else
					vibdpt=s->vibdepth;
			} else {
				/* keyoff -> 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->main.period-=vibval;
		}

		/* update vibrato position */
		aout->avibpos=(aout->avibpos+s->vibrate)&0xff;

		/* process pitch envelope */
		playperiod=aout->main.period;

		if ((aout->main.pitflg&EF_ON)&&(envpit!=32)) {
			long p1;

			envpit-=32;
			if ((aout->main.note<<1)+envpit<=0) envpit=-(aout->main.note<<1);

			p1=GetPeriod(mod->flags, ((UWORD)aout->main.note<<1)+envpit,
			    aout->master->speed)-aout->masterperiod;
			if (p1>0) {
				if ((UWORD)(playperiod+p1)<=playperiod) {
					p1=0;
					aout->main.keyoff|=KEY_OFF;
				}
			} else if (p1<0) {
				if ((UWORD)(playperiod+p1)>=playperiod) {
					p1=0;
					aout->main.keyoff|=KEY_OFF;
				}
			}
			playperiod+=p1;
		}

		if (!aout->main.fadevol) { /* check for a dead note (fadevol=0) */
			Voice_Stop_internal(channel);
			mod->totalchn--;
			if ((tmpvol)&&(aout->master)&&(aout->master->slave==aout))
				mod->realchn--;
		} else {
			Voice_SetFrequency_internal(channel,
			                            getfrequency(mod->flags,playperiod));

			/* if keyfade, start substracting fadeoutspeed from fadevol: */
			if ((i)&&(aout->main.keyoff&KEY_FADE)) {
				if (aout->main.fadevol>=i->volfade)
					aout->main.fadevol-=i->volfade;
				else
					aout->main.fadevol=0;
			}
		}

		md_bpm=mod->bpm+mod->relspd;
		if (md_bpm<32)
			md_bpm=32;
		else if ((!(mod->flags&UF_HIGHBPM)) && md_bpm>255)
			md_bpm=255;
	}
}

/* Handles new notes or instruments */
static void pt_Notes(MODULE *mod)
{
	SWORD channel;
	MP_CONTROL *a;
	UBYTE c,inst;
	int tr,funky; /* funky is set to indicate note or instrument change */

	for (channel=0;channel<mod->numchn;channel++) {
		a=&mod->control[channel];

		if (mod->sngpos>=mod->numpos) {
			tr=mod->numtrk;
			mod->numrow=0;
		} else {
			tr=mod->patterns[(mod->positions[mod->sngpos]*mod->numchn)+channel];
			mod->numrow=mod->pattrows[mod->positions[mod->sngpos]];
		}

		a->row=(tr<mod->numtrk)?UniFindRow(mod->tracks[tr],mod->patpos):NULL;
		a->newsamp=0;
		if (!mod->vbtick) a->main.notedelay=0;

		if (!a->row) continue;
		UniSetRow(a->row);
		funky=0;

		while((c=UniGetByte()))
			switch (c) {
			case UNI_NOTE:
				funky|=1;
				a->oldnote=a->anote,a->anote=UniGetByte();
				a->main.kick =KICK_NOTE;
				a->main.start=-1;
				a->sliding=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:
				inst=UniGetByte();
				if (inst>=mod->numins) break; /* safety valve */
				funky|=2;
				a->main.i=(mod->flags & UF_INST)?&mod->instruments[inst]:NULL;
				a->retrig=0;
				a->s3mtremor=0;
				a->ultoffset=0;
				a->main.sample=inst;
				break;
			default:
				UniSkipOpcode();
				break;
			}

		if (funky) {
			INSTRUMENT *i;
			SAMPLE *s;

			if ((i=a->main.i)) {
				if (i->samplenumber[a->anote] >= mod->numsmp) continue;
				s=&mod->samples[i->samplenumber[a->anote]];
				a->main.note=i->samplenote[a->anote];
			} else {
				a->main.note=a->anote;
				s=&mod->samples[a->main.sample];
			}

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

			/* channel or instrument determined panning ? */
			a->main.panning=mod->panning[channel];
			if (s->flags & SF_OWNPAN)
				a->main.panning=s->panning;
			else if ((i)&&(i->flags & IF_OWNPAN))
				a->main.panning=i->panning;

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

			if (i) {
				if ((mod->panflag)&&(i->flags & IF_PITCHPAN)
				   &&(a->main.panning!=PAN_SURROUND)){
					a->main.panning+=
					    ((a->anote-i->pitpancenter)*i->pitpansep)/8;
					if (a->main.panning<PAN_LEFT)
						a->main.panning=PAN_LEFT;
					else if (a->main.panning>PAN_RIGHT)
						a->main.panning=PAN_RIGHT;
				}
				a->main.pitflg=i->pitflg;
				a->main.volflg=i->volflg;
				a->main.panflg=i->panflg;
				a->main.nna=i->nnatype;
				a->dca=i->dca;
				a->dct=i->dct;
			} else {
				a->main.pitflg=a->main.volflg=a->main.panflg=0;
				a->main.nna=a->dca=0;
				a->dct=DCT_OFF;
			}

			if (funky&2) /* instrument change */ {
				/* IT random volume variations: 0:8 bit fixed, and one bit for
				   sign. */
				a->volume=a->tmpvolume=s->volume;
				if ((s)&&(i)) {
					if (i->rvolvar) {
						a->volume=a->tmpvolume=s->volume+
						  ((s->volume*((SLONG)i->rvolvar*(SLONG)getrandom(512)
						  ))/25600);
						if (a->volume<0)
							a->volume=a->tmpvolume=0;
						else if (a->volume>64)
							a->volume=a->tmpvolume=64;
					}
					if ((mod->panflag)&&(a->main.panning!=PAN_SURROUND)) {
						a->main.panning+=((a->main.panning*((SLONG)i->rpanvar*
						  (SLONG)getrandom(512)))/25600);
						if (a->main.panning<PAN_LEFT)
							a->main.panning=PAN_LEFT;
						else if (a->main.panning>PAN_RIGHT)
							a->main.panning=PAN_RIGHT;
					}
				}
			}

			a->wantedperiod=a->tmpperiod=
			    GetPeriod(mod->flags, (UWORD)a->main.note<<1,a->speed);
			a->main.keyoff=KEY_KICK;
		}
	}
}

/* Handles effects */
static void pt_EffectsPass1(MODULE *mod)
{
	SWORD channel;
	MP_CONTROL *a;
	MP_VOICE *aout;
	int explicitslides;

	for (channel=0;channel<mod->numchn;channel++) {
		a=&mod->control[channel];

		if ((aout=a->slave)) {
			a->main.fadevol=aout->main.fadevol;
			a->main.period=aout->main.period;
			if (a->main.kick==KICK_KEYOFF)
				a->main.keyoff=aout->main.keyoff;
		}

		if (!a->row) continue;
		UniSetRow(a->row);

		a->ownper=a->ownvol=0;
		explicitslides = pt_playeffects(mod, channel, a);

		/* continue volume slide if necessary for XM and IT */
		if (mod->flags&UF_BGSLIDES) {
			if (!explicitslides && a->sliding)
				DoS3MVolSlide(mod->vbtick, mod->flags, a, 0);
			else if (a->tmpvolume)
				a->sliding = explicitslides;
		}

		if (!a->ownper)
			a->main.period=a->tmpperiod;
		if (!a->ownvol)
			a->volume=a->tmpvolume;

		if (a->main.s) {
			if (a->main.i)
				a->main.outvolume=
				    (a->volume*a->main.s->globvol*a->main.i->globvol)>>10;
			else
				a->main.outvolume=(a->volume*a->main.s->globvol)>>4;
			if (a->main.outvolume>256)
				a->main.outvolume=256;
			else if (a->main.outvolume<0)
				a->main.outvolume=0;
		}
	}
}

/* NNA management */
static void pt_NNA(MODULE *mod)
{
	SWORD channel;
	MP_CONTROL *a;

	for (channel=0;channel<mod->numchn;channel++) {
		a=&mod->control[channel];

		if (a->main.kick==KICK_NOTE) {
			BOOL kill=0;

			if (a->slave) {
				MP_VOICE *aout;

				aout=a->slave;
				if (aout->main.nna & NNA_MASK) {
					/* 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->main.nna) {
					case NNA_CONTINUE: /* continue note, do nothing */
						break;
					case NNA_OFF: /* note off */
						aout->main.keyoff|=KEY_OFF;
						if ((!(aout->main.volflg & EF_ON))||
							  (aout->main.volflg & EF_LOOP))
							aout->main.keyoff=KEY_KILL;
						break;
					case NNA_FADE:
						aout->main.keyoff |= KEY_FADE;
						break;
					}
				}
			}

			if (a->dct!=DCT_OFF) {
				int t;

				for (t=0;t<NUMVOICES(mod);t++)
					if ((!Voice_Stopped_internal(t))&&
					   (mod->voice[t].masterchn==channel)&&
					   (a->main.sample==mod->voice[t].main.sample)) {
						kill=0;
						switch (a->dct) {
						case DCT_NOTE:
							if (a->main.note==mod->voice[t].main.note)
								kill=1;
							break;
						case DCT_SAMPLE:
							if (a->main.handle==mod->voice[t].main.handle)
								kill=1;
							break;
						case DCT_INST:
							kill=1;
							break;
						}
						if (kill)
							switch (a->dca) {
							case DCA_CUT:
								mod->voice[t].main.fadevol=0;
								break;
							case DCA_OFF:
								mod->voice[t].main.keyoff|=KEY_OFF;
								if ((!(mod->voice[t].main.volflg&EF_ON))||
								    (mod->voice[t].main.volflg&EF_LOOP))
									mod->voice[t].main.keyoff=KEY_KILL;
								break;
							case DCA_FADE:
								mod->voice[t].main.keyoff|=KEY_FADE;
								break;
							}
					}
			}
		} /* if (a->main.kick==KICK_NOTE) */
	}
}

/* Setup module and NNA voices */
static void pt_SetupVoices(MODULE *mod)
{
	SWORD channel;
	MP_CONTROL *a;
	MP_VOICE *aout;

	for (channel=0;channel<mod->numchn;channel++) {
		a=&mod->control[channel];

		if (a->main.notedelay) continue;
		if (a->main.kick==KICK_NOTE) {
			/* if no channel was cut above, find an empty or quiet channel
			   here */
			if (mod->flags&UF_NNA) {
				if (!a->slave) {
					int newchn;

					if ((newchn=MP_FindEmptyChannel(mod))!=-1)
						a->slave=&mod->voice[a->slavechn=newchn];
				}
			} else
				a->slave=&mod->voice[a->slavechn=channel];

			/* assign parts of MP_VOICE only done for a KICK_NOTE */
			if ((aout=a->slave)) {
				if (aout->mflag && aout->master) aout->master->slave=NULL;
				aout->master=a;
				a->slave=aout;
				aout->masterchn=channel;
				aout->mflag=1;
			}
		} else
			aout=a->slave;

		if (aout)
			aout->main=a->main;
		a->main.kick=KICK_ABSENT;
	}
}

/* second effect pass */
static void pt_EffectsPass2(MODULE *mod)
{
	SWORD channel;
	MP_CONTROL *a;
	UBYTE c;

	for (channel=0;channel<mod->numchn;channel++) {
		a=&mod->control[channel];

		if (!a->row) continue;
		UniSetRow(a->row);

		while((c=UniGetByte()))
			if (c==UNI_ITEFFECTS0) {
				c=UniGetByte();
				if ((c>>4)==SS_S7EFFECTS)
					DoNNAEffects(mod, a, c&0xf);
			} else
				UniSkipOpcode();
	}
}

void Player_HandleTick(void)
{
	SWORD channel;
	int max_volume;

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

	if ((!pf)||(pf->forbid)||(pf->sngpos>=pf->numpos)) return;

	/* update time counter (sngtime is in milliseconds (in fact 2^-10)) */
	pf->sngremainder+=(1<<9)*5; /* thus 2.5*(1<<10), since fps=0.4xtempo */
	pf->sngtime+=pf->sngremainder/pf->bpm;
	pf->sngremainder%=pf->bpm;

	if (++pf->vbtick>=pf->sngspd) {
		if (pf->pat_repcrazy)
			pf->pat_repcrazy=0; /* play 2 times row 0 */
		else
			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)
				/* so turn back pf->patpos by 1 */
				if (pf->patpos) pf->patpos--;
		}

		/* do we have to get a new patternpointer ? (when pf->patpos reaches the
		   pattern size, or when a patternbreak is active) */
		if (((pf->patpos>=pf->numrow)&&(pf->numrow>0))&&(!pf->posjmp))
			pf->posjmp=3;

		if (pf->posjmp) {
			pf->patpos=pf->numrow?(pf->patbrk%pf->numrow):0;
			pf->pat_repcrazy=0;
			pf->sngpos+=(pf->posjmp-2);
			for (channel=0;channel<pf->numchn;channel++)
				pf->control[channel].pat_reppos=-1;

			pf->patbrk=pf->posjmp=0;

			if (pf->sngpos<0) pf->sngpos=(SWORD)(pf->numpos-1);

			/* handle the "---" (end of song) pattern since it can occur
			   *inside* the module in some formats */
			if ((pf->sngpos>=pf->numpos)||
				(pf->positions[pf->sngpos]==LAST_PATTERN)) {
				if (!pf->wrap) return;
				if (!(pf->sngpos=pf->reppos)) {
				    pf->volume=pf->initvolume>128?128:pf->initvolume;
					if(pf->initspeed!=0)
						pf->sngspd=pf->initspeed<32?pf->initspeed:32;
					else
						pf->sngspd=6;
					pf->bpm=pf->inittempo<32?32:pf->inittempo;
				}
			}
		}

		if (!pf->patdly2)
			pt_Notes(pf);
	}

	/* Fade global volume if enabled and we're playing the last pattern */
	if (((pf->sngpos==pf->numpos-1)||
		 (pf->positions[pf->sngpos+1]==LAST_PATTERN))&&
	    (pf->fadeout))
		max_volume=pf->numrow?((pf->numrow-pf->patpos)*128)/pf->numrow:0;
	else
		max_volume=128;

	pt_EffectsPass1(pf);
	if (pf->flags&UF_NNA)
		pt_NNA(pf);
	pt_SetupVoices(pf);
	pt_EffectsPass2(pf);

	/* now set up the actual hardware channel playback information */
	pt_UpdateVoices(pf, max_volume);
}

static void Player_Init_internal(MODULE* mod)
{
	int t;

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

	mod->sngtime=0;
	mod->sngremainder=0;

	mod->pat_repcrazy=0;
	mod->sngpos=0;
	if(mod->initspeed!=0)
		mod->sngspd=mod->initspeed<32?mod->initspeed:32;
	else
		mod->sngspd=6;
	mod->volume=mod->initvolume>128?128:mod->initvolume;

	mod->vbtick=mod->sngspd;
	mod->patdly=0;
	mod->patdly2=0;
	mod->bpm=mod->inittempo<32?32:mod->inittempo;
	mod->realchn=0;

	mod->patpos=0;
	mod->posjmp=2; /* make sure the player fetches the first note */
	mod->numrow=-1;
	mod->patbrk=0;
}

int Player_Init(MODULE* mod)
{
	mod->extspd=1;
	mod->panflag=1;
	mod->wrap=0;
	mod->loop=1;
	mod->fadeout=0;

	mod->relspd=0;

	/* make sure the player doesn't start with garbage */
	if (!(mod->control=(MP_CONTROL*)MikMod_calloc(mod->numchn,sizeof(MP_CONTROL))))
		return 1;
	if (!(mod->voice=(MP_VOICE*)MikMod_calloc(md_sngchn,sizeof(MP_VOICE))))
		return 1;

	/* mod->numvoices was used during loading to clamp md_sngchn.
	   After loading it's used to remember how big mod->voice is.
	*/
	mod->numvoices = md_sngchn;

	Player_Init_internal(mod);
	return 0;
}

void Player_Exit_internal(MODULE* mod)
{
	if (!mod)
		return;

	/* Stop playback if necessary */
	if (mod==pf) {
		Player_Stop_internal();
		pf=NULL;
	}

	MikMod_free(mod->control);
	MikMod_free(mod->voice);
	mod->control=NULL;
	mod->voice=NULL;
}

void Player_Exit(MODULE* mod)
{
	MUTEX_LOCK(vars);
	Player_Exit_internal(mod);
	MUTEX_UNLOCK(vars);
}

MIKMODAPI void Player_SetVolume(SWORD volume)
{
	MUTEX_LOCK(vars);
	if (pf) {
		pf->volume=(volume<0)?0:(volume>128)?128:volume;
		pf->initvolume=pf->volume;
	}
	MUTEX_UNLOCK(vars);
}

MIKMODAPI MODULE* Player_GetModule(void)
{
	MODULE* result;

	MUTEX_LOCK(vars);
	result=pf;
	MUTEX_UNLOCK(vars);

	return result;
}

MIKMODAPI void Player_Start(MODULE *mod)
{
	int t;

	if (!mod)
		return;

	if (!MikMod_Active())
		MikMod_EnableOutput();

	mod->forbid=0;

	MUTEX_LOCK(vars);
	if (pf!=mod) {
		/* new song is being started, so completely stop out the old one. */
		if (pf) pf->forbid=1;
		for (t=0;t<md_sngchn;t++) Voice_Stop_internal(t);
	}
	pf=mod;
	MUTEX_UNLOCK(vars);
}

void Player_Stop_internal(void)
{
	if (!md_sfxchn) MikMod_DisableOutput_internal();
	if (pf) pf->forbid=1;
	pf=NULL;
}

MIKMODAPI void Player_Stop(void)
{
	MUTEX_LOCK(vars);
		Player_Stop_internal();
	MUTEX_UNLOCK(vars);
}

MIKMODAPI BOOL Player_Active(void)
{
	BOOL result=0;

	MUTEX_LOCK(vars);
	if (pf)
		result=(!(pf->sngpos>=pf->numpos));
	MUTEX_UNLOCK(vars);

	return result;
}

MIKMODAPI void Player_NextPosition(void)
{
	MUTEX_LOCK(vars);
	if (pf) {
		int t;

		pf->forbid=1;
		pf->posjmp=3;
		pf->patbrk=0;
		pf->vbtick=pf->sngspd;

		for (t=0;t<NUMVOICES(pf);t++) {
			Voice_Stop_internal(t);
			pf->voice[t].main.i=NULL;
			pf->voice[t].main.s=NULL;
		}
		for (t=0;t<pf->numchn;t++) {
			pf->control[t].main.i=NULL;
			pf->control[t].main.s=NULL;
		}
		pf->forbid=0;
	}
	MUTEX_UNLOCK(vars);
}

MIKMODAPI void Player_PrevPosition(void)
{
	MUTEX_LOCK(vars);
	if (pf) {
		int t;

		pf->forbid=1;
		pf->posjmp=1;
		pf->patbrk=0;
		pf->vbtick=pf->sngspd;

		for (t=0;t<NUMVOICES(pf);t++) {
			Voice_Stop_internal(t);
			pf->voice[t].main.i=NULL;
			pf->voice[t].main.s=NULL;
		}
		for (t=0;t<pf->numchn;t++) {
			pf->control[t].main.i=NULL;
			pf->control[t].main.s=NULL;
		}
		pf->forbid=0;
	}
	MUTEX_UNLOCK(vars);
}

MIKMODAPI void Player_SetPosition(UWORD pos)
{
	MUTEX_LOCK(vars);
	if (pf) {
		int t;

		pf->forbid=1;
		if (pos>=pf->numpos) pos=pf->numpos;
		pf->posjmp=2;
		pf->patbrk=0;
		pf->sngpos=pos;
		pf->vbtick=pf->sngspd;

		for (t=0;t<NUMVOICES(pf);t++) {
			Voice_Stop_internal(t);
			pf->voice[t].main.i=NULL;
			pf->voice[t].main.s=NULL;
		}
		for (t=0;t<pf->numchn;t++) {
			pf->control[t].main.i=NULL;
			pf->control[t].main.s=NULL;
		}
		pf->forbid=0;

		if (!pos)
			Player_Init_internal(pf);
	}
	MUTEX_UNLOCK(vars);
}

static void Player_Unmute_internal(SLONG arg1,va_list ap)
{
	SLONG t,arg2,arg3=0;

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

MIKMODAPI void Player_Unmute(SLONG arg1, ...)
{
	va_list args;

	va_start(args,arg1);
	MUTEX_LOCK(vars);
	Player_Unmute_internal(arg1,args);
	MUTEX_UNLOCK(vars);
	va_end(args);
}

static void Player_Mute_internal(SLONG arg1,va_list ap)
{
	SLONG t,arg2,arg3=0;

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

MIKMODAPI void Player_Mute(SLONG arg1,...)
{
	va_list args;

	va_start(args,arg1);
	MUTEX_LOCK(vars);
	Player_Mute_internal(arg1,args);
	MUTEX_UNLOCK(vars);
	va_end(args);
}

static void Player_ToggleMute_internal(SLONG arg1,va_list ap)
{
	SLONG arg2,arg3=0;
	ULONG t;

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

MIKMODAPI void Player_ToggleMute(SLONG arg1,...)
{
	va_list args;

	va_start(args,arg1);
	MUTEX_LOCK(vars);
	Player_ToggleMute_internal(arg1,args);
	MUTEX_UNLOCK(vars);
	va_end(args);
}

MIKMODAPI BOOL Player_Muted(UBYTE chan)
{
	BOOL result=1;

	MUTEX_LOCK(vars);
	if (pf)
		result=(chan<pf->numchn)?pf->control[chan].muted:1;
	MUTEX_UNLOCK(vars);

	return result;
}

MIKMODAPI int Player_GetChannelVoice(UBYTE chan)
{
	int result=0;

	MUTEX_LOCK(vars);
	if (pf)
		result=(chan<pf->numchn)?pf->control[chan].slavechn:-1;
	MUTEX_UNLOCK(vars);

	return result;
}

MIKMODAPI UWORD Player_GetChannelPeriod(UBYTE chan)
{
	UWORD result=0;

	MUTEX_LOCK(vars);
	if (pf)
	    result=(chan<pf->numchn)?pf->control[chan].main.period:0;
	MUTEX_UNLOCK(vars);

	return result;
}

BOOL Player_Paused_internal(void)
{
	return pf?pf->forbid:1;
}

MIKMODAPI BOOL Player_Paused(void)
{
	BOOL result;

	MUTEX_LOCK(vars);
	result=Player_Paused_internal();
	MUTEX_UNLOCK(vars);

	return result;
}

MIKMODAPI void Player_TogglePause(void)
{
	MUTEX_LOCK(vars);
	if (pf)
		pf->forbid=1-pf->forbid;
	MUTEX_UNLOCK(vars);
}

MIKMODAPI void Player_SetSpeed(UWORD speed)
{
	MUTEX_LOCK(vars);
	if (pf)
		pf->sngspd=speed?(speed<32?speed:32):1;
	MUTEX_UNLOCK(vars);
}

MIKMODAPI void Player_SetTempo(UWORD tempo)
{
	if (tempo<32) tempo=32;
	MUTEX_LOCK(vars);
	if (pf) {
		if ((!(pf->flags&UF_HIGHBPM))&&(tempo>255)) tempo=255;
		pf->bpm=tempo;
	}
	MUTEX_UNLOCK(vars);
}

MIKMODAPI int Player_QueryVoices(UWORD numvoices, VOICEINFO *vinfo)
{
	int i;

	if (numvoices > md_sngchn)
		numvoices = md_sngchn;

	MUTEX_LOCK(vars);
	if (pf)
		for (i = 0; i < md_sngchn; i++) {
			vinfo [i].i = pf->voice[i].main.i;
			vinfo [i].s = pf->voice[i].main.s;
			vinfo [i].panning = pf->voice [i].main.panning;
			vinfo [i].volume = pf->voice [i].main.chanvol;
			vinfo [i].period = pf->voice [i].main.period;
			vinfo [i].kick = pf->voice [i].main.kick_flag;
			pf->voice [i].main.kick_flag = 0;
		}
	MUTEX_UNLOCK(vars);

	return numvoices;
}

/* Get current module order */
MIKMODAPI int Player_GetOrder(void)
{
	int ret;
	MUTEX_LOCK(vars);
	ret = pf ? pf->sngpos :0; /* pf->positions[pf->sngpos ? pf->sngpos-1 : 0]: 0; */
	MUTEX_UNLOCK(vars);
	return ret;
}

/* Get current module row */
MIKMODAPI int Player_GetRow(void)
{
	int ret;
	MUTEX_LOCK(vars);
	ret = pf ? pf->patpos : 0;
	MUTEX_UNLOCK(vars);
	return ret;
}

/* ex:set ts=4: */
