b4e70422a3
* Get rid of the non-functional GT2 loader * Add the UMX loader * Add HQ mixer routines (and make it configurable) * Allow samplerate to be configured at run/playtime * Support >64KHz mixing/playback * Correctly restore non-boost status (The diff to upstream is much smaller now too!) Change-Id: Iaa4ac901ba9cd4123bb225656976e78271353a72
3599 lines
81 KiB
C
3599 lines
81 KiB
C
/* 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 <limits.h>
|
|
|
|
#include "mikmod_internals.h"
|
|
|
|
#ifdef SUNOS
|
|
extern int fprintf(int, 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 const 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 const 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 const 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 const 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 const 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 const 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;
|
|
|
|
if (!t->pts) { /* FIXME: bad/crafted file. better/more general solution? */
|
|
t->b=0;
|
|
return t->env[0].val;
|
|
}
|
|
|
|
/* 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;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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 */
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)tick;
|
|
(void)flags;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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>(SLONG)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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
dat=UniGetByte();
|
|
|
|
if (tick || mod->patdly2)
|
|
return 0;
|
|
|
|
if (dat >= mod->numpos) { /* crafted file? */
|
|
/* fprintf(stderr,"DoPTEffectB: numpos=%d, dat=%d -> %d\n",mod->numpos,dat,mod->numpos-1);*/
|
|
dat=mod->numpos-1;
|
|
}
|
|
|
|
/* 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;
|
|
/* cancel the FT2 pattern loop (E60) bug.
|
|
* also see DoEEffects() below for it. */
|
|
if (flags & UF_FT2QUIRKS) mod->patbrk=0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int DoPTEffectC(UWORD tick, UWORD flags, MP_CONTROL *a, MODULE *mod, SWORD channel)
|
|
{
|
|
UBYTE dat;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
dat=UniGetByte();
|
|
if ((tick)||(mod->patdly2)) return 0;
|
|
if (dat && dat >= mod->numrow) { /* crafted file? */
|
|
/* fprintf(stderr,"DoPTEffectD: numrow=%d, dat=%d -> 0\n",mod->numrow,dat);*/
|
|
dat=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) */
|
|
/* emulate the FT2 pattern loop (E60) bug:
|
|
* http://milkytracker.org/docs/MilkyTracker.html#fxE6x
|
|
* roadblas.xm plays correctly with this. */
|
|
if (flags & UF_FT2QUIRKS) mod->patbrk=mod->patpos;
|
|
}
|
|
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;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
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)
|
|
{
|
|
(void)mod;
|
|
(void)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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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 */
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
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 */
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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)
|
|
{
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)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;
|
|
(void)flags;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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)
|
|
{
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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)
|
|
{
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)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;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
dat=UniGetByte();
|
|
if ((!tick)&&(a->main.i)) {
|
|
UWORD points;
|
|
INSTRUMENT *i=a->main.i;
|
|
MP_VOICE *aout;
|
|
|
|
if ((aout=a->slave) != NULL) {
|
|
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;
|
|
(void)flags;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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)
|
|
{
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)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)
|
|
{
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)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;
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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)
|
|
{
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)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;
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
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 */
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
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 */
|
|
(void)flags;
|
|
|
|
|
|
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>(SLONG)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;
|
|
(void)channel;
|
|
|
|
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();
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
if (offset)
|
|
a->ultoffset=offset;
|
|
|
|
a->main.start=a->ultoffset<<2;
|
|
if ((a->main.s)&&(a->main.start>(SLONG)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();
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)channel;
|
|
|
|
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;
|
|
(void)mod;
|
|
(void)channel;
|
|
|
|
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)
|
|
{
|
|
(void)tick;
|
|
(void)flags;
|
|
(void)a;
|
|
(void)mod;
|
|
(void)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()) != 0) {
|
|
#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)?(SLONG)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()) != 0)
|
|
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) != NULL) {
|
|
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) != NULL) {
|
|
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) {
|
|
int 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) != NULL) {
|
|
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()) != 0)
|
|
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 int Player_Active(void)
|
|
{
|
|
int 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;
|
|
SLONG 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 int Player_Muted(UBYTE chan)
|
|
{
|
|
int 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;
|
|
}
|
|
|
|
int Player_Paused_internal(void)
|
|
{
|
|
return pf?pf->forbid:1;
|
|
}
|
|
|
|
MIKMODAPI int Player_Paused(void)
|
|
{
|
|
int 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: */
|