--- /dev/null
+/* MikMod sound library\r
+ (c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file\r
+ AUTHORS for complete list.\r
+\r
+ This library is free software; you can redistribute it and/or modify\r
+ it under the terms of the GNU Library General Public License as\r
+ published by the Free Software Foundation; either version 2 of\r
+ the License, or (at your option) any later version.\r
+ \r
+ This program is distributed in the hope that it will be useful,\r
+ but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ GNU Library General Public License for more details.\r
+ \r
+ You should have received a copy of the GNU Library General Public\r
+ License along with this library; if not, write to the Free Software\r
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA\r
+ 02111-1307, USA.\r
+*/\r
+\r
+/*==============================================================================\r
+\r
+ $Id: load_m15.c,v 1.1.1.1 2004/01/21 01:36:35 raph Exp $\r
+\r
+ 15 instrument MOD loader\r
+ Also supports Ultimate Sound Tracker (old M15 format)\r
+\r
+==============================================================================*/\r
+\r
+#ifdef HAVE_CONFIG_H\r
+#include "config.h"\r
+#endif\r
+\r
+#ifdef HAVE_UNISTD_H\r
+#include <unistd.h>\r
+#endif\r
+\r
+//#include <ctype.h>\r
+#include <stdio.h>\r
+#ifdef HAVE_MEMORY_H\r
+#include <memory.h>\r
+#endif\r
+#include <string.h>\r
+\r
+#include "mikmod_internals.h"\r
+\r
+#ifdef SUNOS\r
+extern int fprintf(FILE *, const char *, ...);\r
+#endif\r
+\r
+/*========== Module Structure */\r
+\r
+typedef struct MSAMPINFO {\r
+ CHAR samplename[23]; /* 22 in module, 23 in memory */\r
+ UWORD length;\r
+ UBYTE finetune;\r
+ UBYTE volume;\r
+ UWORD reppos;\r
+ UWORD replen;\r
+} MSAMPINFO;\r
+\r
+typedef struct MODULEHEADER {\r
+ CHAR songname[21]; /* the songname.., 20 in module, 21 in memory */\r
+ MSAMPINFO samples[15]; /* all sampleinfo */\r
+ UBYTE songlength; /* number of patterns used */\r
+ UBYTE magic1; /* should be 127 */\r
+ UBYTE positions[128]; /* which pattern to play at pos */\r
+} MODULEHEADER;\r
+\r
+typedef struct MODNOTE {\r
+ UBYTE a,b,c,d;\r
+} MODNOTE;\r
+\r
+/*========== Loader variables */\r
+\r
+static MODULEHEADER *mh = NULL;\r
+static MODNOTE *patbuf = NULL;\r
+static BOOL ust_loader = 0; /* if TRUE, load as an ust module. */\r
+\r
+/* known file formats which can confuse the loader */\r
+#define REJECT 2\r
+static char *signatures[REJECT]={\r
+ "CAKEWALK", /* cakewalk midi files */\r
+ "SZDD" /* Microsoft compressed files */\r
+};\r
+static int siglen[REJECT]={8,4};\r
+\r
+/*========== Loader code */\r
+\r
+static BOOL LoadModuleHeader(MODULEHEADER *mh)\r
+{\r
+ int t,u;\r
+\r
+ _mm_read_string(mh->songname,20,modreader);\r
+ mh->songname[20]=0; /* just in case */\r
+\r
+ /* sanity check : title should contain printable characters and a bunch\r
+ of null chars */\r
+ for(t=0;t<20;t++)\r
+ if((mh->songname[t])&&(mh->songname[t]<32)) return 0;\r
+ for(t=0;(mh->songname[t])&&(t<20);t++);\r
+ if(t<20) for(;t<20;t++) if(mh->songname[t]) return 0;\r
+\r
+ for(t=0;t<15;t++) {\r
+ MSAMPINFO *s=&mh->samples[t];\r
+\r
+ _mm_read_string(s->samplename,22,modreader);\r
+ s->samplename[22]=0; /* just in case */\r
+ s->length =_mm_read_M_UWORD(modreader);\r
+ s->finetune =_mm_read_UBYTE(modreader);\r
+ s->volume =_mm_read_UBYTE(modreader);\r
+ s->reppos =_mm_read_M_UWORD(modreader);\r
+ s->replen =_mm_read_M_UWORD(modreader);\r
+\r
+ /* sanity check : sample title should contain printable characters and\r
+ a bunch of null chars */\r
+ for(u=0;u<20;u++)\r
+ if((s->samplename[u])&&(s->samplename[u]</*32*/14)) return 0;\r
+ for(u=0;(s->samplename[u])&&(u<20);u++);\r
+ if(u<20) for(;u<20;u++) if(s->samplename[u]) return 0;\r
+\r
+ /* sanity check : finetune values */\r
+ if(s->finetune>>4) return 0;\r
+ }\r
+\r
+ mh->songlength =_mm_read_UBYTE(modreader);\r
+ mh->magic1 =_mm_read_UBYTE(modreader); /* should be 127 */\r
+\r
+ /* sanity check : no more than 128 positions, restart position in range */\r
+ if((!mh->songlength)||(mh->songlength>128)) return 0;\r
+ /* values encountered so far are 0x6a and 0x78 */\r
+ if(((mh->magic1&0xf8)!=0x78)&&(mh->magic1!=0x6a)&&(mh->magic1>mh->songlength)) return 0;\r
+\r
+ _mm_read_UBYTES(mh->positions,128,modreader);\r
+\r
+ /* sanity check : pattern range is 0..63 */\r
+ for(t=0;t<128;t++)\r
+ if(mh->positions[t]>63) return 0;\r
+\r
+ return(!_mm_eof(modreader));\r
+}\r
+\r
+/* Checks the patterns in the modfile for UST / 15-inst indications.\r
+ For example, if an effect 3xx is found, it is assumed that the song \r
+ is 15-inst. If a 1xx effect has dat greater than 0x20, it is UST. \r
+\r
+ Returns: 0 indecisive; 1 = UST; 2 = 15-inst */\r
+static int CheckPatternType(int numpat)\r
+{\r
+ int t;\r
+ UBYTE eff, dat;\r
+\r
+ for(t=0;t<numpat*(64U*4);t++) {\r
+ /* Load the pattern into the temp buffer and scan it */\r
+ _mm_read_UBYTE(modreader);_mm_read_UBYTE(modreader);\r
+ eff = _mm_read_UBYTE(modreader);\r
+ dat = _mm_read_UBYTE(modreader);\r
+\r
+ switch(eff) {\r
+ case 1:\r
+ if(dat>0x1f) return 1;\r
+ if(dat<0x3) return 2;\r
+ break;\r
+ case 2:\r
+ if(dat>0x1f) return 1;\r
+ return 2;\r
+ case 3:\r
+ if (dat) return 2;\r
+ break;\r
+ default:\r
+ return 2;\r
+ }\r
+ }\r
+ return 0;\r
+}\r
+\r
+static BOOL M15_Test(void)\r
+{\r
+ int t, numpat;\r
+ MODULEHEADER mh;\r
+\r
+ ust_loader = 0;\r
+ if(!LoadModuleHeader(&mh)) return 0;\r
+\r
+ /* reject other file types */\r
+ for(t=0;t<REJECT;t++)\r
+ if(!memcmp(mh.songname,signatures[t],siglen[t])) return 0;\r
+\r
+ if(mh.magic1>127) return 0;\r
+ if((!mh.songlength)||(mh.songlength>mh.magic1)) return 0;\r
+\r
+ for(t=0;t<15;t++) {\r
+ /* all finetunes should be zero */\r
+ if(mh.samples[t].finetune) return 0;\r
+\r
+ /* all volumes should be <= 64 */\r
+ if(mh.samples[t].volume>64) return 0;\r
+\r
+ /* all instrument names should begin with s, st-, or a number */\r
+ if((mh.samples[t].samplename[0]=='s')||\r
+ (mh.samples[t].samplename[0]=='S')) {\r
+ if((memcmp(mh.samples[t].samplename,"st-",3)) &&\r
+ (memcmp(mh.samples[t].samplename,"ST-",3)) &&\r
+ (*mh.samples[t].samplename))\r
+ ust_loader = 1;\r
+ } else\r
+ if(!isdigit((int)mh.samples[t].samplename[0]))\r
+ ust_loader = 1;\r
+\r
+ if(mh.samples[t].length>4999||mh.samples[t].reppos>9999) {\r
+ ust_loader = 0;\r
+ if(mh.samples[t].length>32768) return 0;\r
+ }\r
+\r
+ /* if loop information is incorrect as words, but correct as bytes,\r
+ this is likely to be an ust-style module */\r
+ if((mh.samples[t].reppos+mh.samples[t].replen>mh.samples[t].length)&&\r
+ (mh.samples[t].reppos+mh.samples[t].replen<(mh.samples[t].length<<1))){\r
+ ust_loader = 1;\r
+ return 1;\r
+ }\r
+\r
+ if(!ust_loader) return 1; \r
+ }\r
+\r
+ for(numpat=0,t=0;t<mh.songlength;t++) \r
+ if(mh.positions[t]>numpat)\r
+ numpat = mh.positions[t];\r
+ numpat++;\r
+ switch(CheckPatternType(numpat)) {\r
+ case 0: /* indecisive, so check more clues... */\r
+ break;\r
+ case 1:\r
+ ust_loader = 1;\r
+ break;\r
+ case 2:\r
+ ust_loader = 0;\r
+ break;\r
+ }\r
+ return 1;\r
+}\r
+\r
+static BOOL M15_Init(void)\r
+{\r
+ if(!(mh=(MODULEHEADER*)_mm_malloc(sizeof(MODULEHEADER)))) return 0;\r
+ return 1;\r
+}\r
+\r
+static void M15_Cleanup(void)\r
+{\r
+ _mm_free(mh);\r
+ _mm_free(patbuf);\r
+}\r
+\r
+/*\r
+Old (amiga) noteinfo:\r
+\r
+ _____byte 1_____ byte2_ _____byte 3_____ byte4_\r
+/ \ / \ / \ / \\r
+0000 0000-00000000 0000 0000-00000000\r
+\r
+Upper four 12 bits for Lower four Effect command.\r
+bits of sam- note period. bits of sam-\r
+ple number. ple number.\r
+*/\r
+\r
+static UBYTE M15_ConvertNote(MODNOTE* n, UBYTE lasteffect)\r
+{\r
+ UBYTE instrument,effect,effdat,note;\r
+ UWORD period;\r
+ UBYTE lastnote=0;\r
+\r
+ /* decode the 4 bytes that make up a single note */\r
+ instrument = n->c>>4;\r
+ period = (((UWORD)n->a&0xf)<<8)+n->b;\r
+ effect = n->c&0xf;\r
+ effdat = n->d;\r
+\r
+ /* Convert the period to a note number */\r
+ note=0;\r
+ if(period) {\r
+ for(note=0;note<7*OCTAVE;note++)\r
+ if(period>=npertab[note]) break;\r
+ if(note==7*OCTAVE) note=0;\r
+ else note++;\r
+ }\r
+\r
+ if(instrument) {\r
+ /* if instrument does not exist, note cut */\r
+ if((instrument>15)||(!mh->samples[instrument-1].length)) {\r
+ UniPTEffect(0xc,0);\r
+ if(effect==0xc) effect=effdat=0;\r
+ } else {\r
+ /* if we had a note, then change instrument... */\r
+ if(note)\r
+ UniInstrument(instrument-1);\r
+ /* ...otherwise, only adjust volume... */\r
+ else {\r
+ /* ...unless an effect was specified, which forces a new note\r
+ to be played */\r
+ if(effect||effdat) {\r
+ UniInstrument(instrument-1);\r
+ note=lastnote;\r
+ } else\r
+ UniPTEffect(0xc,mh->samples[instrument-1].volume&0x7f);\r
+ }\r
+ }\r
+ }\r
+ if(note) {\r
+ UniNote(note+2*OCTAVE-1);\r
+ lastnote=note;\r
+ }\r
+\r
+ /* Convert pattern jump from Dec to Hex */\r
+ if(effect == 0xd)\r
+ effdat=(((effdat&0xf0)>>4)*10)+(effdat&0xf);\r
+\r
+ /* Volume slide, up has priority */\r
+ if((effect==0xa)&&(effdat&0xf)&&(effdat&0xf0))\r
+ effdat&=0xf0;\r
+\r
+ /* Handle ``heavy'' volumes correctly */\r
+ if ((effect == 0xc) && (effdat > 0x40))\r
+ effdat = 0x40;\r
+\r
+ if(ust_loader) {\r
+ switch(effect) {\r
+ case 0:\r
+ case 3:\r
+ break;\r
+ case 1:\r
+ UniPTEffect(0,effdat);\r
+ break;\r
+ case 2: \r
+ if(effdat&0xf) UniPTEffect(1,effdat&0xf);\r
+ else if(effdat>>2) UniPTEffect(2,effdat>>2);\r
+ break;\r
+ default:\r
+ UniPTEffect(effect,effdat);\r
+ break;\r
+ }\r
+ } else {\r
+ /* An isolated 100, 200 or 300 effect should be ignored (no\r
+ "standalone" porta memory in mod files). However, a sequence\r
+ such as 1XX, 100, 100, 100 is fine. */\r
+ if ((!effdat) && ((effect == 1)||(effect == 2)||(effect ==3)) &&\r
+ (lasteffect < 0x10) && (effect != lasteffect))\r
+ effect = 0;\r
+\r
+ UniPTEffect(effect,effdat);\r
+ }\r
+ if (effect == 8)\r
+ of.flags |= UF_PANNING;\r
+ \r
+ return effect;\r
+}\r
+\r
+static UBYTE *M15_ConvertTrack(MODNOTE* n)\r
+{\r
+ int t;\r
+ UBYTE lasteffect = 0x10; /* non existant effect */\r
+\r
+ UniReset();\r
+ for(t=0;t<64;t++) {\r
+ lasteffect = M15_ConvertNote(n,lasteffect);\r
+ UniNewline();\r
+ n+=4;\r
+ }\r
+ return UniDup();\r
+}\r
+\r
+/* Loads all patterns of a modfile and converts them into the 3 byte format. */\r
+static BOOL M15_LoadPatterns(void)\r
+{\r
+ int t,s,tracks=0;\r
+\r
+ if(!AllocPatterns()) return 0;\r
+ if(!AllocTracks()) return 0;\r
+\r
+ /* Allocate temporary buffer for loading and converting the patterns */\r
+ if(!(patbuf=(MODNOTE*)_mm_calloc(64U*4,sizeof(MODNOTE)))) return 0;\r
+\r
+ for(t=0;t<of.numpat;t++) {\r
+ /* Load the pattern into the temp buffer and convert it */\r
+ for(s=0;s<(64U*4);s++) {\r
+ patbuf[s].a=_mm_read_UBYTE(modreader);\r
+ patbuf[s].b=_mm_read_UBYTE(modreader);\r
+ patbuf[s].c=_mm_read_UBYTE(modreader);\r
+ patbuf[s].d=_mm_read_UBYTE(modreader);\r
+ }\r
+\r
+ for(s=0;s<4;s++)\r
+ if(!(of.tracks[tracks++]=M15_ConvertTrack(patbuf+s))) return 0;\r
+ }\r
+ return 1;\r
+}\r
+\r
+static BOOL M15_Load(BOOL curious)\r
+{\r
+ int t,scan;\r
+ SAMPLE *q;\r
+ MSAMPINFO *s;\r
+\r
+ /* try to read module header */\r
+ if(!LoadModuleHeader(mh)) {\r
+ _mm_errno = MMERR_LOADING_HEADER;\r
+ return 0;\r
+ }\r
+\r
+ if(ust_loader)\r
+ of.modtype = strdup("Ultimate Soundtracker");\r
+ else\r
+ of.modtype = strdup("Soundtracker");\r
+\r
+ /* set module variables */\r
+ of.initspeed = 6;\r
+ of.inittempo = 125;\r
+ of.numchn = 4; \r
+ of.songname = DupStr(mh->songname,21,1);\r
+ of.numpos = mh->songlength;\r
+ of.reppos = 0;\r
+\r
+ /* Count the number of patterns */\r
+ of.numpat = 0;\r
+ for(t=0;t<of.numpos;t++)\r
+ if(mh->positions[t]>of.numpat)\r
+ of.numpat=mh->positions[t];\r
+ /* since some old modules embed extra patterns, we have to check the\r
+ whole list to get the samples' file offsets right - however we can find\r
+ garbage here, so check carefully */\r
+ scan=1;\r
+ for(t=of.numpos;t<128;t++)\r
+ if(mh->positions[t]>=0x80) scan=0;\r
+ if (scan)\r
+ for(t=of.numpos;t<128;t++) {\r
+ if(mh->positions[t]>of.numpat)\r
+ of.numpat=mh->positions[t];\r
+ if((curious)&&(mh->positions[t])) of.numpos=t+1;\r
+ }\r
+ of.numpat++;\r
+ of.numtrk = of.numpat*of.numchn;\r
+\r
+ if(!AllocPositions(of.numpos)) return 0;\r
+ for(t=0;t<of.numpos;t++)\r
+ of.positions[t]=mh->positions[t];\r
+\r
+ /* Finally, init the sampleinfo structures */\r
+ of.numins=of.numsmp=15;\r
+ if(!AllocSamples()) return 0;\r
+\r
+ s = mh->samples;\r
+ q = of.samples;\r
+\r
+ for(t=0;t<of.numins;t++) {\r
+ /* convert the samplename */\r
+ q->samplename = DupStr(s->samplename,23,1);\r
+\r
+ /* init the sampleinfo variables and convert the size pointers */\r
+ q->speed = finetune[s->finetune&0xf];\r
+ q->volume = s->volume;\r
+ if(ust_loader)\r
+ q->loopstart = s->reppos;\r
+ else\r
+ q->loopstart = s->reppos<<1;\r
+ q->loopend = q->loopstart+(s->replen<<1);\r
+ q->length = s->length<<1;\r
+\r
+ q->flags = SF_SIGNED;\r
+ if(ust_loader) q->flags |= SF_UST_LOOP;\r
+ if(s->replen>2) q->flags |= SF_LOOP;\r
+\r
+ s++;\r
+ q++;\r
+ }\r
+\r
+ if(!M15_LoadPatterns()) return 0;\r
+ ust_loader = 0;\r
+\r
+ return 1;\r
+}\r
+\r
+static CHAR *M15_LoadTitle(void)\r
+{\r
+ CHAR s[21];\r
+\r
+ _mm_fseek(modreader,0,SEEK_SET);\r
+ if(!_mm_read_UBYTES(s,20,modreader)) return NULL;\r
+ s[20]=0; /* just in case */\r
+ return(DupStr(s,21,1));\r
+}\r
+\r
+/*========== Loader information */\r
+\r
+MIKMODAPI MLOADER load_m15={\r
+ NULL,\r
+ "15-instrument module",\r
+ "MOD (15 instrument)",\r
+ M15_Init,\r
+ M15_Test,\r
+ M15_Load,\r
+ M15_Cleanup,\r
+ M15_LoadTitle\r
+};\r
+\r
+/* ex:set ts=4: */\r