/* 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: */