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
841 lines
24 KiB
C
841 lines
24 KiB
C
/* MikMod sound library
|
|
(c) 1998, 1999, 2000, 2001, 2002 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$
|
|
|
|
Fasttracker (XM) module loader
|
|
|
|
==============================================================================*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#ifdef HAVE_MEMORY_H
|
|
#include <memory.h>
|
|
#endif
|
|
#include <string.h>
|
|
|
|
#include "mikmod_internals.h"
|
|
|
|
#ifdef SUNOS
|
|
extern int fprintf(FILE *, const char *, ...);
|
|
#endif
|
|
|
|
/*========== Module structure */
|
|
|
|
typedef struct XMHEADER {
|
|
CHAR id[17]; /* ID text: 'Extended module: ' */
|
|
CHAR songname[21]; /* Module name */
|
|
CHAR trackername[20]; /* Tracker name */
|
|
UWORD version; /* Version number */
|
|
ULONG headersize; /* Header size */
|
|
UWORD songlength; /* Song length (in patten order table) */
|
|
UWORD restart; /* Restart position */
|
|
UWORD numchn; /* Number of channels (2,4,6,8,10,...,32) */
|
|
UWORD numpat; /* Number of patterns (max 256) */
|
|
UWORD numins; /* Number of instruments (max 128) */
|
|
UWORD flags;
|
|
UWORD tempo; /* Default tempo */
|
|
UWORD bpm; /* Default BPM */
|
|
UBYTE orders[256]; /* Pattern order table */
|
|
} XMHEADER;
|
|
|
|
typedef struct XMINSTHEADER {
|
|
ULONG size; /* Instrument size */
|
|
CHAR name[22]; /* Instrument name */
|
|
UBYTE type; /* Instrument type (always 0) */
|
|
UWORD numsmp; /* Number of samples in instrument */
|
|
ULONG ssize;
|
|
} XMINSTHEADER;
|
|
|
|
#define XMENVCNT (12*2)
|
|
#define XMNOTECNT (8*OCTAVE)
|
|
typedef struct XMPATCHHEADER {
|
|
UBYTE what[XMNOTECNT]; /* Sample number for all notes */
|
|
UWORD volenv[XMENVCNT]; /* Points for volume envelope */
|
|
UWORD panenv[XMENVCNT]; /* Points for panning envelope */
|
|
UBYTE volpts; /* Number of volume points */
|
|
UBYTE panpts; /* Number of panning points */
|
|
UBYTE volsus; /* Volume sustain point */
|
|
UBYTE volbeg; /* Volume loop start point */
|
|
UBYTE volend; /* Volume loop end point */
|
|
UBYTE pansus; /* Panning sustain point */
|
|
UBYTE panbeg; /* Panning loop start point */
|
|
UBYTE panend; /* Panning loop end point */
|
|
UBYTE volflg; /* Volume type: bit 0: On; 1: Sustain; 2: Loop */
|
|
UBYTE panflg; /* Panning type: bit 0: On; 1: Sustain; 2: Loop */
|
|
UBYTE vibflg; /* Vibrato type */
|
|
UBYTE vibsweep; /* Vibrato sweep */
|
|
UBYTE vibdepth; /* Vibrato depth */
|
|
UBYTE vibrate; /* Vibrato rate */
|
|
UWORD volfade; /* Volume fadeout */
|
|
} XMPATCHHEADER;
|
|
|
|
typedef struct XMWAVHEADER {
|
|
ULONG length; /* Sample length */
|
|
ULONG loopstart; /* Sample loop start */
|
|
ULONG looplength; /* Sample loop length */
|
|
UBYTE volume; /* Volume */
|
|
SBYTE finetune; /* Finetune (signed byte -128..+127) */
|
|
UBYTE type; /* Loop type */
|
|
UBYTE panning; /* Panning (0-255) */
|
|
SBYTE relnote; /* Relative note number (signed byte) */
|
|
UBYTE reserved;
|
|
CHAR samplename[22]; /* Sample name */
|
|
UBYTE vibtype; /* Vibrato type */
|
|
UBYTE vibsweep; /* Vibrato sweep */
|
|
UBYTE vibdepth; /* Vibrato depth */
|
|
UBYTE vibrate; /* Vibrato rate */
|
|
} XMWAVHEADER;
|
|
|
|
typedef struct XMPATHEADER {
|
|
ULONG size; /* Pattern header length */
|
|
UBYTE packing; /* Packing type (always 0) */
|
|
UWORD numrows; /* Number of rows in pattern (1..256) */
|
|
SWORD packsize; /* Packed patterndata size */
|
|
} XMPATHEADER;
|
|
|
|
typedef struct XMNOTE {
|
|
UBYTE note,ins,vol,eff,dat;
|
|
} XMNOTE;
|
|
|
|
/*========== Loader variables */
|
|
|
|
static XMNOTE *xmpat=NULL;
|
|
static XMHEADER *mh=NULL;
|
|
|
|
/* increment unit for sample array reallocation */
|
|
#define XM_SMPINCR 64
|
|
static ULONG *nextwav=NULL;
|
|
static XMWAVHEADER *wh=NULL,*s=NULL;
|
|
|
|
/*========== Loader code */
|
|
|
|
static int XM_Test(void)
|
|
{
|
|
UBYTE id[38];
|
|
|
|
if(!_mm_read_UBYTES(id,38,modreader)) return 0;
|
|
if(memcmp(id,"Extended Module: ",17)) return 0;
|
|
if(id[37]==0x1a) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int XM_Init(void)
|
|
{
|
|
if(!(mh=(XMHEADER *)MikMod_malloc(sizeof(XMHEADER)))) return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void XM_Cleanup(void)
|
|
{
|
|
MikMod_free(mh);
|
|
mh=NULL;
|
|
}
|
|
|
|
static int XM_ReadNote(XMNOTE* n)
|
|
{
|
|
UBYTE cmp,result=1;
|
|
|
|
memset(n,0,sizeof(XMNOTE));
|
|
cmp=_mm_read_UBYTE(modreader);
|
|
|
|
if(cmp&0x80) {
|
|
if(cmp&1) { result++;n->note = _mm_read_UBYTE(modreader); }
|
|
if(cmp&2) { result++;n->ins = _mm_read_UBYTE(modreader); }
|
|
if(cmp&4) { result++;n->vol = _mm_read_UBYTE(modreader); }
|
|
if(cmp&8) { result++;n->eff = _mm_read_UBYTE(modreader); }
|
|
if(cmp&16) { result++;n->dat = _mm_read_UBYTE(modreader); }
|
|
} else {
|
|
n->note = cmp;
|
|
n->ins = _mm_read_UBYTE(modreader);
|
|
n->vol = _mm_read_UBYTE(modreader);
|
|
n->eff = _mm_read_UBYTE(modreader);
|
|
n->dat = _mm_read_UBYTE(modreader);
|
|
result += 4;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static UBYTE* XM_Convert(XMNOTE* xmtrack,UWORD rows)
|
|
{
|
|
int t;
|
|
UBYTE note,ins,vol,eff,dat;
|
|
|
|
UniReset();
|
|
for(t=0;t<rows;t++) {
|
|
note = xmtrack->note;
|
|
ins = xmtrack->ins;
|
|
vol = xmtrack->vol;
|
|
eff = xmtrack->eff;
|
|
dat = xmtrack->dat;
|
|
|
|
if(note) {
|
|
if(note>XMNOTECNT)
|
|
UniEffect(UNI_KEYFADE,0);
|
|
else
|
|
UniNote(note-1);
|
|
}
|
|
if(ins) UniInstrument(ins-1);
|
|
|
|
switch(vol>>4) {
|
|
case 0x6: /* volslide down */
|
|
if(vol&0xf) UniEffect(UNI_XMEFFECTA,vol&0xf);
|
|
break;
|
|
case 0x7: /* volslide up */
|
|
if(vol&0xf) UniEffect(UNI_XMEFFECTA,vol<<4);
|
|
break;
|
|
|
|
/* volume-row fine volume slide is compatible with protracker
|
|
EBx and EAx effects i.e. a zero nibble means DO NOT SLIDE, as
|
|
opposed to 'take the last sliding value'. */
|
|
case 0x8: /* finevol down */
|
|
UniPTEffect(0xe,0xb0|(vol&0xf));
|
|
break;
|
|
case 0x9: /* finevol up */
|
|
UniPTEffect(0xe,0xa0|(vol&0xf));
|
|
break;
|
|
case 0xa: /* set vibrato speed */
|
|
UniEffect(UNI_XMEFFECT4,vol<<4);
|
|
break;
|
|
case 0xb: /* vibrato */
|
|
UniEffect(UNI_XMEFFECT4,vol&0xf);
|
|
break;
|
|
case 0xc: /* set panning */
|
|
UniPTEffect(0x8,vol<<4);
|
|
break;
|
|
case 0xd: /* panning slide left (only slide when data not zero) */
|
|
if(vol&0xf) UniEffect(UNI_XMEFFECTP,vol&0xf);
|
|
break;
|
|
case 0xe: /* panning slide right (only slide when data not zero) */
|
|
if(vol&0xf) UniEffect(UNI_XMEFFECTP,vol<<4);
|
|
break;
|
|
case 0xf: /* tone porta */
|
|
UniPTEffect(0x3,vol<<4);
|
|
break;
|
|
default:
|
|
if((vol>=0x10)&&(vol<=0x50))
|
|
UniPTEffect(0xc,vol-0x10);
|
|
}
|
|
|
|
switch(eff) {
|
|
case 0x4:
|
|
UniEffect(UNI_XMEFFECT4,dat);
|
|
break;
|
|
case 0x6:
|
|
UniEffect(UNI_XMEFFECT6,dat);
|
|
break;
|
|
case 0xa:
|
|
UniEffect(UNI_XMEFFECTA,dat);
|
|
break;
|
|
case 0xe: /* Extended effects */
|
|
switch(dat>>4) {
|
|
case 0x1: /* XM fine porta up */
|
|
UniEffect(UNI_XMEFFECTE1,dat&0xf);
|
|
break;
|
|
case 0x2: /* XM fine porta down */
|
|
UniEffect(UNI_XMEFFECTE2,dat&0xf);
|
|
break;
|
|
case 0xa: /* XM fine volume up */
|
|
UniEffect(UNI_XMEFFECTEA,dat&0xf);
|
|
break;
|
|
case 0xb: /* XM fine volume down */
|
|
UniEffect(UNI_XMEFFECTEB,dat&0xf);
|
|
break;
|
|
default:
|
|
UniPTEffect(eff,dat);
|
|
}
|
|
break;
|
|
case 'G'-55: /* G - set global volume */
|
|
UniEffect(UNI_XMEFFECTG,dat>64?128:dat<<1);
|
|
break;
|
|
case 'H'-55: /* H - global volume slide */
|
|
UniEffect(UNI_XMEFFECTH,dat);
|
|
break;
|
|
case 'K'-55: /* K - keyOff and KeyFade */
|
|
UniEffect(UNI_KEYFADE,dat);
|
|
break;
|
|
case 'L'-55: /* L - set envelope position */
|
|
UniEffect(UNI_XMEFFECTL,dat);
|
|
break;
|
|
case 'P'-55: /* P - panning slide */
|
|
UniEffect(UNI_XMEFFECTP,dat);
|
|
break;
|
|
case 'R'-55: /* R - multi retrig note */
|
|
UniEffect(UNI_S3MEFFECTQ,dat);
|
|
break;
|
|
case 'T'-55: /* T - Tremor */
|
|
UniEffect(UNI_S3MEFFECTI,dat);
|
|
break;
|
|
case 'X'-55:
|
|
switch(dat>>4) {
|
|
case 1: /* X1 - Extra Fine Porta up */
|
|
UniEffect(UNI_XMEFFECTX1,dat&0xf);
|
|
break;
|
|
case 2: /* X2 - Extra Fine Porta down */
|
|
UniEffect(UNI_XMEFFECTX2,dat&0xf);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
if(eff<=0xf) {
|
|
/* the pattern jump destination is written in decimal,
|
|
but it seems some poor tracker software writes them
|
|
in hexadecimal... (sigh) */
|
|
if (eff==0xd)
|
|
/* don't change anything if we're sure it's in hexa */
|
|
if ((((dat&0xf0)>>4)<=9)&&((dat&0xf)<=9))
|
|
/* otherwise, convert from dec to hex */
|
|
dat=(((dat&0xf0)>>4)*10)+(dat&0xf);
|
|
UniPTEffect(eff,dat);
|
|
}
|
|
break;
|
|
}
|
|
UniNewline();
|
|
xmtrack++;
|
|
}
|
|
return UniDup();
|
|
}
|
|
|
|
static int LoadPatterns(int dummypat)
|
|
{
|
|
int t,u,v,numtrk;
|
|
|
|
if(!AllocTracks()) return 0;
|
|
if(!AllocPatterns()) return 0;
|
|
|
|
numtrk=0;
|
|
for(t=0;t<mh->numpat;t++) {
|
|
XMPATHEADER ph;
|
|
|
|
ph.size =_mm_read_I_ULONG(modreader);
|
|
if (ph.size<(mh->version==0x0102?8:9)) {
|
|
_mm_errno=MMERR_LOADING_PATTERN;
|
|
return 0;
|
|
}
|
|
ph.packing =_mm_read_UBYTE(modreader);
|
|
if(ph.packing) {
|
|
_mm_errno=MMERR_LOADING_PATTERN;
|
|
return 0;
|
|
}
|
|
if(mh->version==0x0102)
|
|
ph.numrows =_mm_read_UBYTE(modreader)+1;
|
|
else
|
|
ph.numrows =_mm_read_I_UWORD(modreader);
|
|
ph.packsize =_mm_read_I_UWORD(modreader);
|
|
|
|
ph.size-=(mh->version==0x0102?8:9);
|
|
if(ph.size)
|
|
_mm_fseek(modreader,ph.size,SEEK_CUR);
|
|
|
|
of.pattrows[t]=ph.numrows;
|
|
|
|
if(ph.numrows) {
|
|
if(!(xmpat=(XMNOTE*)MikMod_calloc(ph.numrows*of.numchn,sizeof(XMNOTE))))
|
|
return 0;
|
|
|
|
/* when packsize is 0, don't try to load a pattern.. it's empty. */
|
|
if(ph.packsize)
|
|
for(u=0;u<ph.numrows;u++)
|
|
for(v=0;v<of.numchn;v++) {
|
|
if(!ph.packsize) break;
|
|
|
|
ph.packsize-=XM_ReadNote(&xmpat[(v*ph.numrows)+u]);
|
|
if(ph.packsize<0) {
|
|
MikMod_free(xmpat);xmpat=NULL;
|
|
_mm_errno=MMERR_LOADING_PATTERN;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if(ph.packsize) {
|
|
_mm_fseek(modreader,ph.packsize,SEEK_CUR);
|
|
}
|
|
|
|
if(_mm_eof(modreader)) {
|
|
MikMod_free(xmpat);xmpat=NULL;
|
|
_mm_errno=MMERR_LOADING_PATTERN;
|
|
return 0;
|
|
}
|
|
|
|
for(v=0;v<of.numchn;v++)
|
|
of.tracks[numtrk++]=XM_Convert(&xmpat[v*ph.numrows],ph.numrows);
|
|
|
|
MikMod_free(xmpat);xmpat=NULL;
|
|
} else {
|
|
for(v=0;v<of.numchn;v++)
|
|
of.tracks[numtrk++]=XM_Convert(NULL,ph.numrows);
|
|
}
|
|
}
|
|
|
|
if(dummypat) {
|
|
of.pattrows[t]=64;
|
|
if(!(xmpat=(XMNOTE*)MikMod_calloc(64*of.numchn,sizeof(XMNOTE)))) return 0;
|
|
for(v=0;v<of.numchn;v++)
|
|
of.tracks[numtrk++]=XM_Convert(&xmpat[v*64],64);
|
|
MikMod_free(xmpat);xmpat=NULL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void FixEnvelope(ENVPT *cur, int pts)
|
|
{
|
|
int u, old, tmp;
|
|
ENVPT *prev;
|
|
|
|
/* Some broken XM editing program will only save the low byte
|
|
of the position value. Try to compensate by adding the
|
|
missing high byte. */
|
|
|
|
prev = cur++;
|
|
old = prev->pos;
|
|
|
|
for (u = 1; u < pts; u++, prev++, cur++) {
|
|
if (cur->pos < prev->pos) {
|
|
if (cur->pos < 0x100) {
|
|
if (cur->pos > old) /* same hex century */
|
|
tmp = cur->pos + (prev->pos - old);
|
|
else
|
|
tmp = cur->pos | ((prev->pos + 0x100) & 0xff00);
|
|
old = cur->pos;
|
|
cur->pos = tmp;
|
|
#ifdef MIKMOD_DEBUG
|
|
fprintf(stderr, "\rbroken envelope position(%d/%d), %d %d -> %d\n",
|
|
u, pts, prev->pos, old, cur->pos);
|
|
#endif
|
|
} else {
|
|
#ifdef MIKMOD_DEBUG
|
|
/* different brokenness style... fix unknown */
|
|
fprintf(stderr, "\rbroken envelope position(%d/%d), %d %d\n",
|
|
u, pts, old, cur->pos);
|
|
#endif
|
|
old = cur->pos;
|
|
}
|
|
} else
|
|
old = cur->pos;
|
|
}
|
|
}
|
|
|
|
static int LoadInstruments(void)
|
|
{
|
|
long filend,ck;
|
|
int t,u;
|
|
INSTRUMENT *d;
|
|
ULONG next=0;
|
|
UWORD wavcnt=0;
|
|
|
|
ck = _mm_ftell(modreader);
|
|
_mm_fseek(modreader,0,SEEK_END);
|
|
filend = _mm_ftell(modreader);
|
|
_mm_fseek(modreader,ck,SEEK_SET);
|
|
|
|
if(!AllocInstruments()) return 0;
|
|
d=of.instruments;
|
|
for(t=0;t<of.numins;t++,d++) {
|
|
XMINSTHEADER ih;
|
|
long headend;
|
|
|
|
memset(d->samplenumber,0xff,INSTNOTES*sizeof(UWORD));
|
|
|
|
/* read instrument header */
|
|
headend = _mm_ftell(modreader);
|
|
ih.size = _mm_read_I_ULONG(modreader);
|
|
headend += ih.size;
|
|
ck = _mm_ftell(modreader);
|
|
if ((headend<0) || (filend<headend) || (headend<ck)) {
|
|
break;
|
|
}
|
|
_mm_read_string(ih.name, 22, modreader);
|
|
ih.type = _mm_read_UBYTE(modreader);
|
|
ih.numsmp = _mm_read_I_UWORD(modreader);
|
|
|
|
d->insname = DupStr(ih.name,22,1);
|
|
|
|
if((SWORD)ih.size>29) {
|
|
ih.ssize = _mm_read_I_ULONG(modreader);
|
|
if(((SWORD)ih.numsmp>0)&&(ih.numsmp<=XMNOTECNT)) {
|
|
XMPATCHHEADER pth;
|
|
int p;
|
|
|
|
_mm_read_UBYTES (pth.what,XMNOTECNT,modreader);
|
|
_mm_read_I_UWORDS (pth.volenv, XMENVCNT, modreader);
|
|
_mm_read_I_UWORDS (pth.panenv, XMENVCNT, modreader);
|
|
pth.volpts = _mm_read_UBYTE(modreader);
|
|
pth.panpts = _mm_read_UBYTE(modreader);
|
|
pth.volsus = _mm_read_UBYTE(modreader);
|
|
pth.volbeg = _mm_read_UBYTE(modreader);
|
|
pth.volend = _mm_read_UBYTE(modreader);
|
|
pth.pansus = _mm_read_UBYTE(modreader);
|
|
pth.panbeg = _mm_read_UBYTE(modreader);
|
|
pth.panend = _mm_read_UBYTE(modreader);
|
|
pth.volflg = _mm_read_UBYTE(modreader);
|
|
pth.panflg = _mm_read_UBYTE(modreader);
|
|
pth.vibflg = _mm_read_UBYTE(modreader);
|
|
pth.vibsweep = _mm_read_UBYTE(modreader);
|
|
pth.vibdepth = _mm_read_UBYTE(modreader);
|
|
pth.vibrate = _mm_read_UBYTE(modreader);
|
|
pth.volfade = _mm_read_I_UWORD(modreader);
|
|
|
|
/* read the remainder of the header
|
|
(2 bytes for 1.03, 22 for 1.04) */
|
|
if (headend>=_mm_ftell(modreader)) {
|
|
for(u=headend-_mm_ftell(modreader);u;u--) {
|
|
_mm_skip_BYTE(modreader);
|
|
}
|
|
}
|
|
|
|
/* we can't trust the envelope point count here, as some
|
|
modules have incorrect values (K_OSPACE.XM reports 32 volume
|
|
points, for example). */
|
|
if(pth.volpts>XMENVCNT/2) pth.volpts=XMENVCNT/2;
|
|
if(pth.panpts>XMENVCNT/2) pth.panpts=XMENVCNT/2;
|
|
|
|
if((_mm_eof(modreader))||(pth.volpts>XMENVCNT/2)||(pth.panpts>XMENVCNT/2)) {
|
|
MikMod_free(nextwav);nextwav=NULL;
|
|
MikMod_free(wh);wh=NULL;
|
|
_mm_errno = MMERR_LOADING_SAMPLEINFO;
|
|
return 0;
|
|
}
|
|
|
|
for(u=0;u<XMNOTECNT;u++)
|
|
d->samplenumber[u]=pth.what[u]+of.numsmp;
|
|
d->volfade = pth.volfade;
|
|
|
|
#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C
|
|
#define XM_ProcessEnvelope(name) \
|
|
for (u = 0; u < (XMENVCNT >> 1); u++) { \
|
|
d-> name##env[u].pos = pth. name##env[u << 1]; \
|
|
d-> name##env[u].val = pth. name##env[(u << 1)+ 1]; \
|
|
} \
|
|
if (pth. name##flg&1) d-> name##flg|=EF_ON; \
|
|
if (pth. name##flg&2) d-> name##flg|=EF_SUSTAIN; \
|
|
if (pth. name##flg&4) d-> name##flg|=EF_LOOP; \
|
|
d-> name##susbeg=d-> name##susend=pth. name##sus; \
|
|
d-> name##beg=pth. name##beg; \
|
|
d-> name##end=pth. name##end; \
|
|
d-> name##pts=pth. name##pts; \
|
|
\
|
|
/* scale envelope */ \
|
|
for (p=0;p<XMENVCNT/2;p++) \
|
|
d-> name##env[p].val<<=2; \
|
|
\
|
|
if ((d-> name##flg&EF_ON)&&(d-> name##pts<2)) \
|
|
d-> name##flg&=~EF_ON
|
|
#else
|
|
#define XM_ProcessEnvelope(name) \
|
|
for (u = 0; u < (XMENVCNT >> 1); u++) { \
|
|
d-> name/**/env[u].pos = pth. name/**/env[u << 1]; \
|
|
d-> name/**/env[u].val = pth. name/**/env[(u << 1)+ 1]; \
|
|
} \
|
|
if (pth. name/**/flg&1) d-> name/**/flg|=EF_ON; \
|
|
if (pth. name/**/flg&2) d-> name/**/flg|=EF_SUSTAIN; \
|
|
if (pth. name/**/flg&4) d-> name/**/flg|=EF_LOOP; \
|
|
d-> name/**/susbeg=d-> name/**/susend= \
|
|
pth. name/**/sus; \
|
|
d-> name/**/beg=pth. name/**/beg; \
|
|
d-> name/**/end=pth. name/**/end; \
|
|
d-> name/**/pts=pth. name/**/pts; \
|
|
\
|
|
/* scale envelope */ \
|
|
for (p=0;p<XMENVCNT/2;p++) \
|
|
d-> name/**/env[p].val<<=2; \
|
|
\
|
|
if ((d-> name/**/flg&EF_ON)&&(d-> name/**/pts<2)) \
|
|
d-> name/**/flg&=~EF_ON
|
|
#endif
|
|
|
|
XM_ProcessEnvelope(vol);
|
|
XM_ProcessEnvelope(pan);
|
|
#undef XM_ProcessEnvelope
|
|
|
|
if (d->volflg & EF_ON)
|
|
FixEnvelope(d->volenv, d->volpts);
|
|
if (d->panflg & EF_ON)
|
|
FixEnvelope(d->panenv, d->panpts);
|
|
|
|
/* Samples are stored outside the instrument struct now, so we
|
|
have to load them all into a temp area, count the of.numsmp
|
|
along the way and then do an AllocSamples() and move
|
|
everything over */
|
|
if(mh->version>0x0103) next = 0;
|
|
for(u=0;u<ih.numsmp;u++,s++) {
|
|
/* XM sample header is 40 bytes: make sure we won't hit EOF */
|
|
/* Note: last instrument is at the end of file in version 0x0104 */
|
|
if(_mm_ftell(modreader)+40>filend) {
|
|
MikMod_free(nextwav);MikMod_free(wh);
|
|
nextwav=NULL;wh=NULL;
|
|
_mm_errno = MMERR_LOADING_SAMPLEINFO;
|
|
return 0;
|
|
}
|
|
/* Allocate more room for sample information if necessary */
|
|
if(of.numsmp+u==wavcnt) {
|
|
wavcnt+=XM_SMPINCR;
|
|
if(!(nextwav=(ULONG*)MikMod_realloc(nextwav,wavcnt*sizeof(ULONG)))){
|
|
MikMod_free(wh);wh=NULL;
|
|
_mm_errno = MMERR_OUT_OF_MEMORY;
|
|
return 0;
|
|
}
|
|
if(!(wh=(XMWAVHEADER*)MikMod_realloc(wh,wavcnt*sizeof(XMWAVHEADER)))) {
|
|
MikMod_free(nextwav);nextwav=NULL;
|
|
_mm_errno = MMERR_OUT_OF_MEMORY;
|
|
return 0;
|
|
}
|
|
s=wh+(wavcnt-XM_SMPINCR);
|
|
}
|
|
|
|
s->length =_mm_read_I_ULONG (modreader);
|
|
s->loopstart =_mm_read_I_ULONG (modreader);
|
|
s->looplength =_mm_read_I_ULONG (modreader);
|
|
s->volume =_mm_read_UBYTE (modreader);
|
|
s->finetune =_mm_read_SBYTE (modreader);
|
|
s->type =_mm_read_UBYTE (modreader);
|
|
s->panning =_mm_read_UBYTE (modreader);
|
|
s->relnote =_mm_read_SBYTE (modreader);
|
|
s->vibtype = pth.vibflg;
|
|
s->vibsweep = pth.vibsweep;
|
|
s->vibdepth = pth.vibdepth*4;
|
|
s->vibrate = pth.vibrate;
|
|
s->reserved =_mm_read_UBYTE (modreader);
|
|
_mm_read_string(s->samplename, 22, modreader);
|
|
|
|
nextwav[of.numsmp+u]=next;
|
|
next+=s->length;
|
|
}
|
|
|
|
if(mh->version>0x0103) {
|
|
for(u=0;u<ih.numsmp;u++)
|
|
nextwav[of.numsmp++]+=_mm_ftell(modreader);
|
|
_mm_fseek(modreader,next,SEEK_CUR);
|
|
} else
|
|
of.numsmp+=ih.numsmp;
|
|
} else {
|
|
/* read the remainder of the header */
|
|
ck = _mm_ftell(modreader);
|
|
if ((headend<0) || (filend<headend) || (headend<ck)) {
|
|
break;
|
|
}
|
|
for(u=headend-_mm_ftell(modreader);u;u--) {
|
|
_mm_skip_BYTE(modreader);
|
|
}
|
|
|
|
/* last instrument is at the end of file in version 0x0104 */
|
|
if(_mm_eof(modreader) && (mh->version<0x0104 || t<of.numins-1)) {
|
|
MikMod_free(nextwav);MikMod_free(wh);
|
|
nextwav=NULL;wh=NULL;
|
|
_mm_errno = MMERR_LOADING_SAMPLEINFO;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* sanity check */
|
|
if(!of.numsmp) {
|
|
MikMod_free(nextwav);nextwav=NULL;
|
|
MikMod_free(wh);wh=NULL;
|
|
_mm_errno = MMERR_LOADING_SAMPLEINFO;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int XM_Load(int curious)
|
|
{
|
|
INSTRUMENT *d;
|
|
SAMPLE *q;
|
|
int t,u;
|
|
int dummypat=0;
|
|
char tracker[21],modtype[60];
|
|
(void)curious;
|
|
|
|
/* try to read module header */
|
|
_mm_read_string(mh->id,17,modreader);
|
|
_mm_read_string(mh->songname,21,modreader);
|
|
_mm_read_string(mh->trackername,20,modreader);
|
|
mh->version =_mm_read_I_UWORD(modreader);
|
|
if(mh->version < 0x102 || mh->version > 0x104)
|
|
goto bad_xm;
|
|
mh->headersize =_mm_read_I_ULONG(modreader);
|
|
mh->songlength =_mm_read_I_UWORD(modreader);
|
|
mh->restart =_mm_read_I_UWORD(modreader);
|
|
mh->numchn =_mm_read_I_UWORD(modreader);
|
|
mh->numpat =_mm_read_I_UWORD(modreader);
|
|
mh->numins =_mm_read_I_UWORD(modreader);
|
|
mh->flags =_mm_read_I_UWORD(modreader);
|
|
mh->tempo =_mm_read_I_UWORD(modreader);
|
|
mh->bpm =_mm_read_I_UWORD(modreader);
|
|
if(mh->numchn > 64) goto bad_xm;
|
|
if(mh->tempo > 32 || mh->bpm < 32 || mh->bpm > 255)
|
|
goto bad_xm;
|
|
if(mh->songlength > 256 || mh->headersize < 20 || mh->headersize > 20+256)
|
|
goto bad_xm;
|
|
if(mh->numpat > 256 || mh->numins > 255 || mh->restart > 255)
|
|
goto bad_xm;
|
|
/* _mm_read_UBYTES(mh->orders,256,modreader);*/
|
|
/* _mm_read_UBYTES(mh->orders,mh->headersize-20,modreader);*/
|
|
_mm_read_UBYTES(mh->orders,mh->songlength,modreader);
|
|
if(_mm_fseek(modreader, mh->headersize+60, SEEK_SET) || _mm_eof(modreader))
|
|
goto bad_hdr;
|
|
|
|
/* set module variables */
|
|
of.initspeed = mh->tempo;
|
|
of.inittempo = mh->bpm;
|
|
strncpy(tracker,mh->trackername,20);tracker[20]=0;
|
|
for(t=20;(t>=0)&&(tracker[t]<=' ');t--) tracker[t]=0;
|
|
|
|
/* some modules have the tracker name empty */
|
|
if (!tracker[0])
|
|
strcpy(tracker,"Unknown tracker");
|
|
|
|
#ifdef HAVE_SNPRINTF
|
|
snprintf(modtype,60,"%s (XM format %d.%02d)",
|
|
tracker,mh->version>>8,mh->version&0xff);
|
|
#else
|
|
sprintf(modtype,"%s (XM format %d.%02d)",
|
|
tracker,mh->version>>8,mh->version&0xff);
|
|
#endif
|
|
of.modtype = MikMod_strdup(modtype);
|
|
of.numchn = mh->numchn;
|
|
of.numpat = mh->numpat;
|
|
of.numtrk = (UWORD)of.numpat*of.numchn; /* get number of channels */
|
|
of.songname = DupStr(mh->songname,20,1);
|
|
of.numpos = mh->songlength; /* copy the songlength */
|
|
of.reppos = mh->restart<mh->songlength?mh->restart:0;
|
|
of.numins = mh->numins;
|
|
of.flags |= UF_XMPERIODS | UF_INST | UF_NOWRAP | UF_FT2QUIRKS | UF_PANNING;
|
|
if(mh->flags&1) of.flags |= UF_LINEAR;
|
|
of.bpmlimit = 32;
|
|
|
|
memset(of.chanvol,64,of.numchn); /* store channel volumes */
|
|
|
|
if(!AllocPositions(of.numpos+1)) return 0;
|
|
for(t=0;t<of.numpos;t++)
|
|
of.positions[t]=mh->orders[t];
|
|
|
|
/* We have to check for any pattern numbers in the order list greater than
|
|
the number of patterns total. If one or more is found, we set it equal to
|
|
the pattern total and make a dummy pattern to workaround the problem */
|
|
for(t=0;t<of.numpos;t++) {
|
|
if(of.positions[t]>=of.numpat) {
|
|
of.positions[t]=of.numpat;
|
|
dummypat=1;
|
|
}
|
|
}
|
|
if(dummypat) {
|
|
of.numpat++;of.numtrk+=of.numchn;
|
|
}
|
|
|
|
if(mh->version<0x0104) {
|
|
if(!LoadInstruments()) return 0;
|
|
if(!LoadPatterns(dummypat)) return 0;
|
|
for(t=0;t<of.numsmp;t++)
|
|
nextwav[t]+=_mm_ftell(modreader);
|
|
} else {
|
|
if(!LoadPatterns(dummypat)) return 0;
|
|
if(!LoadInstruments()) return 0;
|
|
}
|
|
|
|
if(!AllocSamples()) {
|
|
MikMod_free(nextwav);MikMod_free(wh);
|
|
nextwav=NULL;wh=NULL;
|
|
return 0;
|
|
}
|
|
q = of.samples;
|
|
s = wh;
|
|
for(u=0;u<of.numsmp;u++,q++,s++) {
|
|
q->samplename = DupStr(s->samplename,22,1);
|
|
q->length = s->length;
|
|
q->loopstart = s->loopstart;
|
|
q->loopend = s->loopstart+s->looplength;
|
|
q->volume = s->volume;
|
|
q->speed = s->finetune+128;
|
|
q->panning = s->panning;
|
|
q->seekpos = nextwav[u];
|
|
q->vibtype = s->vibtype;
|
|
q->vibsweep = s->vibsweep;
|
|
q->vibdepth = s->vibdepth;
|
|
q->vibrate = s->vibrate;
|
|
|
|
if(s->type & 0x10) {
|
|
q->length >>= 1;
|
|
q->loopstart >>= 1;
|
|
q->loopend >>= 1;
|
|
}
|
|
|
|
q->flags|=SF_OWNPAN|SF_DELTA|SF_SIGNED;
|
|
if(s->type&0x3) q->flags|=SF_LOOP;
|
|
if(s->type&0x2) q->flags|=SF_BIDI;
|
|
if(s->type&0x10) q->flags|=SF_16BITS;
|
|
}
|
|
|
|
d=of.instruments;
|
|
s=wh;
|
|
for(u=0;u<of.numins;u++,d++)
|
|
for(t=0;t<XMNOTECNT;t++) {
|
|
if (d->samplenumber[t]>=of.numsmp)
|
|
d->samplenote[t]=255;
|
|
else {
|
|
int note=t+s[d->samplenumber[t]].relnote;
|
|
d->samplenote[t]=(note<0)?0:note;
|
|
}
|
|
}
|
|
|
|
MikMod_free(wh);MikMod_free(nextwav);
|
|
wh=NULL;nextwav=NULL;
|
|
return 1;
|
|
|
|
bad_hdr: _mm_errno = MMERR_LOADING_HEADER; return 0;
|
|
bad_xm: _mm_errno = MMERR_NOT_A_MODULE; return 0;
|
|
}
|
|
|
|
static CHAR *XM_LoadTitle(void)
|
|
{
|
|
CHAR str[21];
|
|
|
|
_mm_fseek(modreader,17,SEEK_SET);
|
|
if(!_mm_read_UBYTES(str, 21, modreader)) return NULL;
|
|
|
|
return(DupStr(str,21,1));
|
|
}
|
|
|
|
/*========== Loader information */
|
|
|
|
MIKMODAPI MLOADER load_xm={
|
|
NULL,
|
|
"XM",
|
|
"XM (FastTracker 2)",
|
|
XM_Init,
|
|
XM_Test,
|
|
XM_Load,
|
|
XM_Cleanup,
|
|
XM_LoadTitle
|
|
};
|
|
|
|
/* ex:set ts=4: */
|