/* 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: load_m15.c,v 1.3 2005/04/07 19:57:38 realtech Exp $ 15 instrument MOD loader Also supports Ultimate Sound Tracker (old M15 format) ==============================================================================*/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_UNISTD_H #include #endif #include #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 MSAMPINFO { CHAR samplename[23]; /* 22 in module, 23 in memory */ UWORD length; UBYTE finetune; UBYTE volume; UWORD reppos; UWORD replen; } MSAMPINFO; typedef struct MODULEHEADER { CHAR songname[21]; /* the songname.., 20 in module, 21 in memory */ MSAMPINFO samples[15]; /* all sampleinfo */ UBYTE songlength; /* number of patterns used */ UBYTE magic1; /* should be 127 */ UBYTE positions[128]; /* which pattern to play at pos */ } MODULEHEADER; typedef struct MODNOTE { UBYTE a,b,c,d; } MODNOTE; /*========== Loader variables */ static MODULEHEADER *mh = NULL; static MODNOTE *patbuf = NULL; static int ust_loader = 0; /* if TRUE, load as an ust module. */ /* known file formats which can confuse the loader */ #define REJECT 2 static char *signatures[REJECT]={ "CAKEWALK", /* cakewalk midi files */ "SZDD" /* Microsoft compressed files */ }; static int siglen[REJECT]={8,4}; /*========== Loader code */ static int LoadModuleHeader(MODULEHEADER *mh) { int t,u; _mm_read_string(mh->songname,20,modreader); mh->songname[20]=0; /* just in case */ /* sanity check : title should contain printable characters and a bunch of null chars */ for(t=0;t<20;t++) if((mh->songname[t])&&(mh->songname[t]<32)) return 0; for(t=0;(mh->songname[t])&&(t<20);t++); if(t<20) for(;t<20;t++) if(mh->songname[t]) return 0; for(t=0;t<15;t++) { MSAMPINFO *s=&mh->samples[t]; _mm_read_string(s->samplename,22,modreader); s->samplename[22]=0; /* just in case */ s->length =_mm_read_M_UWORD(modreader); s->finetune =_mm_read_UBYTE(modreader); s->volume =_mm_read_UBYTE(modreader); s->reppos =_mm_read_M_UWORD(modreader); s->replen =_mm_read_M_UWORD(modreader); /* sanity check : sample title should contain printable characters and a bunch of null chars */ for(u=0;u<20;u++) if((s->samplename[u])&&(s->samplename[u]samplename[u])&&(u<20);u++); if(u<20) for(;u<20;u++) if(s->samplename[u]) return 0; /* sanity check : finetune values */ if(s->finetune>>4) return 0; } mh->songlength =_mm_read_UBYTE(modreader); mh->magic1 =_mm_read_UBYTE(modreader); /* should be 127 */ /* sanity check : no more than 128 positions, restart position in range */ if((!mh->songlength)||(mh->songlength>128)) return 0; /* values encountered so far are 0x6a and 0x78 */ if(((mh->magic1&0xf8)!=0x78)&&(mh->magic1!=0x6a)&&(mh->magic1>mh->songlength)) return 0; _mm_read_UBYTES(mh->positions,128,modreader); /* sanity check : pattern range is 0..63 */ for(t=0;t<128;t++) if(mh->positions[t]>63) return 0; return(!_mm_eof(modreader)); } /* Checks the patterns in the modfile for UST / 15-inst indications. For example, if an effect 3xx is found, it is assumed that the song is 15-inst. If a 1xx effect has dat greater than 0x20, it is UST. Returns: 0 indecisive; 1 = UST; 2 = 15-inst */ static int CheckPatternType(int numpat) { unsigned int t; UBYTE eff, dat; for(t=0;t0x1f) return 1; if(dat<0x3) return 2; break; case 2: if(dat>0x1f) return 1; return 2; case 3: if (dat) return 2; break; default: return 2; } } return 0; } static int M15_Test(void) { int t, numpat; MODULEHEADER mh; ust_loader = 0; if(!LoadModuleHeader(&mh)) return 0; /* reject other file types */ for(t=0;t127) return 0; if((!mh.songlength)||(mh.songlength>mh.magic1)) return 0; for(t=0;t<15;t++) { /* all finetunes should be zero */ if(mh.samples[t].finetune) return 0; /* all volumes should be <= 64 */ if(mh.samples[t].volume>64) return 0; /* all instrument names should begin with s, st-, or a number */ if((mh.samples[t].samplename[0]=='s')|| (mh.samples[t].samplename[0]=='S')) { if((memcmp(mh.samples[t].samplename,"st-",3)) && (memcmp(mh.samples[t].samplename,"ST-",3)) && (*mh.samples[t].samplename)) ust_loader = 1; } else if(!isdigit((int)mh.samples[t].samplename[0])) ust_loader = 1; if(mh.samples[t].length>4999||mh.samples[t].reppos>9999) { ust_loader = 0; if(mh.samples[t].length>32768) return 0; } /* if loop information is incorrect as words, but correct as bytes, this is likely to be an ust-style module */ if((mh.samples[t].reppos+mh.samples[t].replen>mh.samples[t].length)&& (mh.samples[t].reppos+mh.samples[t].replen<(mh.samples[t].length<<1))){ ust_loader = 1; return 1; } if(!ust_loader) return 1; } for(numpat=0,t=0;tnumpat) numpat = mh.positions[t]; numpat++; switch(CheckPatternType(numpat)) { case 0: /* indecisive, so check more clues... */ break; case 1: ust_loader = 1; break; case 2: ust_loader = 0; break; } return 1; } static int M15_Init(void) { if(!(mh=(MODULEHEADER*)MikMod_malloc(sizeof(MODULEHEADER)))) return 0; return 1; } static void M15_Cleanup(void) { MikMod_free(mh); MikMod_free(patbuf); } /* Old (amiga) noteinfo: _____byte 1_____ byte2_ _____byte 3_____ byte4_ / \ / \ / \ / \ 0000 0000-00000000 0000 0000-00000000 Upper four 12 bits for Lower four Effect command. bits of sam- note period. bits of sam- ple number. ple number. */ static UBYTE M15_ConvertNote(MODNOTE* n, UBYTE lasteffect) { UBYTE instrument,effect,effdat,note; UWORD period; UBYTE lastnote=0; /* decode the 4 bytes that make up a single note */ instrument = n->c>>4; period = (((UWORD)n->a&0xf)<<8)+n->b; effect = n->c&0xf; effdat = n->d; /* Convert the period to a note number */ note=0; if(period) { for(note=0;note<7*OCTAVE;note++) if(period>=npertab[note]) break; if(note==7*OCTAVE) note=0; else note++; } if(instrument) { /* if instrument does not exist, note cut */ if((instrument>15)||(!mh->samples[instrument-1].length)) { UniPTEffect(0xc,0); if(effect==0xc) effect=effdat=0; } else { /* if we had a note, then change instrument... */ if(note) UniInstrument(instrument-1); /* ...otherwise, only adjust volume... */ else { /* ...unless an effect was specified, which forces a new note to be played */ if(effect||effdat) { UniInstrument(instrument-1); note=lastnote; } else UniPTEffect(0xc,mh->samples[instrument-1].volume&0x7f); } } } if(note) { UniNote(note+2*OCTAVE-1); lastnote=note; } /* Convert pattern jump from Dec to Hex */ if(effect == 0xd) effdat=(((effdat&0xf0)>>4)*10)+(effdat&0xf); /* Volume slide, up has priority */ if((effect==0xa)&&(effdat&0xf)&&(effdat&0xf0)) effdat&=0xf0; /* Handle ``heavy'' volumes correctly */ if ((effect == 0xc) && (effdat > 0x40)) effdat = 0x40; if(ust_loader) { switch(effect) { case 0: case 3: break; case 1: UniPTEffect(0,effdat); break; case 2: if(effdat&0xf) UniPTEffect(1,effdat&0xf); else if(effdat>>2) UniPTEffect(2,effdat>>2); break; default: UniPTEffect(effect,effdat); break; } } else { /* An isolated 100, 200 or 300 effect should be ignored (no "standalone" porta memory in mod files). However, a sequence such as 1XX, 100, 100, 100 is fine. */ if ((!effdat) && ((effect == 1)||(effect == 2)||(effect ==3)) && (lasteffect < 0x10) && (effect != lasteffect)) effect = 0; UniPTEffect(effect,effdat); } if (effect == 8) of.flags |= UF_PANNING; return effect; } static UBYTE *M15_ConvertTrack(MODNOTE* n) { int t; UBYTE lasteffect = 0x10; /* non existant effect */ UniReset(); for(t=0;t<64;t++) { lasteffect = M15_ConvertNote(n,lasteffect); UniNewline(); n+=4; } return UniDup(); } /* Loads all patterns of a modfile and converts them into the 3 byte format. */ static int M15_LoadPatterns(void) { int t,tracks=0; unsigned int s; if(!AllocPatterns()) return 0; if(!AllocTracks()) return 0; /* Allocate temporary buffer for loading and converting the patterns */ if(!(patbuf=(MODNOTE*)MikMod_calloc(64U*4,sizeof(MODNOTE)))) return 0; for(t=0;tsongname,21,1); of.numpos = mh->songlength; of.reppos = 0; /* Count the number of patterns */ of.numpat = 0; for(t=0;tpositions[t]>of.numpat) of.numpat=mh->positions[t]; /* since some old modules embed extra patterns, we have to check the whole list to get the samples' file offsets right - however we can find garbage here, so check carefully */ scan=1; for(t=of.numpos;t<128;t++) if(mh->positions[t]>=0x80) scan=0; if (scan) for(t=of.numpos;t<128;t++) { if(mh->positions[t]>of.numpat) of.numpat=mh->positions[t]; if((curious)&&(mh->positions[t])) of.numpos=t+1; } of.numpat++; of.numtrk = of.numpat*of.numchn; if(!AllocPositions(of.numpos)) return 0; for(t=0;tpositions[t]; /* Finally, init the sampleinfo structures */ of.numins=of.numsmp=15; if(!AllocSamples()) return 0; s = mh->samples; q = of.samples; for(t=0;tsamplename = DupStr(s->samplename,23,1); /* init the sampleinfo variables and convert the size pointers */ q->speed = finetune[s->finetune&0xf]; q->volume = s->volume; if(ust_loader) q->loopstart = s->reppos; else q->loopstart = s->reppos<<1; q->loopend = q->loopstart+(s->replen<<1); q->length = s->length<<1; q->flags = SF_SIGNED; if(ust_loader) q->flags |= SF_UST_LOOP; if(s->replen>2) q->flags |= SF_LOOP; s++; q++; } if(!M15_LoadPatterns()) return 0; ust_loader = 0; return 1; } static CHAR *M15_LoadTitle(void) { CHAR s[21]; _mm_fseek(modreader,0,SEEK_SET); if(!_mm_read_UBYTES(s,20,modreader)) return NULL; s[20]=0; /* just in case */ return(DupStr(s,21,1)); } /*========== Loader information */ MIKMODAPI MLOADER load_m15={ NULL, "15-instrument module", "MOD (15 instrument)", M15_Init, M15_Test, M15_Load, M15_Cleanup, M15_LoadTitle }; /* ex:set ts=4: */