/* Extended Module Player * Copyright (C) 1996-1999 Claudio Matsuoka and Hipolito Carraro Jr * * This file is part of the Extended Module Player and is distributed * under the terms of the GNU General Public License. See doc/COPYING * for more information. */ /* This loader recognizes the following variants of the Protracker * module format: * * - Protracker M.K. and M!K! * - Protracker songs * - Noisetracker N.T. and M&K! (not tested) * - Fast Tracker 6CHN and 8CHN * - Fasttracker II/Take Tracker ?CHN and ??CH * - M.K. with ADPCM samples (MDZ) * - Mod's Grave M.K. w/ 8 channels (WOW) * - Atari Octalyser CD81 (not tested) */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "load.h" #include "mod.h" struct { char *magic; char *id; int flag; char *tracker; int ch; } mod_magic[] = { { "M.K.", "4 channel MOD", 0, "Protracker", 4 }, { "M!K!", "4 channel MOD", 1, "Protracker", 4 }, { "M&K!", "4 channel MOD", 1, "Noisetracker", 4 }, { "N.T.", "4 channel MOD", 1, "Noisetracker", 4 }, { "6CHN", "6 channel MOD", 0, "Fast Tracker", 6 }, { "8CHN", "8 channel MOD", 0, "Fast Tracker", 8 }, { "CD81", "8 channel MOD", 1, "Octalyser", 8 }, /* Atari STe/Falcon */ { "", 0 } }; static int module_load (FILE *, int); int mod_load (FILE *f) { return module_load (f, 0); } int ptdt_load (FILE *f) { return module_load (f, 1); } static int is_st_ins (char *s) { if (s[0] != 's' && s[0] != 'S') return 0; if (s[1] != 't' && s[1] != 'T') return 0; if (s[2] != '-' || s[5] != ':') return 0; if (!isdigit(s[3]) || !isdigit(s[4])) return 0; return 1; } static int module_load (FILE *f, int ptdt) { int i, j; int smp_size, hdr_size, pat_size, wow, ptsong = 0; struct xxm_event *event; struct mod_header mh; uint8 mod_event[4]; char *x, pathname[256] = "", *id = "", *tracker = ""; int lps_mult = xmp_ctl->fetch & XMP_CTL_FIXLOOP ? 1 : 2; int detected = 0; if (!ptdt) LOAD_INIT (); xxh->ins = 31; xxh->smp = xxh->ins; xxh->chn = ptdt ? 4 : 0; smp_size = 0; pat_size = 0; hdr_size = 0; if (xxh->chn == 4) xxh->flg |= XXM_FLG_MODRNG; hdr_size = sizeof (struct mod_header); fread (&mh, 1, sizeof (struct mod_header), f); for (i = 0; mod_magic[i].ch; i++) { if (!(strncmp ((char *) mh.magic, mod_magic[i].magic, 4))) { xxh->chn = mod_magic[i].ch; id = mod_magic[i].id; tracker = mod_magic[i].tracker; detected = mod_magic[i].flag; break; } } if (!xxh->chn) { if (!strncmp ((char *) mh.magic + 2, "CH", 2) && isdigit (*mh.magic) && isdigit (mh.magic[1])) { if ((xxh->chn = (*mh.magic - '0') * 10 + mh.magic[1] - '0') > 32) return -1; } else if (!strncmp ((char *) mh.magic + 1, "CHN", 3) && isdigit (*mh.magic)) { if (!(xxh->chn = (*mh.magic - '0'))) return -1; } else return -1; id = "Multichannel MOD"; tracker = "TakeTracker/FastTracker II"; detected = 1; xxh->flg &= ~XXM_FLG_MODRNG; } if (!ptdt) strncpy (xmp_ctl->name, (char *) mh.name, 20); xxh->len = mh.len; /* xxh->rst = mh.restart; */ if (xxh->rst >= xxh->len) xxh->rst = 0; memcpy (xxo, mh.order, 128); for (i = 0; i < 128; i++) { /* This fixes dragnet.mod (garbage in the order list) */ if (xxo[i] > 0x7f) break; if (xxo[i] > xxh->pat) xxh->pat = xxo[i]; } xxh->pat++; pat_size = 256 * xxh->chn * xxh->pat; INSTRUMENT_INIT (); for (i = 0; i < xxh->ins; i++) { B_ENDIAN16 (mh.ins[i].size); B_ENDIAN16 (mh.ins[i].loop_start); B_ENDIAN16 (mh.ins[i].loop_size); smp_size += 2 * mh.ins[i].size; xxi[i] = calloc (sizeof (struct xxm_instrument), 1); xxs[i].len = 2 * mh.ins[i].size; xxs[i].lps = lps_mult * mh.ins[i].loop_start; xxs[i].lpe = xxs[i].lps + 2 * mh.ins[i].loop_size; xxs[i].flg = (mh.ins[i].loop_size > 1 && xxs[i].lpe > 8) ? WAVE_LOOPING : 0; xxi[i][0].fin = (int8)(mh.ins[i].finetune << 4); xxi[i][0].vol = mh.ins[i].volume; xxi[i][0].pan = 0x80; xxi[i][0].sid = i; xxih[i].nsm = !!(xxs[i].len); strncpy (xxih[i].name, mh.ins[i].name, 22); str_adj (xxih[i].name); } /* Experimental tracker-detection routine */ if (detected) goto skip_test; /* Test for Mod's Grave WOW modules * * Stefan Danes said: * This weird format is identical to '8CHN' but still uses the 'M.K.' ID. * You can only test for WOW by calculating the size of the module for 8 * channels and comparing this to the actual module length. If it's equal, * the module is an 8 channel WOW. */ if ((wow = (!strncmp ((char *) mh.magic, "M.K.", 4) && (0x43c + xxh->pat * 32 * 0x40 + smp_size == xmp_ctl->size)))) { xxh->chn = 8; id = "WOW M.K. 8 channel MOD"; tracker = "Mod's Grave"; goto skip_test; } /* Test for UNIC tracker modules * * From Gryzor's Pro-Wizard PW_FORMATS-Engl.guide: * ``The UNIC format is very similar to Protracker... At least in the * heading... same length : 1084 bytes. Even the "M.K." is present, * sometimes !! Maybe to disturb the rippers.. hehe but Pro-Wizard * doesn't test this only!'' */ else if (sizeof (struct mod_header) + xxh->pat * 0x300 + smp_size == xmp_ctl->size) { return -1; } /* Test for Protracker song files */ else if ((ptsong = (!strncmp ((char *) mh.magic, "M.K.", 4) && (0x43c + xxh->pat * 0x400 == xmp_ctl->size)))) { id = "4 channel song"; tracker = "Protracker"; goto skip_test; } /* Test Protracker-like files */ if (xxh->chn == 4 && mh.restart == xxh->pat) { tracker = "Soundtracker"; } else if (xxh->chn == 4 && mh.restart == 0x78) { tracker = "Noisetracker" /*" (0x78)"*/; } else if (mh.restart < 0x7f) { if (xxh->chn == 4) tracker = "Noisetracker"; else tracker = "(unknown)"; xxh->rst = mh.restart; } if (xxh->chn != 4 && mh.restart == 0x7f) tracker = "(unknown)"; if (xxh->chn == 4 && mh.restart == 0x7f) { for (i = 0; i < 31; i++) { if (mh.ins[i].loop_size == 0) break; } if (i < 31) tracker = "Protracker clone"; } if (mh.restart != 0x78 && mh.restart < 0x7f) { for (i = 0; i < 31; i++) { if (mh.ins[i].loop_size == 0) break; } if (i == 31) { /* All loops are size 2 or greater */ for (i = 0; i < 31; i++) { if (mh.ins[i].size == 1 && mh.ins[i].volume == 0) { tracker = "Probably converted"; goto skip_test; } } for (i = 0; i < 31; i++) { if (is_st_ins (mh.ins[i].name)) break; } if (i == 31) { /* No st- instruments */ for (i = 0; i < 31; i++) { if (mh.ins[i].size == 0 && mh.ins[i].loop_size == 1) { switch (xxh->chn) { case 4: tracker = "old Noisetracker"; break; case 6: case 8: tracker = "Octalyser"; break; default: tracker = "Unknown. Email claudio!"; } goto skip_test; } } tracker = "Maybe Protracker"; } } else { /* Has loops with 0 size */ for (i = 15; i < 31; i++) { if (strlen (mh.ins[i].name) || mh.ins[i].size > 0) break; } if (i == 31 && is_st_ins(mh.ins[14].name)) { tracker = "converted 15 instrument"; goto skip_test; } /* Assume that Fast Tracker modules won't have ST- instruments */ for (i = 0; i < 31; i++) { if (is_st_ins (mh.ins[i].name)) break; } if (i < 31) { tracker = "Unknown (converted?)"; goto skip_test; } if (xxh->chn == 4 || xxh->chn == 6 || xxh->chn == 8) { tracker = "Fast Tracker"; xxh->flg &= ~XXM_FLG_MODRNG; goto skip_test; } tracker = "No idea"; /* ??!? */ } } skip_test: xxh->trk = xxh->chn * xxh->pat; if (!ptdt) { sprintf (xmp_ctl->type, "%4.4s (%s)", mh.magic, id); strcpy (tracker_name, tracker); MODULE_INFO (); } if (V (1)) { report (" Instrument name Len LBeg LEnd L Vol Fin\n"); for (i = 0; i < xxh->ins; i++) { if (V(3) || (strlen ((char *) xxih[i].name) || (xxs[i].len > 2))) report ("[%2X] %-22.22s %04x %04x %04x %c V%02x %+d\n", i, xxih[i].name, xxs[i].len, xxs[i].lps, xxs[i].lpe, (mh.ins[i].loop_size > 1 && xxs[i].lpe > 8) ? 'L' : ' ', xxi[i][0].vol, (char) xxi[i][0].fin >> 4); } } PATTERN_INIT (); /* Load and convert patterns */ if (V (0)) report ("Stored patterns: %d ", xxh->pat); for (i = 0; i < xxh->pat; i++) { PATTERN_ALLOC (i); xxp[i]->rows = 64; TRACK_ALLOC (i); for (j = 0; j < (64 * xxh->chn); j++) { event = &EVENT (i, j % xxh->chn, j / xxh->chn); fread (mod_event, 1, 4, f); cvt_pt_event (event, mod_event); /* Special translation for e8 (set panning) effect. * This is not an official Protracker effect but DMP uses * it for panning, and a couple of modules follow this * "standard". */ if ((event->fxt == 0x0e) && ((event->fxp & 0xf0) == 0x80)) { event->fxt = FX_SETPAN; event->fxp <<= 4; } } if (V (0)) report ("."); } /* Load samples */ if ((x = strrchr (xmp_ctl->filename, '/'))) { *x = 0; strcpy (pathname, xmp_ctl->filename); strcat (pathname, "/"); *x = '/'; } if (V (0)) report ("\nStored samples : %d ", xxh->smp); for (i = 0; i < xxh->smp; i++) { if (!xxs[i].len) continue; if (ptsong) { FILE *s; char sn[256]; sprintf (sn, "%s%s", pathname, xxih[i].name); if ((s = fopen (sn, "rb"))) { xmp_drv_loadpatch (s, xxi[i][0].sid, xmp_ctl->c4rate, 0, &xxs[xxi[i][0].sid], NULL); if (V (0)) report ("."); } } else { xmp_drv_loadpatch (f, xxi[i][0].sid, xmp_ctl->c4rate, 0, &xxs[xxi[i][0].sid], NULL); if (V (0)) report ("."); } } if (V (0)) report ("\n"); if (xxh->chn > 4) xmp_ctl->fetch |= XMP_MODE_FT2; return 0; }