Add "MikMod for Rockbox 0.1" from 2007-06-29
[mikmod-rockbox.git] / apps / plugins / mikmod / loaders / load_m15.c
diff --git a/apps/plugins/mikmod/loaders/load_m15.c b/apps/plugins/mikmod/loaders/load_m15.c
new file mode 100644 (file)
index 0000000..6adae0e
--- /dev/null
@@ -0,0 +1,505 @@
+/*     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