From 26f2bfde03420edad4de1f22cb3d515dc063b20d Mon Sep 17 00:00:00 2001 From: Frank Gevaerts Date: Sun, 12 Dec 2010 15:03:30 +0000 Subject: Add MikMod plugin, ported by Jason Yu, with some minor work by Craig Mann and William Peters (FS#8806) git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28810 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/mikmod/load_med.c | 719 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 719 insertions(+) create mode 100644 apps/plugins/mikmod/load_med.c (limited to 'apps/plugins/mikmod/load_med.c') diff --git a/apps/plugins/mikmod/load_med.c b/apps/plugins/mikmod/load_med.c new file mode 100644 index 0000000000..21a85b39a0 --- /dev/null +++ b/apps/plugins/mikmod/load_med.c @@ -0,0 +1,719 @@ +/* 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_med.c,v 1.3 2005/04/07 19:57:38 realtech Exp $ + + Amiga MED 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 information */ + +typedef struct MEDHEADER { + ULONG id; + ULONG modlen; + ULONG MEDSONGP; /* struct MEDSONG *song; */ + UWORD psecnum; /* for the player routine, MMD2 only */ + UWORD pseq; /* " " " " */ + ULONG MEDBlockPP; /* struct MEDBlock **blockarr; */ + ULONG reserved1; + ULONG MEDINSTHEADERPP; /* struct MEDINSTHEADER **smplarr; */ + ULONG reserved2; + ULONG MEDEXPP; /* struct MEDEXP *expdata; */ + ULONG reserved3; + UWORD pstate; /* some data for the player routine */ + UWORD pblock; + UWORD pline; + UWORD pseqnum; + SWORD actplayline; + UBYTE counter; + UBYTE extra_songs; /* number of songs - 1 */ +} MEDHEADER; + +typedef struct MEDSAMPLE { + UWORD rep, replen; /* offs: 0(s), 2(s) */ + UBYTE midich; /* offs: 4(s) */ + UBYTE midipreset; /* offs: 5(s) */ + UBYTE svol; /* offs: 6(s) */ + SBYTE strans; /* offs: 7(s) */ +} MEDSAMPLE; + +typedef struct MEDSONG { + MEDSAMPLE sample[63]; /* 63 * 8 bytes = 504 bytes */ + UWORD numblocks; /* offs: 504 */ + UWORD songlen; /* offs: 506 */ + UBYTE playseq[256]; /* offs: 508 */ + UWORD deftempo; /* offs: 764 */ + SBYTE playtransp; /* offs: 766 */ + UBYTE flags; /* offs: 767 */ + UBYTE flags2; /* offs: 768 */ + UBYTE tempo2; /* offs: 769 */ + UBYTE trkvol[16]; /* offs: 770 */ + UBYTE mastervol; /* offs: 786 */ + UBYTE numsamples; /* offs: 787 */ +} MEDSONG; + +typedef struct MEDEXP { + ULONG nextmod; /* pointer to next module */ + ULONG exp_smp; /* pointer to MEDINSTEXT array */ + UWORD s_ext_entries; + UWORD s_ext_entrsz; + ULONG annotxt; /* pointer to annotation text */ + ULONG annolen; + ULONG iinfo; /* pointer to MEDINSTINFO array */ + UWORD i_ext_entries; + UWORD i_ext_entrsz; + ULONG jumpmask; + ULONG rgbtable; + ULONG channelsplit; + ULONG n_info; + ULONG songname; /* pointer to songname */ + ULONG songnamelen; + ULONG dumps; + ULONG reserved2[7]; +} MEDEXP; + +typedef struct MMD0NOTE { + UBYTE a, b, c; +} MMD0NOTE; + +typedef struct MMD1NOTE { + UBYTE a, b, c, d; +} MMD1NOTE; + +typedef struct MEDINSTHEADER { + ULONG length; + SWORD type; + /* Followed by actual data */ +} MEDINSTHEADER; + +typedef struct MEDINSTEXT { + UBYTE hold; + UBYTE decay; + UBYTE suppress_midi_off; + SBYTE finetune; +} MEDINSTEXT; + +typedef struct MEDINSTINFO { + UBYTE name[40]; +} MEDINSTINFO; + +/*========== Loader variables */ + +#define MMD0_string 0x4D4D4430 +#define MMD1_string 0x4D4D4431 + +static MEDHEADER *mh = NULL; +static MEDSONG *ms = NULL; +static MEDEXP *me = NULL; +static ULONG *ba = NULL; +static MMD0NOTE *mmd0pat = NULL; +static MMD1NOTE *mmd1pat = NULL; + +static int decimalvolumes; +static int bpmtempos; + +#define d0note(row,col) mmd0pat[((row)*(UWORD)of.numchn)+(col)] +#define d1note(row,col) mmd1pat[((row)*(UWORD)of.numchn)+(col)] + +static CHAR MED_Version[] = "OctaMED (MMDx)"; + +/*========== Loader code */ + +int MED_Test(void) +{ + UBYTE id[4]; + + if (!_mm_read_UBYTES(id, 4, modreader)) + return 0; + if ((!memcmp(id, "MMD0", 4)) || (!memcmp(id, "MMD1", 4))) + return 1; + return 0; +} + +int MED_Init(void) +{ + if (!(me = (MEDEXP *)MikMod_malloc(sizeof(MEDEXP)))) + return 0; + if (!(mh = (MEDHEADER *)MikMod_malloc(sizeof(MEDHEADER)))) + return 0; + if (!(ms = (MEDSONG *)MikMod_malloc(sizeof(MEDSONG)))) + return 0; + return 1; +} + +void MED_Cleanup(void) +{ + MikMod_free(me); + MikMod_free(mh); + MikMod_free(ms); + MikMod_free(ba); + MikMod_free(mmd0pat); + MikMod_free(mmd1pat); +} + +static void EffectCvt(UBYTE eff, UBYTE dat) +{ + switch (eff) { + /* 0x0 0x1 0x2 0x3 0x4 PT effects */ + case 0x5: /* PT vibrato with speed/depth nibbles swapped */ + UniPTEffect(0x4, (dat >> 4) | ((dat & 0xf) << 4)); + break; + /* 0x6 0x7 not used */ + case 0x6: + case 0x7: + break; + case 0x8: /* midi hold/decay */ + break; + case 0x9: + if (bpmtempos) { + if (!dat) + dat = of.initspeed; + UniEffect(UNI_S3MEFFECTA, dat); + } else { + if (dat <= 0x20) { + if (!dat) + dat = of.initspeed; + else + dat /= 4; + UniPTEffect(0xf, dat); + } else + UniEffect(UNI_MEDSPEED, ((UWORD)dat * 125) / (33 * 4)); + } + break; + /* 0xa 0xb PT effects */ + case 0xc: + if (decimalvolumes) + dat = (dat >> 4) * 10 + (dat & 0xf); + UniPTEffect(0xc, dat); + break; + case 0xd: /* same as PT volslide */ + UniPTEffect(0xa, dat); + break; + case 0xe: /* synth jmp - midi */ + break; + case 0xf: + switch (dat) { + case 0: /* patternbreak */ + UniPTEffect(0xd, 0); + break; + case 0xf1: /* play note twice */ + UniWriteByte(UNI_MEDEFFECTF1); + break; + case 0xf2: /* delay note */ + UniWriteByte(UNI_MEDEFFECTF2); + break; + case 0xf3: /* play note three times */ + UniWriteByte(UNI_MEDEFFECTF3); + break; + case 0xfe: /* stop playing */ + UniPTEffect(0xb, of.numpat); + break; + case 0xff: /* note cut */ + UniPTEffect(0xc, 0); + break; + default: + if (dat <= 10) + UniPTEffect(0xf, dat); + else if (dat <= 240) { + if (bpmtempos) + UniPTEffect(0xf, (dat < 32) ? 32 : dat); + else + UniEffect(UNI_MEDSPEED, ((UWORD)dat * 125) / 33); + } + } + break; + default: /* all normal PT effects are handled here */ + UniPTEffect(eff, dat); + break; + } +} + +static UBYTE *MED_Convert1(int count, int col) +{ + int t; + UBYTE inst, note, eff, dat; + MMD1NOTE *n; + + UniReset(); + for (t = 0; t < count; t++) { + n = &d1note(t, col); + + note = n->a & 0x7f; + inst = n->b & 0x3f; + eff = n->c & 0xf; + dat = n->d; + + if (inst) + UniInstrument(inst - 1); + if (note) + UniNote(note + 3 * OCTAVE - 1); + EffectCvt(eff, dat); + UniNewline(); + } + return UniDup(); +} + +static UBYTE *MED_Convert0(int count, int col) +{ + int t; + UBYTE a, b, inst, note, eff, dat; + MMD0NOTE *n; + + UniReset(); + for (t = 0; t < count; t++) { + n = &d0note(t, col); + a = n->a; + b = n->b; + + note = a & 0x3f; + a >>= 6; + a = ((a & 1) << 1) | (a >> 1); + inst = (b >> 4) | (a << 4); + eff = b & 0xf; + dat = n->c; + + if (inst) + UniInstrument(inst - 1); + if (note) + UniNote(note + 3 * OCTAVE - 1); + EffectCvt(eff, dat); + UniNewline(); + } + return UniDup(); +} + +static int LoadMEDPatterns(void) +{ + int t, row, col; + UWORD numtracks, numlines, maxlines = 0, track = 0; + MMD0NOTE *mmdp; + + /* first, scan patterns to see how many channels are used */ + for (t = 0; t < of.numpat; t++) { + _mm_fseek(modreader, ba[t], SEEK_SET); + numtracks = _mm_read_UBYTE(modreader); + numlines = _mm_read_UBYTE(modreader); + + if (numtracks > of.numchn) + of.numchn = numtracks; + if (numlines > maxlines) + maxlines = numlines; + } + + of.numtrk = of.numpat * of.numchn; + if (!AllocTracks()) + return 0; + if (!AllocPatterns()) + return 0; + + if (! + (mmd0pat = + (MMD0NOTE *)MikMod_calloc(of.numchn * (maxlines + 1), + sizeof(MMD0NOTE)))) return 0; + + /* second read: read and convert patterns */ + for (t = 0; t < of.numpat; t++) { + _mm_fseek(modreader, ba[t], SEEK_SET); + numtracks = _mm_read_UBYTE(modreader); + numlines = _mm_read_UBYTE(modreader); + + of.pattrows[t] = ++numlines; + memset(mmdp = mmd0pat, 0, of.numchn * maxlines * sizeof(MMD0NOTE)); + for (row = numlines; row; row--) { + for (col = numtracks; col; col--, mmdp++) { + mmdp->a = _mm_read_UBYTE(modreader); + mmdp->b = _mm_read_UBYTE(modreader); + mmdp->c = _mm_read_UBYTE(modreader); + } + } + + for (col = 0; col < of.numchn; col++) + of.tracks[track++] = MED_Convert0(numlines, col); + } + return 1; +} + +static int LoadMMD1Patterns(void) +{ + int t, row, col; + UWORD numtracks, numlines, maxlines = 0, track = 0; + MMD1NOTE *mmdp; + + /* first, scan patterns to see how many channels are used */ + for (t = 0; t < of.numpat; t++) { + _mm_fseek(modreader, ba[t], SEEK_SET); + numtracks = _mm_read_M_UWORD(modreader); + numlines = _mm_read_M_UWORD(modreader); + if (numtracks > of.numchn) + of.numchn = numtracks; + if (numlines > maxlines) + maxlines = numlines; + } + + of.numtrk = of.numpat * of.numchn; + if (!AllocTracks()) + return 0; + if (!AllocPatterns()) + return 0; + + if (! + (mmd1pat = + (MMD1NOTE *)MikMod_calloc(of.numchn * (maxlines + 1), + sizeof(MMD1NOTE)))) return 0; + + /* second read: really read and convert patterns */ + for (t = 0; t < of.numpat; t++) { + _mm_fseek(modreader, ba[t], SEEK_SET); + numtracks = _mm_read_M_UWORD(modreader); + numlines = _mm_read_M_UWORD(modreader); + + _mm_fseek(modreader, sizeof(ULONG), SEEK_CUR); + of.pattrows[t] = ++numlines; + memset(mmdp = mmd1pat, 0, of.numchn * maxlines * sizeof(MMD1NOTE)); + + for (row = numlines; row; row--) { + for (col = numtracks; col; col--, mmdp++) { + mmdp->a = _mm_read_UBYTE(modreader); + mmdp->b = _mm_read_UBYTE(modreader); + mmdp->c = _mm_read_UBYTE(modreader); + mmdp->d = _mm_read_UBYTE(modreader); + } + } + + for (col = 0; col < of.numchn; col++) + of.tracks[track++] = MED_Convert1(numlines, col); + } + return 1; +} + +int MED_Load(int curious) +{ + int t; + ULONG sa[64]; + MEDINSTHEADER s; + SAMPLE *q; + MEDSAMPLE *mss; + + /* try to read module header */ + mh->id = _mm_read_M_ULONG(modreader); + mh->modlen = _mm_read_M_ULONG(modreader); + mh->MEDSONGP = _mm_read_M_ULONG(modreader); + mh->psecnum = _mm_read_M_UWORD(modreader); + mh->pseq = _mm_read_M_UWORD(modreader); + mh->MEDBlockPP = _mm_read_M_ULONG(modreader); + mh->reserved1 = _mm_read_M_ULONG(modreader); + mh->MEDINSTHEADERPP = _mm_read_M_ULONG(modreader); + mh->reserved2 = _mm_read_M_ULONG(modreader); + mh->MEDEXPP = _mm_read_M_ULONG(modreader); + mh->reserved3 = _mm_read_M_ULONG(modreader); + mh->pstate = _mm_read_M_UWORD(modreader); + mh->pblock = _mm_read_M_UWORD(modreader); + mh->pline = _mm_read_M_UWORD(modreader); + mh->pseqnum = _mm_read_M_UWORD(modreader); + mh->actplayline = _mm_read_M_SWORD(modreader); + mh->counter = _mm_read_UBYTE(modreader); + mh->extra_songs = _mm_read_UBYTE(modreader); + + /* Seek to MEDSONG struct */ + _mm_fseek(modreader, mh->MEDSONGP, SEEK_SET); + + /* Load the MED Song Header */ + mss = ms->sample; /* load the sample data first */ + for (t = 63; t; t--, mss++) { + mss->rep = _mm_read_M_UWORD(modreader); + mss->replen = _mm_read_M_UWORD(modreader); + mss->midich = _mm_read_UBYTE(modreader); + mss->midipreset = _mm_read_UBYTE(modreader); + mss->svol = _mm_read_UBYTE(modreader); + mss->strans = _mm_read_SBYTE(modreader); + } + + ms->numblocks = _mm_read_M_UWORD(modreader); + ms->songlen = _mm_read_M_UWORD(modreader); + _mm_read_UBYTES(ms->playseq, 256, modreader); + ms->deftempo = _mm_read_M_UWORD(modreader); + ms->playtransp = _mm_read_SBYTE(modreader); + ms->flags = _mm_read_UBYTE(modreader); + ms->flags2 = _mm_read_UBYTE(modreader); + ms->tempo2 = _mm_read_UBYTE(modreader); + _mm_read_UBYTES(ms->trkvol, 16, modreader); + ms->mastervol = _mm_read_UBYTE(modreader); + ms->numsamples = _mm_read_UBYTE(modreader); + + /* check for a bad header */ + if (_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* load extension structure */ + if (mh->MEDEXPP) { + _mm_fseek(modreader, mh->MEDEXPP, SEEK_SET); + me->nextmod = _mm_read_M_ULONG(modreader); + me->exp_smp = _mm_read_M_ULONG(modreader); + me->s_ext_entries = _mm_read_M_UWORD(modreader); + me->s_ext_entrsz = _mm_read_M_UWORD(modreader); + me->annotxt = _mm_read_M_ULONG(modreader); + me->annolen = _mm_read_M_ULONG(modreader); + me->iinfo = _mm_read_M_ULONG(modreader); + me->i_ext_entries = _mm_read_M_UWORD(modreader); + me->i_ext_entrsz = _mm_read_M_UWORD(modreader); + me->jumpmask = _mm_read_M_ULONG(modreader); + me->rgbtable = _mm_read_M_ULONG(modreader); + me->channelsplit = _mm_read_M_ULONG(modreader); + me->n_info = _mm_read_M_ULONG(modreader); + me->songname = _mm_read_M_ULONG(modreader); + me->songnamelen = _mm_read_M_ULONG(modreader); + me->dumps = _mm_read_M_ULONG(modreader); + } + + /* seek to and read the samplepointer array */ + _mm_fseek(modreader, mh->MEDINSTHEADERPP, SEEK_SET); + if (!_mm_read_M_ULONGS(sa, ms->numsamples, modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* alloc and read the blockpointer array */ + if (!(ba = (ULONG *)MikMod_calloc(ms->numblocks, sizeof(ULONG)))) + return 0; + _mm_fseek(modreader, mh->MEDBlockPP, SEEK_SET); + if (!_mm_read_M_ULONGS(ba, ms->numblocks, modreader)) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + + /* copy song positions */ + if (!AllocPositions(ms->songlen)) + return 0; + for (t = 0; t < ms->songlen; t++) + of.positions[t] = ms->playseq[t]; + + decimalvolumes = (ms->flags & 0x10) ? 0 : 1; + bpmtempos = (ms->flags2 & 0x20) ? 1 : 0; + + if (bpmtempos) { + int bpmlen = (ms->flags2 & 0x1f) + 1; + of.initspeed = ms->tempo2; + of.inittempo = ms->deftempo * bpmlen / 4; + + if (bpmlen != 4) { + /* Let's do some math : compute GCD of BPM beat length and speed */ + int a, b; + + a = bpmlen; + b = ms->tempo2; + + if (a > b) { + t = b; + b = a; + a = t; + } + while ((a != b) && (a)) { + t = a; + a = b - a; + b = t; + if (a > b) { + t = b; + b = a; + a = t; + } + } + + of.initspeed /= b; + of.inittempo = ms->deftempo * bpmlen / (4 * b); + } + } else { + of.initspeed = ms->tempo2; + of.inittempo = ms->deftempo ? ((UWORD)ms->deftempo * 125) / 33 : 128; + if ((ms->deftempo <= 10) && (ms->deftempo)) + of.inittempo = (of.inittempo * 33) / 6; + of.flags |= UF_HIGHBPM; + } + MED_Version[12] = mh->id; + of.modtype = StrDup(MED_Version); + of.numchn = 0; /* will be counted later */ + of.numpat = ms->numblocks; + of.numpos = ms->songlen; + of.numins = ms->numsamples; + of.numsmp = of.numins; + of.reppos = 0; + if ((mh->MEDEXPP) && (me->songname) && (me->songnamelen)) { + char *name; + + _mm_fseek(modreader, me->songname, SEEK_SET); + name = MikMod_malloc(me->songnamelen); + _mm_read_UBYTES(name, me->songnamelen, modreader); + of.songname = DupStr(name, me->songnamelen, 1); + MikMod_free(name); + } else + of.songname = DupStr(NULL, 0, 0); + if ((mh->MEDEXPP) && (me->annotxt) && (me->annolen)) { + _mm_fseek(modreader, me->annotxt, SEEK_SET); + ReadComment(me->annolen); + } + + if (!AllocSamples()) + return 0; + q = of.samples; + for (t = 0; t < of.numins; t++) { + q->flags = SF_SIGNED; + q->volume = 64; + if (sa[t]) { + _mm_fseek(modreader, sa[t], SEEK_SET); + s.length = _mm_read_M_ULONG(modreader); + s.type = _mm_read_M_SWORD(modreader); + + if (s.type) { +#ifdef MIKMOD_DEBUG + fprintf(stderr, "\rNon-sample instruments not supported in MED loader yet\n"); +#endif + if (!curious) { + _mm_errno = MMERR_MED_SYNTHSAMPLES; + return 0; + } + s.length = 0; + } + + if (_mm_eof(modreader)) { + _mm_errno = MMERR_LOADING_SAMPLEINFO; + return 0; + } + + q->length = s.length; + q->seekpos = _mm_ftell(modreader); + q->loopstart = ms->sample[t].rep << 1; + q->loopend = q->loopstart + (ms->sample[t].replen << 1); + + if (ms->sample[t].replen > 1) + q->flags |= SF_LOOP; + + /* don't load sample if length>='MMD0'... + such kluges make libmikmod's code unique !!! */ + if (q->length >= MMD0_string) + q->length = 0; + } else + q->length = 0; + + if ((mh->MEDEXPP) && (me->exp_smp) && + (t < me->s_ext_entries) && (me->s_ext_entrsz >= 4)) { + MEDINSTEXT ie; + + _mm_fseek(modreader, me->exp_smp + t * me->s_ext_entrsz, + SEEK_SET); + ie.hold = _mm_read_UBYTE(modreader); + ie.decay = _mm_read_UBYTE(modreader); + ie.suppress_midi_off = _mm_read_UBYTE(modreader); + ie.finetune = _mm_read_SBYTE(modreader); + + q->speed = finetune[ie.finetune & 0xf]; + } else + q->speed = 8363; + + if ((mh->MEDEXPP) && (me->iinfo) && + (t < me->i_ext_entries) && (me->i_ext_entrsz >= 40)) { + MEDINSTINFO ii; + + _mm_fseek(modreader, me->iinfo + t * me->i_ext_entrsz, SEEK_SET); + _mm_read_UBYTES(ii.name, 40, modreader); + q->samplename = DupStr((char*)ii.name, 40, 1); + } else + q->samplename = NULL; + + q++; + } + + if (mh->id == MMD0_string) { + if (!LoadMEDPatterns()) { + _mm_errno = MMERR_LOADING_PATTERN; + return 0; + } + } else if (mh->id == MMD1_string) { + if (!LoadMMD1Patterns()) { + _mm_errno = MMERR_LOADING_PATTERN; + return 0; + } + } else { + _mm_errno = MMERR_NOT_A_MODULE; + return 0; + } + return 1; +} + +CHAR *MED_LoadTitle(void) +{ + ULONG posit, namelen; + CHAR *name, *retvalue = NULL; + + _mm_fseek(modreader, 0x20, SEEK_SET); + posit = _mm_read_M_ULONG(modreader); + + if (posit) { + _mm_fseek(modreader, posit + 0x2C, SEEK_SET); + posit = _mm_read_M_ULONG(modreader); + namelen = _mm_read_M_ULONG(modreader); + + _mm_fseek(modreader, posit, SEEK_SET); + name = MikMod_malloc(namelen); + _mm_read_UBYTES(name, namelen, modreader); + retvalue = DupStr(name, namelen, 1); + MikMod_free(name); + } + + return retvalue; +} + +/*========== Loader information */ + +MIKMODAPI MLOADER load_med = { + NULL, + "MED", + "MED (OctaMED)", + MED_Init, + MED_Test, + MED_Load, + MED_Cleanup, + MED_LoadTitle +}; + +/* ex:set ts=4: */ -- cgit v1.2.3