X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=dosdemo;a=blobdiff_plain;f=libs%2Fmikmod%2Floaders%2Fload_xm.c;fp=libs%2Fmikmod%2Floaders%2Fload_xm.c;h=f40f27c562ccc164b1b08eb3b59a0b3bb390c9af;hp=0000000000000000000000000000000000000000;hb=cf94899f4f4d8535074db6421245b973d2fcde8c;hpb=8024ae981f39d370af5cceb3cb97f62820b0a120 diff --git a/libs/mikmod/loaders/load_xm.c b/libs/mikmod/loaders/load_xm.c new file mode 100644 index 0000000..f40f27c --- /dev/null +++ b/libs/mikmod/loaders/load_xm.c @@ -0,0 +1,840 @@ +/* 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 +#endif + +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +#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 BOOL 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 BOOL 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;tnote; + 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 BOOL LoadPatterns(BOOL dummypat) +{ + int t,u,v,numtrk; + + if(!AllocTracks()) return 0; + if(!AllocPatterns()) return 0; + + numtrk=0; + for(t=0;tnumpat;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;upos; + + 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 BOOL LoadInstruments(void) +{ + int t,u, ck; + INSTRUMENT *d; + ULONG next=0; + UWORD wavcnt=0; + + if(!AllocInstruments()) return 0; + d=of.instruments; + for(t=0;tsamplenumber,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); + _mm_fseek(modreader,0,SEEK_END); + if ((headend<0) || (_mm_ftell(modreader)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;usamplenumber[u]=pth.what[u]+of.numsmp; + d->volfade = pth.volfade; + +#if defined __STDC__ || defined _MSC_VER || 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 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 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;ulength =_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; + + /* last instrument is at the end of file in version 0x0104 */ + if(_mm_eof(modreader) && (mh->version<0x0104 || tversion>0x0103) { + for(u=0;uversion<0x0104 || tid,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)) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + 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->bpm || mh->songlength > 256) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } +/* _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)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* 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->restartsonglength?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;torders[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.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;tsamplename = 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;usamplenumber[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; +} + +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: */