2022-08-26 15:15:16 +00:00
|
|
|
/* Extended Module Player
|
2023-06-17 08:37:50 +00:00
|
|
|
* Copyright (C) 1996-2023 Claudio Matsuoka and Hipolito Carraro Jr
|
2022-08-26 15:15:16 +00:00
|
|
|
*
|
|
|
|
* 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:
|
|
|
|
*
|
2023-06-17 08:37:50 +00:00
|
|
|
* - 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.
|
2022-08-26 15:15:16 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "loader.h"
|
|
|
|
#include "mod.h"
|
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
#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 */
|
|
|
|
|
2022-08-26 15:15:16 +00:00
|
|
|
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 = {
|
2023-06-17 08:37:50 +00:00
|
|
|
#ifdef LIBXMP_CORE_PLAYER
|
2022-08-26 15:15:16 +00:00
|
|
|
"Protracker",
|
2023-06-17 08:37:50 +00:00
|
|
|
#else
|
|
|
|
"Amiga Protracker/Compatible",
|
|
|
|
#endif
|
2022-08-26 15:15:16 +00:00
|
|
|
mod_test,
|
|
|
|
mod_load
|
|
|
|
};
|
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
#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)
|
2022-08-26 15:15:16 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
char buf[4];
|
2023-06-17 08:37:50 +00:00
|
|
|
#ifndef LIBXMP_CORE_PLAYER
|
|
|
|
uint8 pat_buf[1024];
|
|
|
|
int smp_size, num_pat;
|
|
|
|
long size;
|
|
|
|
int count;
|
|
|
|
int detected;
|
|
|
|
#endif
|
2022-08-26 15:15:16 +00:00
|
|
|
|
|
|
|
hio_seek(f, start + 1080, SEEK_SET);
|
2023-06-17 08:37:50 +00:00
|
|
|
if (hio_read(buf, 1, 4, f) < 4) {
|
2022-08-26 15:15:16 +00:00
|
|
|
return -1;
|
2023-06-17 08:37:50 +00:00
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if (!strncmp(buf + 2, "CH", 2) &&
|
|
|
|
isdigit((unsigned char)buf[0]) && isdigit((unsigned char)buf[1])) {
|
2022-08-26 15:15:16 +00:00
|
|
|
i = (buf[0] - '0') * 10 + buf[1] - '0';
|
|
|
|
if (i > 0 && i <= 32) {
|
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if (!strncmp(buf + 1, "CHN", 3) && isdigit((unsigned char)*buf)) {
|
|
|
|
if (*buf - '0') {
|
2022-08-26 15:15:16 +00:00
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
#ifdef LIBXMP_CORE_PLAYER
|
2022-08-26 15:15:16 +00:00
|
|
|
if (memcmp(buf, "M.K.", 4))
|
|
|
|
return -1;
|
2023-06-17 08:37:50 +00:00
|
|
|
#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 */
|
2022-08-26 15:15:16 +00:00
|
|
|
|
|
|
|
found:
|
|
|
|
hio_seek(f, start + 0, SEEK_SET);
|
|
|
|
libxmp_read_title(f, t, 20);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
|
|
|
|
#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)
|
2022-08-26 15:15:16 +00:00
|
|
|
{
|
|
|
|
struct xmp_module *mod = &m->mod;
|
2023-06-17 08:37:50 +00:00
|
|
|
int has_loop_0 = 0;
|
|
|
|
int has_vol_in_empty_ins = 0;
|
|
|
|
int i;
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
/* 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
|
2022-08-26 15:15:16 +00:00
|
|
|
if (!memcmp(magic, "M.K.", 4)) {
|
|
|
|
mod->chn = 4;
|
2023-06-17 08:37:50 +00:00
|
|
|
} 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';
|
2022-08-26 15:15:16 +00:00
|
|
|
} else {
|
2023-06-17 08:37:50 +00:00
|
|
|
return -1;
|
2022-08-26 15:15:16 +00:00
|
|
|
}
|
2023-06-17 08:37:50 +00:00
|
|
|
#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
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
xxi = &mod->xxi[i];
|
|
|
|
sub = &xxi->sub[0];
|
|
|
|
xxs = &mod->xxs[i];
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
skip_test:
|
|
|
|
#endif
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if (mod->chn >= XMP_MAX_CHANNELS) {
|
|
|
|
return -1;
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
mod->trk = mod->chn * mod->pat;
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
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 ? '!' : ' ');
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if (libxmp_init_pattern(mod) < 0)
|
|
|
|
return -1;
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
/* Load and convert patterns */
|
|
|
|
D_(D_INFO "Stored patterns: %d", mod->pat);
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if ((patbuf = (uint8 *) malloc(64 * 4 * mod->chn)) == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
#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;
|
2022-08-26 15:15:16 +00:00
|
|
|
}
|
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if (hio_read(patbuf, 64 * 4 * mod->chn, 1, f) < 1) {
|
|
|
|
free(patbuf);
|
|
|
|
return -1;
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
#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;
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
/* Filter noisetracker events */
|
|
|
|
if (tracker_id == TRACKER_PROBABLY_NOISETRACKER) {
|
|
|
|
unsigned char fxt = LSN(mod_event[2]);
|
|
|
|
unsigned char fxp = LSN(mod_event[3]);
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
}
|
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if (out_of_range) {
|
|
|
|
if (tracker_id == TRACKER_UNKNOWN && mh.restart == 0x7f) {
|
|
|
|
tracker_id = TRACKER_SCREAMTRACKER3;
|
|
|
|
}
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
/* 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 */
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
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);
|
2022-08-26 15:15:16 +00:00
|
|
|
}
|
2023-06-17 08:37:50 +00:00
|
|
|
#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;
|
2022-08-26 15:15:16 +00:00
|
|
|
}
|
2023-06-17 08:37:50 +00:00
|
|
|
m->compare_vblank = 0;
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
} 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) {
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
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;
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if (!instname[0] || !m->dirname)
|
|
|
|
continue;
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if (libxmp_copy_name_for_fopen(tmpname, instname, 32))
|
|
|
|
continue;
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
snprintf(sn, XMP_MAXPATH, "%s%s", m->dirname, tmpname);
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
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;
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
if ((pos = hio_tell(f)) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
num = hio_read(buf, 1, 5, f);
|
2022-08-26 15:15:16 +00:00
|
|
|
|
2023-06-17 08:37:50 +00:00
|
|
|
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;
|
2022-08-26 15:15:16 +00:00
|
|
|
}
|