1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-09-19 16:45:06 +00:00
QB64-PE/internal/c/parts/audio/extras/libxmp-lite/mod_load.c
2023-06-17 14:07:50 +05:30

1010 lines
26 KiB
C

/* Extended Module Player
* Copyright (C) 1996-2023 Claudio Matsuoka and Hipolito Carraro Jr
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/* 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
* - Mod's Grave M.K. w/ 8 channels (WOW)
* - Atari Octalyser CD61 and CD81
* - Digital Tracker FA04, FA06 and FA08
* - TakeTracker TDZ1, TDZ2, TDZ3, and TDZ4
* - (unknown) NSMS, LARD
*
* The 'lite' version only recognizes Protracker M.K. and
* Fasttracker ?CHN and ??CH formats.
*/
#include <ctype.h>
#include "loader.h"
#include "mod.h"
#ifndef LIBXMP_CORE_PLAYER
struct mod_magic {
const char *magic;
int flag;
int id;
int ch;
};
#define TRACKER_PROTRACKER 0
#define TRACKER_NOISETRACKER 1
#define TRACKER_SOUNDTRACKER 2
#define TRACKER_FASTTRACKER 3
#define TRACKER_FASTTRACKER2 4
#define TRACKER_OCTALYSER 5
#define TRACKER_TAKETRACKER 6
#define TRACKER_DIGITALTRACKER 7
#define TRACKER_FLEXTRAX 8
#define TRACKER_MODSGRAVE 9
#define TRACKER_SCREAMTRACKER3 10
#define TRACKER_OPENMPT 11
#define TRACKER_UNKNOWN_CONV 95
#define TRACKER_CONVERTEDST 96
#define TRACKER_CONVERTED 97
#define TRACKER_CLONE 98
#define TRACKER_UNKNOWN 99
#define TRACKER_PROBABLY_NOISETRACKER 20
const struct mod_magic mod_magic[] = {
{"M.K.", 0, TRACKER_PROTRACKER, 4},
{"M!K!", 1, TRACKER_PROTRACKER, 4},
{"M&K!", 1, TRACKER_NOISETRACKER, 4},
{"N.T.", 1, TRACKER_NOISETRACKER, 4},
{"6CHN", 0, TRACKER_FASTTRACKER, 6},
{"8CHN", 0, TRACKER_FASTTRACKER, 8},
{"CD61", 1, TRACKER_OCTALYSER, 6}, /* Atari STe/Falcon */
{"CD81", 1, TRACKER_OCTALYSER, 8}, /* Atari STe/Falcon */
{"TDZ1", 1, TRACKER_TAKETRACKER, 1}, /* TakeTracker 1ch */
{"TDZ2", 1, TRACKER_TAKETRACKER, 2}, /* TakeTracker 2ch */
{"TDZ3", 1, TRACKER_TAKETRACKER, 3}, /* TakeTracker 3ch */
{"TDZ4", 1, TRACKER_TAKETRACKER, 4}, /* see XModule SaveTracker.c */
{"FA04", 1, TRACKER_DIGITALTRACKER, 4}, /* Atari Falcon */
{"FA06", 1, TRACKER_DIGITALTRACKER, 6}, /* Atari Falcon */
{"FA08", 1, TRACKER_DIGITALTRACKER, 8}, /* Atari Falcon */
{"LARD", 1, TRACKER_UNKNOWN, 4}, /* in judgement_day_gvine.mod */
{"NSMS", 1, TRACKER_UNKNOWN, 4}, /* in Kingdom.mod */
};
/* Returns non-zero if the given tracker ONLY supports VBlank timing. This
* should be used only when the tracker is known for sure, e.g. magic match. */
static int tracker_is_vblank(int id)
{
switch (id) {
case TRACKER_NOISETRACKER:
case TRACKER_SOUNDTRACKER:
return 1;
default:
return 0;
}
}
#endif /* LIBXMP_CORE_PLAYER */
static int mod_test(HIO_HANDLE *, char *, const int);
static int mod_load(struct module_data *, HIO_HANDLE *, const int);
const struct format_loader libxmp_loader_mod = {
#ifdef LIBXMP_CORE_PLAYER
"Protracker",
#else
"Amiga Protracker/Compatible",
#endif
mod_test,
mod_load
};
#ifndef LIBXMP_CORE_PLAYER
static int validate_pattern(uint8 *buf)
{
int i, j;
for (i = 0; i < 64; i++) {
for (j = 0; j < 4; j++) {
uint8 *d = buf + (i * 4 + j) * 4;
if ((d[0] >> 4) > 1) {
D_(D_CRIT "invalid pattern data: row %d ch %d: %02x", i, j, d[0]);
return -1;
}
}
}
return 0;
}
#endif
static int mod_test(HIO_HANDLE * f, char *t, const int start)
{
int i;
char buf[4];
#ifndef LIBXMP_CORE_PLAYER
uint8 pat_buf[1024];
int smp_size, num_pat;
long size;
int count;
int detected;
#endif
hio_seek(f, start + 1080, SEEK_SET);
if (hio_read(buf, 1, 4, f) < 4) {
return -1;
}
if (!strncmp(buf + 2, "CH", 2) &&
isdigit((unsigned char)buf[0]) && isdigit((unsigned char)buf[1])) {
i = (buf[0] - '0') * 10 + buf[1] - '0';
if (i > 0 && i <= 32) {
goto found;
}
}
if (!strncmp(buf + 1, "CHN", 3) && isdigit((unsigned char)*buf)) {
if (*buf - '0') {
goto found;
}
}
#ifdef LIBXMP_CORE_PLAYER
if (memcmp(buf, "M.K.", 4))
return -1;
#else
for (i = 0; i < ARRAY_SIZE(mod_magic); i++) {
if (!memcmp(buf, mod_magic[i].magic, 4))
break;
}
if (i >= ARRAY_SIZE(mod_magic)) {
return -1;
}
detected = mod_magic[i].flag;
/*
* Sanity check to prevent loading NoiseRunner and other module
* formats with valid magic at offset 1080 (e.g. His Master's Noise)
*/
hio_seek(f, start + 20, SEEK_SET);
for (i = 0; i < 31; i++) {
uint8 x;
hio_seek(f, 22, SEEK_CUR); /* Instrument name */
/* OpenMPT can create mods with large samples */
hio_read16b(f); /* sample size */
/* Chris Spiegel tells me that sandman.mod has 0x20 in finetune */
x = hio_read8(f);
if (x & 0xf0 && x != 0x20) /* test finetune */
return -1;
if (hio_read8(f) > 0x40) /* test volume */
return -1;
hio_read16b(f); /* loop start */
hio_read16b(f); /* loop size */
}
/* The following checks are only relevant for filtering out atypical
* M.K. variants. If the magic is from a recognizable source, skip them. */
if (detected)
goto found;
/* 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!''
*/
/* get file size */
size = hio_size(f);
smp_size = 0;
hio_seek(f, start + 20, SEEK_SET);
/* get samples size */
for (i = 0; i < 31; i++) {
hio_seek(f, 22, SEEK_CUR);
smp_size += 2 * hio_read16b(f); /* Length in 16-bit words */
hio_seek(f, 6, SEEK_CUR);
}
/* get number of patterns */
num_pat = 0;
hio_seek(f, start + 952, SEEK_SET);
for (i = 0; i < 128; i++) {
uint8 x = hio_read8(f);
if (x > 0x7f)
break;
if (x > num_pat)
num_pat = x;
}
num_pat++;
/* see if module size matches UNIC */
if (start + 1084 + num_pat * 0x300 + smp_size == size) {
D_(D_CRIT "module size matches UNIC");
return -1;
}
/* validate pattern data in an attempt to catch UNICs with MOD size */
for (count = i = 0; i < num_pat; i++) {
hio_seek(f, start + 1084 + 1024 * i, SEEK_SET);
if (!hio_read(pat_buf, 1024, 1, f)) {
D_(D_WARN "pattern %d: failed to read pattern data", i);
return -1;
}
if (validate_pattern(pat_buf) < 0) {
D_(D_WARN "pattern %d: error in pattern data", i);
/* Allow a few errors, "lexstacy" has 0x52 */
count++;
}
}
if (count > 2) {
return -1;
}
#endif /* LIBXMP_CORE_PLAYER */
found:
hio_seek(f, start + 0, SEEK_SET);
libxmp_read_title(f, t, 20);
return 0;
}
#ifndef LIBXMP_CORE_PLAYER
static int is_st_ins(const 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((unsigned char)s[3]) || !isdigit((unsigned char)s[4]))
return 0;
return 1;
}
static int get_tracker_id(struct module_data *m, struct mod_header *mh, int id)
{
struct xmp_module *mod = &m->mod;
int has_loop_0 = 0;
int has_vol_in_empty_ins = 0;
int i;
/* Check if has instruments with loop size 0 */
for (i = 0; i < 31; i++) {
if (mh->ins[i].loop_size == 0) {
has_loop_0 = 1;
break;
}
}
/* Check if has instruments with size 0 and volume > 0 */
for (i = 0; i < 31; i++) {
if (mh->ins[i].size == 0 && mh->ins[i].volume > 0) {
has_vol_in_empty_ins = 1;
break;
}
}
/*
* Test Protracker-like files
*/
if (mh->restart == mod->pat) {
if (mod->chn == 4) {
id = TRACKER_SOUNDTRACKER;
} else {
id = TRACKER_UNKNOWN;
}
} else if (mh->restart == 0x78) {
if (mod->chn == 4) {
/* Can't trust this for Noisetracker, MOD.Data City Remix
* has Protracker effects and Noisetracker restart byte
*/
id = TRACKER_PROBABLY_NOISETRACKER;
} else {
id = TRACKER_UNKNOWN;
}
return id;
} else if (mh->restart < 0x7f) {
if (mod->chn == 4 && !has_vol_in_empty_ins) {
id = TRACKER_NOISETRACKER;
} else {
id = TRACKER_UNKNOWN; /* ? */
}
mod->rst = mh->restart;
} else if (mh->restart == 0x7f) {
if (mod->chn == 4) {
if (has_loop_0) {
id = TRACKER_CLONE;
}
} else {
id = TRACKER_SCREAMTRACKER3;
}
return id;
} else if (mh->restart > 0x7f) {
id = TRACKER_UNKNOWN; /* ? */
return id;
}
if (!has_loop_0) { /* All loops are size 2 or greater */
for (i = 0; i < 31; i++) {
if (mh->ins[i].size == 1 && mh->ins[i].volume == 0) {
return TRACKER_CONVERTED;
}
}
for (i = 0; i < 31; i++) {
if (is_st_ins((char *)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) {
continue;
}
switch (mod->chn) {
case 4:
if (has_vol_in_empty_ins) {
id = TRACKER_OPENMPT;
} else {
id = TRACKER_NOISETRACKER;
/* or Octalyser */
}
break;
case 6:
case 8:
id = TRACKER_OCTALYSER;
break;
default:
id = TRACKER_UNKNOWN;
}
return id;
}
if (mod->chn == 4) {
id = TRACKER_PROTRACKER;
} else if (mod->chn == 6 || mod->chn == 8) {
/* FastTracker 1.01? */
id = TRACKER_FASTTRACKER;
} else {
id = TRACKER_UNKNOWN;
}
}
} else { /* Has loops with size 0 */
for (i = 15; i < 31; i++) {
/* Is the name or size set? */
if (mh->ins[i].name[0] || mh->ins[i].size > 0)
break;
}
if (i == 31 && is_st_ins((char *)mh->ins[14].name)) {
return TRACKER_CONVERTEDST;
}
/* Assume that Fast Tracker modules won't have ST- instruments */
for (i = 0; i < 31; i++) {
if (is_st_ins((char *)mh->ins[i].name))
break;
}
if (i < 31) {
return TRACKER_UNKNOWN_CONV;
}
if (mod->chn == 4 || mod->chn == 6 || mod->chn == 8) {
return TRACKER_FASTTRACKER;
}
id = TRACKER_UNKNOWN; /* ?! */
}
return id;
}
#endif /* LIBXMP_CORE_PLAYER */
static int mod_load(struct module_data *m, HIO_HANDLE *f, const int start)
{
struct xmp_module *mod = &m->mod;
int i, j, k;
struct xmp_event *event;
struct mod_header mh;
char magic[8];
uint8 *patbuf;
#ifndef LIBXMP_CORE_PLAYER
uint8 pat_high_fxx[256];
const char *tracker = "";
int detected = 0;
int tracker_id = TRACKER_PROTRACKER;
int out_of_range = 0;
int maybe_wow = 1;
int smp_size, ptsong = 0;
int needs_timing_detection = 0;
int samerow_fxx = 0; /* speed + BPM set on same row */
int high_fxx = 0; /* high Fxx is used anywhere */
#endif
int ptkloop = 0; /* Protracker loop */
LOAD_INIT();
mod->ins = 31;
mod->smp = mod->ins;
mod->chn = 0;
#ifndef LIBXMP_CORE_PLAYER
smp_size = 0;
#else
m->quirk |= QUIRK_PROTRACK;
#endif
m->period_type = PERIOD_MODRNG;
hio_read(mh.name, 20, 1, f);
for (i = 0; i < 31; i++) {
hio_read(mh.ins[i].name, 22, 1, f); /* Instrument name */
mh.ins[i].size = hio_read16b(f); /* Length in 16-bit words */
mh.ins[i].finetune = hio_read8(f); /* Finetune (signed nibble) */
mh.ins[i].volume = hio_read8(f); /* Linear playback volume */
mh.ins[i].loop_start = hio_read16b(f); /* Loop start in 16-bit words */
mh.ins[i].loop_size = hio_read16b(f); /* Loop size in 16-bit words */
#ifndef LIBXMP_CORE_PLAYER
/* Mod's Grave WOW files are converted from 669s and have default
* finetune and volume.
*/
if (mh.ins[i].size && (mh.ins[i].finetune != 0 || mh.ins[i].volume != 64))
maybe_wow = 0;
smp_size += 2 * mh.ins[i].size;
#endif
}
mh.len = hio_read8(f);
mh.restart = hio_read8(f);
hio_read(mh.order, 128, 1, f);
memset(magic, 0, sizeof(magic));
hio_read(magic, 1, 4, f);
if (hio_error(f)) {
return -1;
}
#ifndef LIBXMP_CORE_PLAYER
/* Mod's Grave WOW files always have a 0 restart byte; 6692WOW implements
* 669 repeating by inserting a pattern jump and ignores this byte.
*/
if (mh.restart != 0)
maybe_wow = 0;
for (i = 0; i < ARRAY_SIZE(mod_magic); i++) {
if (!(strncmp (magic, mod_magic[i].magic, 4))) {
mod->chn = mod_magic[i].ch;
tracker_id = mod_magic[i].id;
detected = mod_magic[i].flag;
break;
}
}
/* Enable timing detection for M.K. and M!K! modules. */
if (tracker_id == TRACKER_PROTRACKER)
needs_timing_detection = 1;
/* Digital Tracker MODs have an extra four bytes after the magic.
* These are always 00h 40h 00h 00h and can probably be ignored. */
if (tracker_id == TRACKER_DIGITALTRACKER) {
hio_read32b(f);
}
#endif
if (mod->chn == 0) {
#ifdef LIBXMP_CORE_PLAYER
if (!memcmp(magic, "M.K.", 4)) {
mod->chn = 4;
} else
#endif
if (!strncmp(magic + 2, "CH", 2) &&
isdigit((unsigned char)magic[0]) && isdigit((unsigned char)magic[1])) {
mod->chn = (*magic - '0') * 10 + magic[1] - '0';
} else if (!strncmp(magic + 1, "CHN", 3) && isdigit((unsigned char)*magic)) {
mod->chn = *magic - '0';
} else {
return -1;
}
#ifndef LIBXMP_CORE_PLAYER
tracker_id = mod->chn & 1 ? TRACKER_TAKETRACKER : TRACKER_FASTTRACKER2;
detected = 1;
#endif
}
strncpy(mod->name, (char *) mh.name, 20);
mod->len = mh.len;
/* mod->rst = mh.restart; */
if (mod->rst >= mod->len)
mod->rst = 0;
memcpy(mod->xxo, mh.order, 128);
for (i = 0; i < 128; i++) {
/* This fixes dragnet.mod (garbage in the order list) */
if (mod->xxo[i] > 0x7f)
break;
if (mod->xxo[i] > mod->pat)
mod->pat = mod->xxo[i];
}
mod->pat++;
if (libxmp_init_instrument(m) < 0)
return -1;
for (i = 0; i < mod->ins; i++) {
struct xmp_instrument *xxi;
struct xmp_subinstrument *sub;
struct xmp_sample *xxs;
if (libxmp_alloc_subinstrument(mod, i, 1) < 0)
return -1;
#ifndef LIBXMP_CORE_PLAYER
if (mh.ins[i].size >= 0x8000) {
tracker_id = TRACKER_OPENMPT;
needs_timing_detection = 0;
detected = 1;
}
#endif
xxi = &mod->xxi[i];
sub = &xxi->sub[0];
xxs = &mod->xxs[i];
xxs->len = 2 * mh.ins[i].size;
xxs->lps = 2 * mh.ins[i].loop_start;
xxs->lpe = xxs->lps + 2 * mh.ins[i].loop_size;
if (xxs->lpe > xxs->len) {
xxs->lpe = xxs->len;
}
xxs->flg = (mh.ins[i].loop_size > 1 && xxs->lpe >= 4) ?
XMP_SAMPLE_LOOP : 0;
sub->fin = (int8)(mh.ins[i].finetune << 4);
sub->vol = mh.ins[i].volume;
sub->pan = 0x80;
sub->sid = i;
libxmp_instrument_name(mod, i, mh.ins[i].name, 22);
if (xxs->len > 0) {
xxi->nsm = 1;
}
}
#ifndef LIBXMP_CORE_PLAYER
/* Experimental tracker-detection routine */
if (detected)
goto skip_test;
/* Test for Flextrax modules
*
* FlexTrax is a soundtracker for Atari Falcon030 compatible computers.
* FlexTrax supports the standard MOD file format (up to eight channels)
* for compatibility reasons but also features a new enhanced module
* format FLX. The FLX format is an extended version of the standard
* MOD file format with support for real-time sound effects like reverb
* and delay.
*/
if (0x43c + mod->pat * 4 * mod->chn * 0x40 + smp_size < m->size) {
char idbuffer[4];
int pos = hio_tell(f);
int num_read;
if (pos < 0) {
return -1;
}
hio_seek(f, start + 0x43c + mod->pat * 4 * mod->chn * 0x40 + smp_size, SEEK_SET);
num_read = hio_read(idbuffer, 1, 4, f);
hio_seek(f, start + pos, SEEK_SET);
if (num_read == 4 && !memcmp(idbuffer, "FLEX", 4)) {
tracker_id = TRACKER_FLEXTRAX;
needs_timing_detection = 0;
goto skip_test;
}
}
/* Test for Mod's Grave WOW modules
*
* Stefan Danes <sdanes@marvels.hacktic.nl> 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.
*
* Addendum: very rarely, WOWs will have an odd length due to an extra byte,
* so round the filesize down in this check. False positive WOWs can be ruled
* out by checking the restart byte and sample volume (see above).
*
* Worst case if there are still issues with this, OpenMPT validates later
* patterns in potential WOW files (where sample data would be located in a
* regular M.K. MOD) to rule out false positives.
*/
if (!strncmp(magic, "M.K.", 4) && maybe_wow &&
(0x43c + mod->pat * 32 * 0x40 + smp_size) == (m->size & ~1)) {
mod->chn = 8;
tracker_id = TRACKER_MODSGRAVE;
needs_timing_detection = 0;
} else {
/* Test for Protracker song files */
ptsong = !strncmp((char *)magic, "M.K.", 4) &&
(0x43c + mod->pat * 0x400 == m->size);
if (ptsong) {
tracker_id = TRACKER_PROTRACKER;
goto skip_test;
} else {
/* something else */
tracker_id = get_tracker_id(m, &mh, tracker_id);
}
}
skip_test:
#endif
if (mod->chn >= XMP_MAX_CHANNELS) {
return -1;
}
mod->trk = mod->chn * mod->pat;
for (i = 0; i < mod->ins; i++) {
D_(D_INFO "[%2X] %-22.22s %04x %04x %04x %c V%02x %+d %c",
i, mod->xxi[i].name,
mod->xxs[i].len, mod->xxs[i].lps, mod->xxs[i].lpe,
(mh.ins[i].loop_size > 1 && mod->xxs[i].lpe > 8) ?
'L' : ' ', mod->xxi[i].sub[0].vol,
mod->xxi[i].sub[0].fin >> 4,
ptkloop && mod->xxs[i].lps == 0 && mh.ins[i].loop_size > 1 &&
mod->xxs[i].len > mod->xxs[i].lpe ? '!' : ' ');
}
if (libxmp_init_pattern(mod) < 0)
return -1;
/* Load and convert patterns */
D_(D_INFO "Stored patterns: %d", mod->pat);
if ((patbuf = (uint8 *) malloc(64 * 4 * mod->chn)) == NULL) {
return -1;
}
#ifndef LIBXMP_CORE_PLAYER
memset(pat_high_fxx, 0, sizeof(pat_high_fxx));
#endif
for (i = 0; i < mod->pat; i++) {
uint8 *mod_event;
if (libxmp_alloc_pattern_tracks(mod, i, 64) < 0) {
free(patbuf);
return -1;
}
if (hio_read(patbuf, 64 * 4 * mod->chn, 1, f) < 1) {
free(patbuf);
return -1;
}
#ifndef LIBXMP_CORE_PLAYER
mod_event = patbuf;
for (j = 0; j < 64; j++) {
int speed_row = 0;
int bpm_row = 0;
for (k = 0; k < mod->chn; k++) {
int period;
period = ((int)(LSN(mod_event[0])) << 8) | mod_event[1];
if (period != 0 && (period < 108 || period > 907)) {
out_of_range = 1;
}
/* Filter noisetracker events */
if (tracker_id == TRACKER_PROBABLY_NOISETRACKER) {
unsigned char fxt = LSN(mod_event[2]);
unsigned char fxp = LSN(mod_event[3]);
if ((fxt > 0x06 && fxt < 0x0a) || (fxt == 0x0e && fxp > 1)) {
tracker_id = TRACKER_UNKNOWN;
}
}
/* Needs CIA/VBlank detection? */
if (LSN(mod_event[2]) == 0x0f) {
if (mod_event[3] >= 0x20) {
pat_high_fxx[i] = mod_event[3];
m->compare_vblank = 1;
high_fxx = 1;
bpm_row = 1;
} else {
speed_row = 1;
}
}
mod_event += 4;
}
if (bpm_row && speed_row) {
samerow_fxx = 1;
}
}
if (out_of_range) {
if (tracker_id == TRACKER_UNKNOWN && mh.restart == 0x7f) {
tracker_id = TRACKER_SCREAMTRACKER3;
}
/* Check out-of-range notes in Amiga trackers */
if (tracker_id == TRACKER_PROTRACKER ||
tracker_id == TRACKER_NOISETRACKER ||
tracker_id == TRACKER_PROBABLY_NOISETRACKER ||
tracker_id == TRACKER_SOUNDTRACKER) { /* note > B-3 */
tracker_id = TRACKER_UNKNOWN;
}
}
#endif
mod_event = patbuf;
for (j = 0; j < 64; j++) {
for (k = 0; k < mod->chn; k++) {
event = &EVENT(i, k, j);
#ifdef LIBXMP_CORE_PLAYER
libxmp_decode_protracker_event(event, mod_event);
#else
switch (tracker_id) {
case TRACKER_PROBABLY_NOISETRACKER:
case TRACKER_NOISETRACKER:
libxmp_decode_noisetracker_event(event, mod_event);
break;
default:
libxmp_decode_protracker_event(event, mod_event);
}
#endif
mod_event += 4;
}
}
}
free(patbuf);
#ifndef LIBXMP_CORE_PLAYER
/* VBlank detection routine.
* Despite VBlank being dependent on the tracker used, VBlank detection
* is complex and uses heuristics mostly independent from tracker ID.
* See also: the scan.c comparison code enabled by m->compare_vblank
*/
if (!needs_timing_detection) {
/* Noisetracker and some other trackers do not support CIA timing. The
* only known MOD in the wild that relies on this is muppenkorva.mod
* by Glue Master (loaded by the His Master's Noise loader).
*/
if (tracker_is_vblank(tracker_id)) {
m->quirk |= QUIRK_NOBPM;
}
m->compare_vblank = 0;
} else if (samerow_fxx) {
/* If low Fxx and high Fxx are on the same row, there's a high chance
* this is from a CIA-based tracker. There are some exceptions.
*/
if (tracker_id == TRACKER_NOISETRACKER ||
tracker_id == TRACKER_PROBABLY_NOISETRACKER ||
tracker_id == TRACKER_SOUNDTRACKER) {
tracker_id = TRACKER_UNKNOWN;
}
m->compare_vblank = 0;
} else if (high_fxx && mod->len >= 8) {
/* Test for high Fxx at the end only--this is typically VBlank,
* and is used to add silence to the end of modules.
*
* Exception: if the final high Fxx is F7D, this module is either CIA
* or is VBlank that was modified to play as CIA, so do nothing.
*
* TODO: MPT resets modules on the end loop, so some of the very long
* silent sections in modules affected by this probably expect CIA. It
* should eventually be possible to detect those.
*/
const int threshold = mod->len - 2;
for (i = 0; i < threshold; i++) {
if (pat_high_fxx[mod->xxo[i]])
break;
}
if (i == threshold) {
for (i = mod->len - 1; i >= threshold; i--) {
uint8 fxx = pat_high_fxx[mod->xxo[i]];
if (fxx == 0x00)
continue;
if (fxx == 0x7d)
break;
m->compare_vblank = 0;
m->quirk |= QUIRK_NOBPM;
break;
}
}
}
switch (tracker_id) {
case TRACKER_PROTRACKER:
tracker = "Protracker";
ptkloop = 1;
break;
case TRACKER_PROBABLY_NOISETRACKER:
case TRACKER_NOISETRACKER:
tracker = "Noisetracker";
break;
case TRACKER_SOUNDTRACKER:
tracker = "Soundtracker";
break;
case TRACKER_FASTTRACKER:
case TRACKER_FASTTRACKER2:
tracker = "Fast Tracker";
m->period_type = PERIOD_AMIGA;
break;
case TRACKER_TAKETRACKER:
tracker = "Take Tracker";
m->period_type = PERIOD_AMIGA;
break;
case TRACKER_OCTALYSER:
tracker = "Octalyser";
break;
case TRACKER_DIGITALTRACKER:
tracker = "Digital Tracker";
break;
case TRACKER_FLEXTRAX:
tracker = "Flextrax";
break;
case TRACKER_MODSGRAVE:
tracker = "Mod's Grave";
break;
case TRACKER_SCREAMTRACKER3:
tracker = "Scream Tracker";
m->period_type = PERIOD_AMIGA;
break;
case TRACKER_CONVERTEDST:
case TRACKER_CONVERTED:
tracker = "Converted";
break;
case TRACKER_CLONE:
tracker = "Protracker clone";
m->period_type = PERIOD_AMIGA;
break;
case TRACKER_OPENMPT:
tracker = "OpenMPT";
ptkloop = 1;
break;
default:
case TRACKER_UNKNOWN_CONV:
case TRACKER_UNKNOWN:
tracker = "Unknown tracker";
m->period_type = PERIOD_AMIGA;
break;
}
if (out_of_range) {
m->period_type = PERIOD_AMIGA;
}
if (tracker_id == TRACKER_MODSGRAVE) {
snprintf(mod->type, XMP_NAME_SIZE, "%s", tracker);
} else {
snprintf(mod->type, XMP_NAME_SIZE, "%s %s", tracker, magic);
}
#else
libxmp_set_type(m, (mod->chn == 4) ? "Protracker" : "Fasttracker");
#endif
MODULE_INFO();
/* Load samples */
D_(D_INFO "Stored samples: %d", mod->smp);
for (i = 0; i < mod->smp; i++) {
int flags;
if (!mod->xxs[i].len)
continue;
flags = (ptkloop && mod->xxs[i].lps == 0) ? SAMPLE_FLAG_FULLREP : 0;
#ifdef LIBXMP_CORE_PLAYER
if (libxmp_load_sample(m, f, flags, &mod->xxs[i], NULL) < 0)
return -1;
#else
if (ptsong) {
HIO_HANDLE *s;
char sn[XMP_MAXPATH];
char tmpname[32];
const char *instname = mod->xxi[i].name;
if (!instname[0] || !m->dirname)
continue;
if (libxmp_copy_name_for_fopen(tmpname, instname, 32))
continue;
snprintf(sn, XMP_MAXPATH, "%s%s", m->dirname, tmpname);
if ((s = hio_open(sn, "rb")) != NULL) {
if (libxmp_load_sample(m, s, flags, &mod->xxs[i], NULL) < 0) {
hio_close(s);
return -1;
}
hio_close(s);
}
} else {
uint8 buf[5];
long pos;
int num;
if ((pos = hio_tell(f)) < 0) {
return -1;
}
num = hio_read(buf, 1, 5, f);
if (num == 5 && !memcmp(buf, "ADPCM", 5)) {
flags |= SAMPLE_FLAG_ADPCM;
} else {
hio_seek(f, pos, SEEK_SET);
}
if (libxmp_load_sample(m, f, flags, &mod->xxs[i], NULL) < 0)
return -1;
}
#endif
}
#ifdef LIBXMP_CORE_PLAYER
if (mod->chn > 4) {
m->quirk &= ~QUIRK_PROTRACK;
m->quirk |= QUIRKS_FT2 | QUIRK_FTMOD;
m->read_event_type = READ_EVENT_FT2;
m->period_type = PERIOD_AMIGA;
}
#else
if (tracker_id == TRACKER_PROTRACKER || tracker_id == TRACKER_OPENMPT) {
m->quirk |= QUIRK_PROTRACK;
} else if (tracker_id == TRACKER_SCREAMTRACKER3) {
m->c4rate = C4_NTSC_RATE;
m->quirk |= QUIRKS_ST3;
m->read_event_type = READ_EVENT_ST3;
} else if (tracker_id == TRACKER_FASTTRACKER || tracker_id == TRACKER_FASTTRACKER2 || tracker_id == TRACKER_TAKETRACKER || tracker_id == TRACKER_MODSGRAVE || mod->chn > 4) {
m->c4rate = C4_NTSC_RATE;
m->quirk |= QUIRKS_FT2 | QUIRK_FTMOD;
m->read_event_type = READ_EVENT_FT2;
m->period_type = PERIOD_AMIGA;
}
#endif
return 0;
}