583 lines
13 KiB
C
583 lines
13 KiB
C
/* Extended Module Player
|
|
* Copyright (C) 1996-2021 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.
|
|
*/
|
|
|
|
/* Based on DigiBooster_E.guide from the DigiBoosterPro 2.20 package.
|
|
* DigiBooster Pro written by Tomasz & Waldemar Piasta
|
|
*/
|
|
|
|
#include "loader.h"
|
|
#include "iff.h"
|
|
#include "../period.h"
|
|
|
|
#define MAGIC_DBM0 MAGIC4('D','B','M','0')
|
|
|
|
|
|
static int dbm_test(HIO_HANDLE *, char *, const int);
|
|
static int dbm_load (struct module_data *, HIO_HANDLE *, const int);
|
|
|
|
const struct format_loader libxmp_loader_dbm = {
|
|
"DigiBooster Pro",
|
|
dbm_test,
|
|
dbm_load
|
|
};
|
|
|
|
static int dbm_test(HIO_HANDLE * f, char *t, const int start)
|
|
{
|
|
if (hio_read32b(f) != MAGIC_DBM0)
|
|
return -1;
|
|
|
|
hio_seek(f, 12, SEEK_CUR);
|
|
libxmp_read_title(f, t, 44);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct local_data {
|
|
int have_info;
|
|
int have_song;
|
|
int have_patt;
|
|
int have_smpl;
|
|
int have_inst;
|
|
int have_venv;
|
|
int have_penv;
|
|
int maj_version;
|
|
int min_version;
|
|
};
|
|
|
|
struct dbm_envelope {
|
|
int ins;
|
|
int flg;
|
|
int npt;
|
|
int sus;
|
|
int lps;
|
|
int lpe;
|
|
int sus2;
|
|
struct dbm_envelope_node {
|
|
uint16 position;
|
|
int16 value;
|
|
} nodes[32];
|
|
};
|
|
|
|
|
|
static void dbm_translate_effect(struct xmp_event *event, uint8 *fxt, uint8 *fxp)
|
|
{
|
|
switch (*fxt) {
|
|
case 0x0e:
|
|
switch (MSN(*fxp)) {
|
|
case 0x3: /* Play from backward */
|
|
/* TODO: this is supposed to play the sample in
|
|
* reverse only once, then forward. */
|
|
if (event->note) {
|
|
*fxt = FX_REVERSE;
|
|
*fxp = 1;
|
|
} else {
|
|
*fxt = *fxp = 0;
|
|
}
|
|
break;
|
|
case 0x4: /* Turn off sound in channel */
|
|
*fxt = FX_EXTENDED;
|
|
*fxp = (EX_CUT << 4);
|
|
break;
|
|
case 0x5: /* Turn on/off channel */
|
|
/* In DigiBooster Pro, this is tied to
|
|
* the channel mute toggle in the UI. */
|
|
*fxt = FX_TRK_VOL;
|
|
*fxp = *fxp ? 0x40 : 0x00;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x1c: /* Set Real BPM */
|
|
*fxt = FX_S3M_BPM;
|
|
break;
|
|
|
|
default:
|
|
if (*fxt > 0x1c)
|
|
*fxt = *fxp = 0;
|
|
}
|
|
}
|
|
|
|
static int get_info(struct module_data *m, int size, HIO_HANDLE *f, void *parm)
|
|
{
|
|
struct xmp_module *mod = &m->mod;
|
|
struct local_data *data = (struct local_data *)parm;
|
|
int val;
|
|
|
|
/* Sanity check */
|
|
if (data->have_info || size < 10) {
|
|
return -1;
|
|
}
|
|
data->have_info = 1;
|
|
|
|
val = hio_read16b(f);
|
|
if (val < 0 || val > 255) {
|
|
D_(D_CRIT "Invalid number of instruments: %d", val);
|
|
goto err;
|
|
}
|
|
mod->ins = val;
|
|
|
|
val = hio_read16b(f);
|
|
if (val < 0) {
|
|
D_(D_CRIT "Invalid number of samples: %d", val);
|
|
goto err2;
|
|
}
|
|
mod->smp = val;
|
|
|
|
hio_read16b(f); /* Songs */
|
|
|
|
val = hio_read16b(f);
|
|
if (val < 0 || val > 256) {
|
|
D_(D_CRIT "Invalid number of patterns: %d", val);
|
|
goto err3;
|
|
}
|
|
mod->pat = val;
|
|
|
|
val = hio_read16b(f);
|
|
if (val < 0 || val > XMP_MAX_CHANNELS) {
|
|
D_(D_CRIT "Invalid number of channels: %d", val);
|
|
goto err4;
|
|
}
|
|
mod->chn = val;
|
|
|
|
mod->trk = mod->pat * mod->chn;
|
|
|
|
if (libxmp_init_instrument(m) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
|
|
err4:
|
|
mod->pat = 0;
|
|
err3:
|
|
mod->smp = 0;
|
|
err2:
|
|
mod->ins = 0;
|
|
err:
|
|
return -1;
|
|
}
|
|
|
|
static int get_song(struct module_data *m, int size, HIO_HANDLE *f, void *parm)
|
|
{
|
|
struct xmp_module *mod = &m->mod;
|
|
struct local_data *data = (struct local_data *)parm;
|
|
int i;
|
|
char buffer[50];
|
|
|
|
/* Sanity check */
|
|
if (data->have_song || size < 46) {
|
|
return 0;
|
|
}
|
|
data->have_song = 1;
|
|
|
|
hio_read(buffer, 44, 1, f);
|
|
D_(D_INFO "Song name: %.44s", buffer);
|
|
|
|
mod->len = hio_read16b(f);
|
|
D_(D_INFO "Song length: %d patterns", mod->len);
|
|
|
|
/* Sanity check */
|
|
if (mod->len > 256) {
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < mod->len; i++)
|
|
mod->xxo[i] = hio_read16b(f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_inst(struct module_data *m, int size, HIO_HANDLE *f, void *parm)
|
|
{
|
|
struct xmp_module *mod = &m->mod;
|
|
struct local_data *data = (struct local_data *)parm;
|
|
int i;
|
|
int c2spd, flags, snum;
|
|
uint8 buffer[50];
|
|
|
|
/* Sanity check */
|
|
if (data->have_inst || size < 50 * mod->ins) {
|
|
return -1;
|
|
}
|
|
data->have_inst = 1;
|
|
|
|
D_(D_INFO "Instruments: %d", mod->ins);
|
|
|
|
for (i = 0; i < mod->ins; i++) {
|
|
mod->xxi[i].nsm = 1;
|
|
if (libxmp_alloc_subinstrument(mod, i, 1) < 0)
|
|
return -1;
|
|
|
|
if (hio_read(buffer, 30, 1, f) == 0)
|
|
return -1;
|
|
|
|
libxmp_instrument_name(mod, i, buffer, 30);
|
|
snum = hio_read16b(f);
|
|
if (snum == 0 || snum > mod->smp) {
|
|
/* Skip remaining data for this instrument. */
|
|
hio_seek(f, 18, SEEK_CUR);
|
|
continue;
|
|
}
|
|
|
|
mod->xxi[i].sub[0].sid = --snum;
|
|
mod->xxi[i].sub[0].vol = hio_read16b(f);
|
|
c2spd = hio_read32b(f);
|
|
mod->xxs[snum].lps = hio_read32b(f);
|
|
mod->xxs[snum].lpe = mod->xxs[snum].lps + hio_read32b(f);
|
|
mod->xxi[i].sub[0].pan = 0x80 + (int16)hio_read16b(f);
|
|
if (mod->xxi[i].sub[0].pan > 0xff)
|
|
mod->xxi[i].sub[0].pan = 0xff;
|
|
flags = hio_read16b(f);
|
|
mod->xxs[snum].flg = flags & 0x03 ? XMP_SAMPLE_LOOP : 0;
|
|
mod->xxs[snum].flg |= flags & 0x02 ? XMP_SAMPLE_LOOP_BIDIR : 0;
|
|
|
|
libxmp_c2spd_to_note(c2spd, &mod->xxi[i].sub[0].xpo, &mod->xxi[i].sub[0].fin);
|
|
|
|
D_(D_INFO "[%2X] %-30.30s #%02X V%02x P%02x %5d",
|
|
i, mod->xxi[i].name, snum,
|
|
mod->xxi[i].sub[0].vol, mod->xxi[i].sub[0].pan, c2spd);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_patt(struct module_data *m, int size, HIO_HANDLE *f, void *parm)
|
|
{
|
|
struct xmp_module *mod = &m->mod;
|
|
struct local_data *data = (struct local_data *)parm;
|
|
int i, c, r, n, sz;
|
|
struct xmp_event *event, dummy;
|
|
uint8 x;
|
|
|
|
/* Sanity check */
|
|
if (data->have_patt || !data->have_info) {
|
|
return -1;
|
|
}
|
|
data->have_patt = 1;
|
|
|
|
if (libxmp_init_pattern(mod) < 0)
|
|
return -1;
|
|
|
|
D_(D_INFO "Stored patterns: %d ", mod->pat);
|
|
|
|
/*
|
|
* Note: channel and flag bytes are inverted in the format
|
|
* description document
|
|
*/
|
|
|
|
for (i = 0; i < mod->pat; i++) {
|
|
int rows = hio_read16b(f);
|
|
if (hio_error(f))
|
|
return -1;
|
|
|
|
if (libxmp_alloc_pattern_tracks(mod, i, rows) < 0)
|
|
return -1;
|
|
|
|
sz = hio_read32b(f);
|
|
//printf("rows = %d, size = %d\n", mod->xxp[i]->rows, sz);
|
|
|
|
r = 0;
|
|
/*c = -1;*/
|
|
|
|
while (sz > 0) {
|
|
//printf(" offset=%x, sz = %d, ", hio_tell(f), sz);
|
|
c = hio_read8(f);
|
|
if (hio_error(f))
|
|
return -1;
|
|
|
|
if (--sz <= 0) break;
|
|
//printf("c = %02x\n", c);
|
|
|
|
if (c == 0) {
|
|
r++;
|
|
continue;
|
|
}
|
|
c--;
|
|
|
|
n = hio_read8(f);
|
|
if (--sz <= 0) break;
|
|
//printf(" n = %d\n", n);
|
|
|
|
if (c >= mod->chn || r >= mod->xxp[i]->rows) {
|
|
event = &dummy;
|
|
} else {
|
|
event = &EVENT(i, c, r);
|
|
}
|
|
|
|
memset(event, 0, sizeof (struct xmp_event));
|
|
|
|
if (n & 0x01) {
|
|
x = hio_read8(f);
|
|
event->note = 13 + MSN(x) * 12 + LSN(x);
|
|
if (--sz <= 0) break;
|
|
}
|
|
if (n & 0x02) {
|
|
event->ins = hio_read8(f);
|
|
if (--sz <= 0) break;
|
|
}
|
|
if (n & 0x04) {
|
|
event->fxt = hio_read8(f);
|
|
if (--sz <= 0) break;
|
|
}
|
|
if (n & 0x08) {
|
|
event->fxp = hio_read8(f);
|
|
if (--sz <= 0) break;
|
|
}
|
|
if (n & 0x10) {
|
|
event->f2t = hio_read8(f);
|
|
if (--sz <= 0) break;
|
|
}
|
|
if (n & 0x20) {
|
|
event->f2p = hio_read8(f);
|
|
if (--sz <= 0) break;
|
|
}
|
|
|
|
dbm_translate_effect(event, &event->fxt, &event->fxp);
|
|
dbm_translate_effect(event, &event->f2t, &event->f2p);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_smpl(struct module_data *m, int size, HIO_HANDLE *f, void *parm)
|
|
{
|
|
struct xmp_module *mod = &m->mod;
|
|
struct local_data *data = (struct local_data *)parm;
|
|
int i, flags;
|
|
|
|
/* Sanity check */
|
|
if (data->have_smpl || !data->have_info) {
|
|
return -1;
|
|
}
|
|
data->have_smpl = 1;
|
|
|
|
D_(D_INFO "Stored samples: %d", mod->smp);
|
|
|
|
for (i = 0; i < mod->smp; i++) {
|
|
flags = hio_read32b(f);
|
|
mod->xxs[i].len = hio_read32b(f);
|
|
|
|
if (flags & 0x02) {
|
|
mod->xxs[i].flg |= XMP_SAMPLE_16BIT;
|
|
}
|
|
|
|
if (flags & 0x04) { /* Skip 32-bit samples */
|
|
mod->xxs[i].len <<= 2;
|
|
hio_seek(f, mod->xxs[i].len, SEEK_CUR);
|
|
continue;
|
|
}
|
|
|
|
if (libxmp_load_sample(m, f, SAMPLE_FLAG_BIGEND, &mod->xxs[i], NULL) < 0)
|
|
return -1;
|
|
|
|
if (mod->xxs[i].len == 0)
|
|
continue;
|
|
|
|
D_(D_INFO "[%2X] %08x %05x%c%05x %05x %c",
|
|
i, flags, mod->xxs[i].len,
|
|
mod->xxs[i].flg & XMP_SAMPLE_16BIT ? '+' : ' ',
|
|
mod->xxs[i].lps, mod->xxs[i].lpe,
|
|
mod->xxs[i].flg & XMP_SAMPLE_LOOP ?
|
|
(mod->xxs[i].flg & XMP_SAMPLE_LOOP_BIDIR ? 'B' : 'L') : ' ');
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_envelope(struct xmp_module *mod, struct dbm_envelope *env, HIO_HANDLE *f)
|
|
{
|
|
int i;
|
|
|
|
env->ins = (int)hio_read16b(f) - 1;
|
|
env->flg = hio_read8(f) & 0x7;
|
|
env->npt = (int)hio_read8(f) + 1; /* DBM counts sections, not points. */
|
|
env->sus = hio_read8(f);
|
|
env->lps = hio_read8(f);
|
|
env->lpe = hio_read8(f);
|
|
env->sus2 = hio_read8(f);
|
|
|
|
/* The format document claims there should be a reserved byte here but
|
|
* no DigiBooster Pro module actually has this. The revised document
|
|
* on the DigiBooster 3 website is corrected.
|
|
*/
|
|
|
|
/* Sanity check */
|
|
if (env->ins < 0 || env->ins >= mod->ins || env->npt > 32 ||
|
|
env->sus >= 32 || env->lps >= 32 || env->lpe >= 32)
|
|
return -1;
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
env->nodes[i].position = hio_read16b(f);
|
|
env->nodes[i].value = (int16)hio_read16b(f);
|
|
}
|
|
|
|
if (hio_error(f))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_venv(struct module_data *m, int size, HIO_HANDLE *f, void *parm)
|
|
{
|
|
struct xmp_module *mod = &m->mod;
|
|
struct local_data *data = (struct local_data *)parm;
|
|
struct dbm_envelope env;
|
|
int i, j, nenv, ins;
|
|
|
|
/* Sanity check */
|
|
if (data->have_venv || !data->have_info) {
|
|
return -1;
|
|
}
|
|
data->have_venv = 1;
|
|
|
|
nenv = hio_read16b(f);
|
|
|
|
D_(D_INFO "Vol envelopes : %d ", nenv);
|
|
|
|
for (i = 0; i < nenv; i++) {
|
|
if (read_envelope(mod, &env, f) != 0)
|
|
return -1;
|
|
|
|
ins = env.ins;
|
|
mod->xxi[ins].aei.flg = env.flg;
|
|
mod->xxi[ins].aei.npt = env.npt;
|
|
mod->xxi[ins].aei.sus = env.sus;
|
|
mod->xxi[ins].aei.lps = env.lps;
|
|
mod->xxi[ins].aei.lpe = env.lpe;
|
|
|
|
for (j = 0; j < 32; j++) {
|
|
mod->xxi[ins].aei.data[j * 2 + 0] = env.nodes[j].position;
|
|
mod->xxi[ins].aei.data[j * 2 + 1] = env.nodes[j].value;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_penv(struct module_data *m, int size, HIO_HANDLE *f, void *parm)
|
|
{
|
|
struct xmp_module *mod = &m->mod;
|
|
struct local_data *data = (struct local_data *)parm;
|
|
struct dbm_envelope env;
|
|
int i, j, nenv, ins;
|
|
|
|
/* Sanity check */
|
|
if (data->have_penv || !data->have_info) {
|
|
return -1;
|
|
}
|
|
data->have_penv = 1;
|
|
|
|
nenv = hio_read16b(f);
|
|
|
|
D_(D_INFO "Pan envelopes : %d ", nenv);
|
|
|
|
for (i = 0; i < nenv; i++) {
|
|
if (read_envelope(mod, &env, f) != 0)
|
|
return -1;
|
|
|
|
ins = env.ins;
|
|
mod->xxi[ins].pei.flg = env.flg;
|
|
mod->xxi[ins].pei.npt = env.npt;
|
|
mod->xxi[ins].pei.sus = env.sus;
|
|
mod->xxi[ins].pei.lps = env.lps;
|
|
mod->xxi[ins].pei.lpe = env.lpe;
|
|
|
|
for (j = 0; j < 32; j++) {
|
|
/* DigiBooster Pro 2 stores the pan value between 0 and 64.
|
|
* DigiBooster 3 stores it from -128 to 128 (Krashan - M2.dbm).
|
|
*/
|
|
if (data->maj_version >= 3) {
|
|
env.nodes[j].value = env.nodes[j].value / 4 + 32;
|
|
}
|
|
|
|
mod->xxi[ins].pei.data[j * 2 + 0] = env.nodes[j].position;
|
|
mod->xxi[ins].pei.data[j * 2 + 1] = env.nodes[j].value;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dbm_load(struct module_data *m, HIO_HANDLE *f, const int start)
|
|
{
|
|
struct xmp_module *mod = &m->mod;
|
|
iff_handle handle;
|
|
char name[XMP_NAME_SIZE];
|
|
uint16 version;
|
|
int i, ret;
|
|
struct local_data data;
|
|
|
|
LOAD_INIT();
|
|
|
|
hio_read32b(f); /* DBM0 */
|
|
|
|
memset(&data, 0, sizeof(struct local_data));
|
|
version = hio_read16b(f);
|
|
data.maj_version = version >> 8;
|
|
data.min_version = version & 0xFF;
|
|
|
|
hio_seek(f, 10, SEEK_CUR);
|
|
if (hio_read(name, 1, 44, f) < 44)
|
|
return -1;
|
|
name[44] = '\0';
|
|
|
|
handle = libxmp_iff_new();
|
|
if (handle == NULL)
|
|
return -1;
|
|
|
|
m->c4rate = C4_NTSC_RATE;
|
|
m->quirk |= QUIRK_FINEFX;
|
|
|
|
/* IFF chunk IDs */
|
|
ret = libxmp_iff_register(handle, "INFO", get_info);
|
|
ret |= libxmp_iff_register(handle, "SONG", get_song);
|
|
ret |= libxmp_iff_register(handle, "INST", get_inst);
|
|
ret |= libxmp_iff_register(handle, "PATT", get_patt);
|
|
ret |= libxmp_iff_register(handle, "SMPL", get_smpl);
|
|
ret |= libxmp_iff_register(handle, "VENV", get_venv);
|
|
ret |= libxmp_iff_register(handle, "PENV", get_penv);
|
|
|
|
if (ret != 0)
|
|
return -1;
|
|
|
|
strncpy(mod->name, name, XMP_NAME_SIZE);
|
|
snprintf(mod->type, XMP_NAME_SIZE, "DigiBooster Pro %d.%02x DBM0",
|
|
data.maj_version, data.min_version);
|
|
|
|
MODULE_INFO();
|
|
|
|
/* Load IFF chunks */
|
|
if (libxmp_iff_load(handle, m, f, &data) < 0) {
|
|
libxmp_iff_release(handle);
|
|
return -1;
|
|
}
|
|
|
|
libxmp_iff_release(handle);
|
|
|
|
for (i = 0; i < mod->chn; i++)
|
|
mod->xxc[i].pan = 0x80;
|
|
|
|
return 0;
|
|
}
|