xmpnx/libxmp/src/loaders/dbm_load.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;
}