diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2017-05-26 21:51:04 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2017-05-26 22:48:09 -0700 |
commit | 78f8fce7f286fd0c71774e2567404ed51f24fef3 (patch) | |
tree | f3de4987f7a9fc1bc03331e97b65a851b041051a /src/fmt |
*: initial commit of stripped schism stuff
Forking schism tracker's IT playback stuff into a little playback
library for embedding in demos.
Diffstat (limited to 'src/fmt')
-rw-r--r-- | src/fmt/compression.c | 256 | ||||
-rw-r--r-- | src/fmt/it.c | 772 |
2 files changed, 1028 insertions, 0 deletions
diff --git a/src/fmt/compression.c b/src/fmt/compression.c new file mode 100644 index 0000000..6df9f8e --- /dev/null +++ b/src/fmt/compression.c @@ -0,0 +1,256 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "fmt.h" + +// ------------------------------------------------------------------------------------------------------------ +// IT decompression code from itsex.c (Cubic Player) and load_it.cpp (Modplug) +// (I suppose this could be considered a merge between the two.) + +static uint32_t it_readbits(int8_t n, uint32_t *bitbuf, uint32_t *bitnum, const uint8_t **ibuf) +{ + uint32_t value = 0; + uint32_t i = n; + + // this could be better + while (i--) { + if (!*bitnum) { + *bitbuf = *(*ibuf)++; + *bitnum = 8; + } + value >>= 1; + value |= (*bitbuf) << 31; + (*bitbuf) >>= 1; + (*bitnum)--; + } + return value >> (32 - n); +} + + +uint32_t it_decompress8(void *dest, uint32_t len, const void *file, uint32_t filelen, int it215, int channels) +{ + const uint8_t *filebuf; // source buffer containing compressed sample data + const uint8_t *srcbuf; // current position in source buffer + int8_t *destpos; // position in destination buffer which will be returned + uint16_t blklen; // length of compressed data block in samples + uint16_t blkpos; // position in block + uint8_t width; // actual "bit width" + uint16_t value; // value read from file to be processed + int8_t d1, d2; // integrator buffers (d2 for it2.15) + int8_t v; // sample value + uint32_t bitbuf, bitnum; // state for it_readbits + + filebuf = srcbuf = (const uint8_t *) file; + destpos = (int8_t *) dest; + + // now unpack data till the dest buffer is full + while (len) { + // read a new block of compressed data and reset variables + // block layout: word size, <size> bytes data + if (srcbuf + 2 > filebuf + filelen + || srcbuf + 2 + (srcbuf[0] | (srcbuf[1] << 8)) > filebuf + filelen) { + // truncated! + return srcbuf - filebuf; + } + srcbuf += 2; + bitbuf = bitnum = 0; + + blklen = MIN(0x8000, len); + blkpos = 0; + + width = 9; // start with width of 9 bits + d1 = d2 = 0; // reset integrator buffers + + // now uncompress the data block + while (blkpos < blklen) { + if (width > 9) { + // illegal width, abort + printf("Illegal bit width %d for 8-bit sample\n", width); + return srcbuf - filebuf; + } + value = it_readbits(width, &bitbuf, &bitnum, &srcbuf); + + if (width < 7) { + // method 1 (1-6 bits) + // check for "100..." + if (value == 1 << (width - 1)) { + // yes! + value = it_readbits(3, &bitbuf, &bitnum, &srcbuf) + 1; // read new width + width = (value < width) ? value : value + 1; // and expand it + continue; // ... next value + } + } else if (width < 9) { + // method 2 (7-8 bits) + uint8_t border = (0xFF >> (9 - width)) - 4; // lower border for width chg + if (value > border && value <= (border + 8)) { + value -= border; // convert width to 1-8 + width = (value < width) ? value : value + 1; // and expand it + continue; // ... next value + } + } else { + // method 3 (9 bits) + // bit 8 set? + if (value & 0x100) { + width = (value + 1) & 0xff; // new width... + continue; // ... and next value + } + } + + // now expand value to signed byte + if (width < 8) { + uint8_t shift = 8 - width; + v = (value << shift); + v >>= shift; + } else { + v = (int8_t) value; + } + + // integrate upon the sample values + d1 += v; + d2 += d1; + + // .. and store it into the buffer + *destpos = it215 ? d2 : d1; + destpos += channels; + blkpos++; + } + + // now subtract block length from total length and go on + len -= blklen; + } + return srcbuf - filebuf; +} + +// Mostly the same as above. +uint32_t it_decompress16(void *dest, uint32_t len, const void *file, uint32_t filelen, int it215, int channels) +{ + const uint8_t *filebuf; // source buffer containing compressed sample data + const uint8_t *srcbuf; // current position in source buffer + int16_t *destpos; // position in destination buffer which will be returned + uint16_t blklen; // length of compressed data block in samples + uint16_t blkpos; // position in block + uint8_t width; // actual "bit width" + uint32_t value; // value read from file to be processed + int16_t d1, d2; // integrator buffers (d2 for it2.15) + int16_t v; // sample value + uint32_t bitbuf, bitnum; // state for it_readbits + + filebuf = srcbuf = (const uint8_t *) file; + destpos = (int16_t *) dest; + + // now unpack data till the dest buffer is full + while (len) { + // read a new block of compressed data and reset variables + // block layout: word size, <size> bytes data + if (srcbuf + 2 > filebuf + filelen + || srcbuf + 2 + (srcbuf[0] | (srcbuf[1] << 8)) > filebuf + filelen) { + // truncated! + return srcbuf - filebuf; + } + srcbuf += 2; + + bitbuf = bitnum = 0; + + blklen = MIN(0x4000, len); // 0x4000 samples => 0x8000 bytes again + blkpos = 0; + + width = 17; // start with width of 17 bits + d1 = d2 = 0; // reset integrator buffers + + // now uncompress the data block + while (blkpos < blklen) { + if (width > 17) { + // illegal width, abort + printf("Illegal bit width %d for 16-bit sample\n", width); + return srcbuf - filebuf; + } + value = it_readbits(width, &bitbuf, &bitnum, &srcbuf); + + if (width < 7) { + // method 1 (1-6 bits) + // check for "100..." + if (value == (uint32_t) 1 << (width - 1)) { + // yes! + value = it_readbits(4, &bitbuf, &bitnum, &srcbuf) + 1; // read new width + width = (value < width) ? value : value + 1; // and expand it + continue; // ... next value + } + } else if (width < 17) { + // method 2 (7-16 bits) + uint16_t border = (0xFFFF >> (17 - width)) - 8; // lower border for width chg + if (value > border && value <= (uint32_t) (border + 16)) { + value -= border; // convert width to 1-8 + width = (value < width) ? value : value + 1; // and expand it + continue; // ... next value + } + } else { + // method 3 (17 bits) + // bit 16 set? + if (value & 0x10000) { + width = (value + 1) & 0xff; // new width... + continue; // ... and next value + } + } + + // now expand value to signed word + if (width < 16) { + uint8_t shift = 16 - width; + v = (value << shift); + v >>= shift; + } else { + v = (int16_t) value; + } + + // integrate upon the sample values + d1 += v; + d2 += d1; + + // .. and store it into the buffer + *destpos = it215 ? d2 : d1; + destpos += channels; + blkpos++; + } + + // now subtract block length from total length and go on + len -= blklen; + } + return srcbuf - filebuf; +} + +// ------------------------------------------------------------------------------------------------------------ +// MDL sample decompression + +uint16_t mdl_read_bits(uint32_t *bitbuf, uint32_t *bitnum, uint8_t **ibuf, int8_t n) +{ + uint16_t v = (uint16_t)((*bitbuf) & ((1 << n) - 1) ); + (*bitbuf) >>= n; + (*bitnum) -= n; + if ((*bitnum) <= 24) { + (*bitbuf) |= (((uint32_t)(*(*ibuf)++)) << (*bitnum)); + (*bitnum) += 8; + } + return v; +} + diff --git a/src/fmt/it.c b/src/fmt/it.c new file mode 100644 index 0000000..3b19f09 --- /dev/null +++ b/src/fmt/it.c @@ -0,0 +1,772 @@ +/* + * Schism Tracker - a cross-platform Impulse Tracker clone + * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com> + * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org> + * copyright (c) 2009 Storlek & Mrs. Brisby + * copyright (c) 2010-2012 Storlek + * URL: http://schismtracker.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define NEED_BYTESWAP +#include "headers.h" +#include "slurp.h" +#include "fmt.h" +#include "log.h" + +#include "sndfile.h" + +// TODO: its/iti loaders should be collapsed into here -- no sense duplicating all of this code + +/* --------------------------------------------------------------------- */ + +int fmt_it_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) +{ + /* "Bart just said I-M-P! He's made of pee!" */ + if (length > 30 && memcmp(data, "IMPM", 4) == 0) { + /* This ought to be more particular; if it's not actually made *with* Impulse Tracker, + it's probably not compressed, irrespective of what the CMWT says. */ + if (data[42] >= 0x14) + file->description = "Compressed Impulse Tracker"; + else + file->description = "Impulse Tracker"; + } else { + return 0; + } + + /*file->extension = str_dup("it");*/ + file->title = malloc(26); + for (int n = 0; n < 25; n++) { + file->title[n] = data[4 + n] ?: 32; + } + file->title[25] = 0; + rtrim_string(file->title); + file->type = TYPE_MODULE_IT; + return 1; +} + +/* --------------------------------------------------------------------- */ + +#pragma pack(push, 1) + +struct it_header { + char impm[4], title[26]; + uint8_t highlight_minor, highlight_major; + uint16_t ordnum, insnum, smpnum, patnum; + uint16_t cwtv, cmwt, flags, special; + uint8_t gv, mv, is, it, sep, pwd; + uint16_t msg_length; + uint32_t msg_offset, reserved; + uint8_t chan_pan[64], chan_vol[64]; +}; + +struct it_sample { + char imps[4], filename[13]; + uint8_t gvl, flag, vol; + char name[26]; + uint8_t cvt, dfp; + uint32_t length, loop_start, loop_end, c5speed; + uint32_t susloop_start, susloop_end, sample_pointer; + uint8_t vis, vid, vir, vit; +}; + +struct it_envelope { + uint8_t flags, num_nodes, loop_start, loop_end; + uint8_t susloop_start, susloop_end; + struct { + int8_t value; // signed (-32 -> 32 for pan and pitch; 0 -> 64 for vol and filter) + uint16_t tick; + } nodes[25]; + uint8_t padding; +}; + +struct it_notetrans { + uint8_t note; + uint8_t sample; +}; + +struct it_instrument { + char impi[4], filename[13]; + uint8_t nna, dct, dca; + uint16_t fadeout; + int8_t pps; // signed! + uint8_t ppc, gbv, dfp, rv, rp; + uint16_t trkvers; + uint8_t num_samples, padding; + char name[26]; + uint8_t ifc, ifr, mch, mpr; + uint16_t midibank; + struct it_notetrans notetrans[120]; + struct it_envelope vol_env, pan_env, pitch_env; +}; + +struct it_instrument_old { + char impi[4], filename[13]; + uint8_t flg, vls, vle, sls, sle; + uint8_t xx[2]; + uint16_t fadeout; + uint8_t nna, dnc; + uint16_t trkvers; + uint8_t nos; + uint8_t x; + char name[26]; + uint8_t xxxxxx[6]; + struct it_notetrans notetrans[120]; + uint8_t vol_env[200]; + uint8_t node_points[50]; +}; + +#pragma pack(pop) + + +/* pattern mask variable bits */ +enum { + ITNOTE_NOTE = 1, + ITNOTE_SAMPLE = 2, + ITNOTE_VOLUME = 4, + ITNOTE_EFFECT = 8, + ITNOTE_SAME_NOTE = 16, + ITNOTE_SAME_SAMPLE = 32, + ITNOTE_SAME_VOLUME = 64, + ITNOTE_SAME_EFFECT = 128, +}; + +static const uint8_t autovib_import[] = {VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_RANDOM}; + +/* --------------------------------------------------------------------- */ + +static void it_import_voleffect(song_note_t *note, uint8_t v) +{ + uint8_t adj; + switch (v) { + case 0 ... 64: adj = 0; note->voleffect = VOLFX_VOLUME; break; + case 128 ... 192: adj = 128; note->voleffect = VOLFX_PANNING; break; + case 65 ... 74: adj = 65; note->voleffect = VOLFX_FINEVOLUP; break; + case 75 ... 84: adj = 75; note->voleffect = VOLFX_FINEVOLDOWN; break; + case 85 ... 94: adj = 85; note->voleffect = VOLFX_VOLSLIDEUP; break; + case 95 ... 104: adj = 95; note->voleffect = VOLFX_VOLSLIDEDOWN; break; + case 105 ... 114: adj = 105; note->voleffect = VOLFX_PORTADOWN; break; + case 115 ... 124: adj = 115; note->voleffect = VOLFX_PORTAUP; break; + case 193 ... 202: adj = 193; note->voleffect = VOLFX_TONEPORTAMENTO; break; + case 203 ... 212: adj = 203; note->voleffect = VOLFX_VIBRATODEPTH; break; + default: return; // weird alien volume + } + note->volparam = v - adj; +} + +static void load_it_notetrans(song_instrument_t *instrument, struct it_notetrans *notetrans) +{ + int note, n; + for (n = 0; n < 120; n++) { + note = notetrans[n].note + NOTE_FIRST; + // map invalid notes to themselves + if (!NOTE_IS_NOTE(note)) + note = n + NOTE_FIRST; + instrument->note_map[n] = note; + instrument->sample_map[n] = notetrans[n].sample; + } +} + + + +static void load_it_pattern(song_note_t *note, slurp_t *fp, int rows) +{ + song_note_t last_note[64]; + int chan, row = 0; + uint8_t last_mask[64] = { 0 }; + uint8_t chanvar, maskvar, c; + + while (row < rows) { + chanvar = slurp_getc(fp); + + if (chanvar == 255 && slurp_eof(fp)) { + /* truncated file? we might want to complain or something ... eh. */ + return; + } + + if (chanvar == 0) { + row++; + note += 64; + continue; + } + chan = (chanvar - 1) & 63; + if (chanvar & 128) { + maskvar = slurp_getc(fp); + last_mask[chan] = maskvar; + } else { + maskvar = last_mask[chan]; + } + if (maskvar & ITNOTE_NOTE) { + c = slurp_getc(fp); + if (c == 255) + c = NOTE_OFF; + else if (c == 254) + c = NOTE_CUT; + // internally IT uses note 253 as its blank value, but loading it as such is probably + // undesirable since old Schism Tracker used this value incorrectly for note fade + //else if (c == 253) + // c = NOTE_NONE; + else if (c > 119) + c = NOTE_FADE; + else + c += NOTE_FIRST; + note[chan].note = c; + last_note[chan].note = note[chan].note; + } + if (maskvar & ITNOTE_SAMPLE) { + note[chan].instrument = slurp_getc(fp); + last_note[chan].instrument = note[chan].instrument; + } + if (maskvar & ITNOTE_VOLUME) { + it_import_voleffect(note + chan, slurp_getc(fp)); + last_note[chan].voleffect = note[chan].voleffect; + last_note[chan].volparam = note[chan].volparam; + } + if (maskvar & ITNOTE_EFFECT) { + note[chan].effect = slurp_getc(fp) & 0x1f; + note[chan].param = slurp_getc(fp); + csf_import_s3m_effect(note + chan, 1); + last_note[chan].effect = note[chan].effect; + last_note[chan].param = note[chan].param; + } + if (maskvar & ITNOTE_SAME_NOTE) + note[chan].note = last_note[chan].note; + if (maskvar & ITNOTE_SAME_SAMPLE) + note[chan].instrument = last_note[chan].instrument; + if (maskvar & ITNOTE_SAME_VOLUME) { + note[chan].voleffect = last_note[chan].voleffect; + note[chan].volparam = last_note[chan].volparam; + } + if (maskvar & ITNOTE_SAME_EFFECT) { + note[chan].effect = last_note[chan].effect; + note[chan].param = last_note[chan].param; + } + } +} + + + +static void load_it_instrument_old(song_instrument_t *instrument, slurp_t *fp) +{ + struct it_instrument_old ihdr; + int n; + + slurp_read(fp, &ihdr, sizeof(ihdr)); + + memcpy(instrument->name, ihdr.name, 25); + instrument->name[25] = '\0'; + memcpy(instrument->filename, ihdr.filename, 12); + ihdr.filename[12] = '\0'; + + instrument->nna = ihdr.nna % 4; + if (ihdr.dnc) { + // XXX is this right? + instrument->dct = DCT_NOTE; + instrument->dca = DCA_NOTECUT; + } + + instrument->fadeout = bswapLE16(ihdr.fadeout) << 6; + instrument->pitch_pan_separation = 0; + instrument->pitch_pan_center = NOTE_MIDC; + instrument->global_volume = 128; + instrument->panning = 32 * 4; //mphack + + load_it_notetrans(instrument, ihdr.notetrans); + + if (ihdr.flg & 1) + instrument->flags |= ENV_VOLUME; + if (ihdr.flg & 2) + instrument->flags |= ENV_VOLLOOP; + if (ihdr.flg & 4) + instrument->flags |= ENV_VOLSUSTAIN; + + instrument->vol_env.loop_start = ihdr.vls; + instrument->vol_env.loop_end = ihdr.vle; + instrument->vol_env.sustain_start = ihdr.sls; + instrument->vol_env.sustain_end = ihdr.sle; + instrument->vol_env.nodes = 25; + // this seems totally wrong... why isn't this using ihdr.vol_env at all? + // apparently it works, though. + for (n = 0; n < 25; n++) { + int node = ihdr.node_points[2 * n]; + if (node == 0xff) { + instrument->vol_env.nodes = n; + break; + } + instrument->vol_env.ticks[n] = node; + instrument->vol_env.values[n] = ihdr.node_points[2 * n + 1]; + } +} + + +static const uint32_t env_flags[3][4] = { + {ENV_VOLUME, ENV_VOLLOOP, ENV_VOLSUSTAIN, ENV_VOLCARRY}, + {ENV_PANNING, ENV_PANLOOP, ENV_PANSUSTAIN, ENV_PANCARRY}, + {ENV_PITCH, ENV_PITCHLOOP, ENV_PITCHSUSTAIN, ENV_PITCHCARRY}, +}; + +static uint32_t load_it_envelope(song_envelope_t *env, struct it_envelope *itenv, int envtype, int adj) +{ + uint32_t flags = 0; + int n; + + env->nodes = CLAMP(itenv->num_nodes, 2, 25); + env->loop_start = MIN(itenv->loop_start, env->nodes); + env->loop_end = CLAMP(itenv->loop_end, env->loop_start, env->nodes); + env->sustain_start = MIN(itenv->susloop_start, env->nodes); + env->sustain_end = CLAMP(itenv->susloop_end, env->sustain_start, env->nodes); + + for (n = 0; n < env->nodes; n++) { + int v = itenv->nodes[n].value + adj; + env->values[n] = CLAMP(v, 0, 64); + env->ticks[n] = bswapLE16(itenv->nodes[n].tick); + } + env->ticks[0] = 0; // sanity check + + for (n = 0; n < 4; n++) { + if (itenv->flags & (1 << n)) + flags |= env_flags[envtype][n]; + } + if (envtype == 2 && (itenv->flags & 0x80)) + flags |= ENV_FILTER; + return flags; +} + + +static void load_it_instrument(song_instrument_t *instrument, slurp_t *fp) +{ + struct it_instrument ihdr; + + slurp_read(fp, &ihdr, sizeof(ihdr)); + + memcpy(instrument->name, ihdr.name, 25); + instrument->name[25] = '\0'; + memcpy(instrument->filename, ihdr.filename, 12); + ihdr.filename[12] = '\0'; + + instrument->nna = ihdr.nna % 4; + instrument->dct = ihdr.dct % 4; + instrument->dca = ihdr.dca % 3; + instrument->fadeout = bswapLE16(ihdr.fadeout) << 5; + instrument->pitch_pan_separation = CLAMP(ihdr.pps, -32, 32); + instrument->pitch_pan_center = MIN(ihdr.ppc, 119); // I guess + instrument->global_volume = MIN(ihdr.gbv, 128); + instrument->panning = MIN((ihdr.dfp & 127), 64) * 4; //mphack + if (!(ihdr.dfp & 128)) + instrument->flags |= ENV_SETPANNING; + instrument->vol_swing = MIN(ihdr.rv, 100); + instrument->pan_swing = MIN(ihdr.rp, 64); + + instrument->ifc = ihdr.ifc; + instrument->ifr = ihdr.ifr; + + // (blah... this isn't supposed to be a mask according to the + // spec. where did this code come from? and what is 0x10000?) + instrument->midi_channel_mask = + ((ihdr.mch > 16) + ? (0x10000 + ihdr.mch) + : ((ihdr.mch > 0) + ? (1 << (ihdr.mch - 1)) + : 0)); + instrument->midi_program = ihdr.mpr; + instrument->midi_bank = bswapLE16(ihdr.midibank); + + load_it_notetrans(instrument, ihdr.notetrans); + + instrument->flags |= load_it_envelope(&instrument->vol_env, &ihdr.vol_env, 0, 0); + instrument->flags |= load_it_envelope(&instrument->pan_env, &ihdr.pan_env, 1, 32); + instrument->flags |= load_it_envelope(&instrument->pitch_env, &ihdr.pitch_env, 2, 32); +} + + +static void load_it_sample(song_sample_t *sample, slurp_t *fp, uint16_t cwtv) +{ + struct it_sample shdr; + + slurp_read(fp, &shdr, sizeof(shdr)); + + /* Fun fact: Impulse Tracker doesn't check any of the header data for consistency when loading samples + (or instruments, for that matter). If some other data is stored in place of the IMPS/IMPI, it'll + happily load it anyway -- and in fact, since the song is manipulated in memory in the same format as + on disk, this data is even preserved when the file is saved! */ + + memcpy(sample->name, shdr.name, 25); + sample->name[25] = '\0'; + + memcpy(sample->filename, shdr.filename, 12); + sample->filename[12] = '\0'; + + if (shdr.dfp & 128) { + sample->flags |= CHN_PANNING; + shdr.dfp &= 127; + } + + sample->global_volume = MIN(shdr.gvl, 64); + sample->volume = MIN(shdr.vol, 64) * 4; //mphack + sample->panning = MIN(shdr.dfp, 64) * 4; //mphack + sample->length = bswapLE32(shdr.length); + sample->length = MIN(sample->length, MAX_SAMPLE_LENGTH); + sample->loop_start = bswapLE32(shdr.loop_start); + sample->loop_end = bswapLE32(shdr.loop_end); + sample->c5speed = bswapLE32(shdr.c5speed); + sample->sustain_start = bswapLE32(shdr.susloop_start); + sample->sustain_end = bswapLE32(shdr.susloop_end); + + sample->vib_speed = MIN(shdr.vis, 64); + sample->vib_depth = MIN(shdr.vid, 32); + sample->vib_rate = shdr.vir; + sample->vib_type = autovib_import[shdr.vit % 4]; + + if (shdr.flag & 16) + sample->flags |= CHN_LOOP; + if (shdr.flag & 32) + sample->flags |= CHN_SUSTAINLOOP; + if (shdr.flag & 64) + sample->flags |= CHN_PINGPONGLOOP; + if (shdr.flag & 128) + sample->flags |= CHN_PINGPONGSUSTAIN; + + /* IT sometimes didn't clear the flag after loading a stereo sample. This appears to have + been fixed sometime before IT 2.14, which is fortunate because that's what a lot of other + programs annoyingly identify themselves as. */ + if (cwtv < 0x0214) + shdr.flag &= ~4; + + if (shdr.flag & 1) { + slurp_seek(fp, bswapLE32(shdr.sample_pointer), SEEK_SET); + + uint32_t flags = SF_LE; + flags |= (shdr.flag & 4) ? SF_SS : SF_M; + if (shdr.flag & 8) { + flags |= (shdr.cvt & 4) ? SF_IT215 : SF_IT214; + } else { + // XXX for some reason I had a note in pm/fmt/it.c saying that I had found some + // .it files with the signed flag set incorrectly and to assume unsigned when + // hdr.cwtv < 0x0202. Why, and for what files? + // Do any other players use the header for deciding sample data signedness? + flags |= (shdr.cvt & 4) ? SF_PCMD : (shdr.cvt & 1) ? SF_PCMS : SF_PCMU; + } + flags |= (shdr.flag & 2) ? SF_16 : SF_8; + csf_read_sample(sample, flags, fp->data + fp->pos, fp->length - fp->pos); + } else { + sample->length = 0; + } +} + +int fmt_it_load_song(song_t *song, slurp_t *fp, unsigned int lflags) +{ + struct it_header hdr; + uint32_t para_smp[MAX_SAMPLES], para_ins[MAX_INSTRUMENTS], para_pat[MAX_PATTERNS], para_min; + int n; + int ignoremidi = 0; + song_channel_t *channel; + song_sample_t *sample; + uint16_t hist = 0; // save history (for IT only) + const char *tid = NULL; + + slurp_read(fp, &hdr, sizeof(hdr)); + + if (memcmp(hdr.impm, "IMPM", 4) != 0) + return LOAD_UNSUPPORTED; + + hdr.ordnum = bswapLE16(hdr.ordnum); + hdr.insnum = bswapLE16(hdr.insnum); + hdr.smpnum = bswapLE16(hdr.smpnum); + hdr.patnum = bswapLE16(hdr.patnum); + hdr.cwtv = bswapLE16(hdr.cwtv); + hdr.cmwt = bswapLE16(hdr.cmwt); + hdr.flags = bswapLE16(hdr.flags); + hdr.special = bswapLE16(hdr.special); + hdr.msg_length = bswapLE16(hdr.msg_length); + hdr.msg_offset = bswapLE32(hdr.msg_offset); + hdr.reserved = bswapLE32(hdr.reserved); + + // Screwy limits? + if (hdr.ordnum > MAX_ORDERS || hdr.insnum > MAX_INSTRUMENTS + || hdr.smpnum > MAX_SAMPLES || hdr.patnum > MAX_PATTERNS) { + return LOAD_FORMAT_ERROR; + } + + for (n = 0; n < 25; n++) { + song->title[n] = hdr.title[n] ?: 32; + } + song->title[25] = 0; + rtrim_string(song->title); + + if (hdr.cwtv < 0x0214) + ignoremidi = 1; + if (hdr.special & 4) { + /* "reserved" bit, experimentally determined to indicate presence of otherwise-documented row + highlight information - introduced in IT 2.13. Formerly checked cwtv here, but that's lame :) + XXX does any tracker save highlight but *not* set this bit? (old Schism versions maybe?) */ + song->row_highlight_minor = hdr.highlight_minor; + song->row_highlight_major = hdr.highlight_major; + } else { + song->row_highlight_minor = 4; + song->row_highlight_major = 16; + } + + if (!(hdr.flags & 1)) + song->flags |= SONG_NOSTEREO; + // (hdr.flags & 2) no longer used (was vol0 optimizations) + if (hdr.flags & 4) + song->flags |= SONG_INSTRUMENTMODE; + if (hdr.flags & 8) + song->flags |= SONG_LINEARSLIDES; + if (hdr.flags & 16) + song->flags |= SONG_ITOLDEFFECTS; + if (hdr.flags & 32) + song->flags |= SONG_COMPATGXX; +#if 0 + if (hdr.flags & 64) { + midi_flags |= MIDI_PITCHBEND; + midi_pitch_depth = hdr.pwd; + } +#endif + if ((hdr.flags & 128) && !ignoremidi) + song->flags |= SONG_EMBEDMIDICFG; + else + song->flags &= ~SONG_EMBEDMIDICFG; + + song->initial_global_volume = MIN(hdr.gv, 128); + song->mixing_volume = MIN(hdr.mv, 128); + song->initial_speed = hdr.is ?: 6; + song->initial_tempo = MAX(hdr.it, 31); + song->pan_separation = hdr.sep; + + for (n = 0, channel = song->channels; n < 64; n++, channel++) { + int pan = hdr.chan_pan[n]; + if (pan & 128) { + channel->flags |= CHN_MUTE; + pan &= ~128; + } + if (pan == 100) { + channel->flags |= CHN_SURROUND; + channel->panning = 32; + } else { + channel->panning = MIN(pan, 64); + } + channel->panning *= 4; //mphack + channel->volume = MIN(hdr.chan_vol[n], 64); + } + + slurp_read(fp, song->orderlist, hdr.ordnum); + slurp_read(fp, para_ins, 4 * hdr.insnum); + slurp_read(fp, para_smp, 4 * hdr.smpnum); + slurp_read(fp, para_pat, 4 * hdr.patnum); + + para_min = ((hdr.special & 1) && hdr.msg_length) ? hdr.msg_offset : fp->length; + for (n = 0; n < hdr.insnum; n++) { + para_ins[n] = bswapLE32(para_ins[n]); + if (para_ins[n] < para_min) + para_min = para_ins[n]; + } + for (n = 0; n < hdr.smpnum; n++) { + para_smp[n] = bswapLE32(para_smp[n]); + if (para_smp[n] < para_min) + para_min = para_smp[n]; + } + for (n = 0; n < hdr.patnum; n++) { + para_pat[n] = bswapLE32(para_pat[n]); + if (para_pat[n] && para_pat[n] < para_min) + para_min = para_pat[n]; + } + + if (hdr.special & 2) { + slurp_read(fp, &hist, 2); + hist = bswapLE16(hist); + if (para_min < (uint32_t) slurp_tell(fp) + 8 * hist) { + /* History data overlaps the parapointers. Discard it, it's probably broken. + Some programs, notably older versions of Schism Tracker, set the history flag + but didn't actually write any data, so the "length" we just read is actually + some other data in the file. */ + hist = 0; + } + } else { + // History flag isn't even set. Probably an old version of Impulse Tracker. + hist = 0; + } + if (hist) { + song->histlen = hist; + song->histdata = malloc(8 * song->histlen); + slurp_read(fp, song->histdata, 8 * song->histlen); + } + if (ignoremidi) { + if (hdr.special & 8) { + log_appendf(4, " Warning: ignoring embedded MIDI data (CWTV is too old)"); + slurp_seek(fp, sizeof(midi_config_t), SEEK_CUR); + } + memset(&song->midi_config, 0, sizeof(midi_config_t)); + } else if ((hdr.special & 8) && fp->pos + sizeof(midi_config_t) <= fp->length) { + slurp_read(fp, &song->midi_config, sizeof(midi_config_t)); + } + if (!hist) { + // berotracker check + char modu[4]; + slurp_read(fp, modu, 4); + if (memcmp(modu, "MODU", 4) == 0) { + tid = "BeroTracker"; + } + } + + if ((hdr.special & 1) && hdr.msg_length && hdr.msg_offset + hdr.msg_length < fp->length) { + int len = MIN(MAX_MESSAGE, hdr.msg_length); + slurp_seek(fp, hdr.msg_offset, SEEK_SET); + slurp_read(fp, song->message, len); + song->message[len] = '\0'; + } + + + if (!(lflags & LOAD_NOSAMPLES)) { + for (n = 0; n < hdr.insnum; n++) { + song_instrument_t *inst; + + if (!para_ins[n]) + continue; + slurp_seek(fp, para_ins[n], SEEK_SET); + inst = song->instruments[n + 1] = csf_allocate_instrument(); + (hdr.cmwt >= 0x0200 ? load_it_instrument : load_it_instrument_old)(inst, fp); + } + + for (n = 0, sample = song->samples + 1; n < hdr.smpnum; n++, sample++) { + slurp_seek(fp, para_smp[n], SEEK_SET); + load_it_sample(sample, fp, hdr.cwtv); + } + } + + if (!(lflags & LOAD_NOPATTERNS)) { + for (n = 0; n < hdr.patnum; n++) { + uint16_t rows, bytes; + size_t got; + + if (!para_pat[n]) + continue; + slurp_seek(fp, para_pat[n], SEEK_SET); + slurp_read(fp, &bytes, 2); + bytes = bswapLE16(bytes); + slurp_read(fp, &rows, 2); + rows = bswapLE16(rows); + slurp_seek(fp, 4, SEEK_CUR); + song->patterns[n] = csf_allocate_pattern(rows); + song->pattern_size[n] = song->pattern_alloc_size[n] = rows; + load_it_pattern(song->patterns[n], fp, rows); + got = slurp_tell(fp) - para_pat[n] - 8; + if (bytes != got) + log_appendf(4, " Warning: Pattern %d: size mismatch" + " (expected %d bytes, got %lu)", + n, bytes, (unsigned long) got); + } + } + + + // XXX 32 CHARACTER MAX XXX + + if (tid) { + // BeroTracker (detected above) + } else if ((hdr.cwtv >> 12) == 1) { + tid = NULL; + strcpy(song->tracker_id, "Schism Tracker "); +#if 0 + ver_decode_cwtv(hdr.cwtv, song->tracker_id + strlen(song->tracker_id)); TEMP DISABLED HACK +#endif + } else if ((hdr.cwtv >> 12) == 0 && hist != 0 && hdr.reserved != 0) { + // early catch to exclude possible false positives without repeating a bunch of stuff. + } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0200 && hdr.flags == 9 && hdr.special == 0 + && hdr.highlight_major == 0 && hdr.highlight_minor == 0 + && hdr.insnum == 0 && hdr.patnum + 1 == hdr.ordnum + && hdr.gv == 128 && hdr.mv == 100 && hdr.is == 1 && hdr.sep == 128 && hdr.pwd == 0 + && hdr.msg_length == 0 && hdr.msg_offset == 0 && hdr.reserved == 0) { + // :) + tid = "OpenSPC conversion"; + } else if ((hdr.cwtv >> 12) == 5) { + tid = (hdr.reserved == 0x54504d4f) ? "OpenMPT %d.%02x" : "OpenMPT %d.%02x (compat.)"; + } else if (hdr.cwtv == 0x0888 && hdr.cmwt == 0x0888 && hdr.reserved == 0/* && hdr.ordnum == 256*/) { + // erh. + // There's a way to identify the exact version apparently, but it seems too much trouble + // (ordinarily ordnum == 256, but I have encountered at least one file for which this is NOT + // the case (trackit_r2.it by dsck) and no other trackers I know of use 0x0888) + tid = "OpenMPT 1.17+"; + } else if (hdr.cwtv == 0x0300 && hdr.cmwt == 0x0300 && hdr.reserved == 0 && hdr.ordnum == 256 && hdr.sep == 128 && hdr.pwd == 0) { + tid = "OpenMPT 1.17.02.20 - 1.17.02.25"; + } else if (hdr.cwtv == 0x0217 && hdr.cmwt == 0x0200 && hdr.reserved == 0) { + int ompt = 0; + if (hdr.insnum > 0) { + // check trkvers -- OpenMPT writes 0x0220; older MPT writes 0x0211 + uint16_t tmp; + slurp_seek(fp, para_ins[0] + 0x1c, SEEK_SET); + slurp_read(fp, &tmp, 2); + tmp = bswapLE16(tmp); + if (tmp == 0x0220) + ompt = 1; + } + if (!ompt && (memchr(hdr.chan_pan, 0xff, 64) == NULL)) { + // MPT 1.16 writes 0xff for unused channels; OpenMPT never does this + // XXX this is a false positive if all 64 channels are actually in use + // -- but then again, who would use 64 channels and not instrument mode? + ompt = 1; + } + tid = (ompt + ? "OpenMPT (compatibility mode)" + : "Modplug Tracker 1.09 - 1.16"); + } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0200 && hdr.reserved == 0) { + // instruments 560 bytes apart + tid = "Modplug Tracker 1.00a5"; + } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0202 && hdr.reserved == 0) { + // instruments 557 bytes apart + tid = "Modplug Tracker b3.3 - 1.07"; + } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0214 && hdr.reserved == 0x49424843) { + // sample data stored directly after header + // all sample/instrument filenames say "-DEPRECATED-" + // 0xa for message newlines instead of 0xd + tid = "ChibiTracker"; + } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0214 && (hdr.flags & 0x10C6) == 4 && hdr.special <= 1 && hdr.reserved == 0) { + // sample data stored directly after header + // all sample/instrument filenames say "XXXXXXXX.YYY" + tid = "CheeseTracker?"; + } else if ((hdr.cwtv >> 12) == 0) { + // Catch-all. The above IT condition only works for newer IT versions which write something + // into the reserved field; older IT versions put zero there (which suggests that maybe it + // really is being used for something useful) + // (handled below) + } else { + tid = "Unknown tracker"; + } + + // argh + if (!tid && (hdr.cwtv >> 12) == 0) { + tid = "Impulse Tracker %d.%02x"; + if (hdr.cmwt > 0x0214) { + hdr.cwtv = 0x0215; + } else if (hdr.cwtv > 0x0214) { + // Patched update of IT 2.14 (0x0215 - 0x0217 == p1 - p3) + // p4 (as found on modland) adds the ITVSOUND driver, but doesn't seem to change + // anything as far as file saving is concerned. + tid = NULL; + sprintf(song->tracker_id, "Impulse Tracker 2.14p%d", hdr.cwtv - 0x0214); + } + //"saved %d time%s", hist, (hist == 1) ? "" : "s" + } + if (tid) { + sprintf(song->tracker_id, tid, (hdr.cwtv & 0xf00) >> 8, hdr.cwtv & 0xff); + } + +// if (ferror(fp)) { +// return LOAD_FILE_ERROR; +// } + + return LOAD_SUCCESS; +} + |