shithub: dumb

ref: affae6de24bf5bb8c5d9f49aa5e9c970c5f2a36b
dir: /src/it/readxm.c/

View raw version
/*  _______         ____    __         ___    ___
 * \    _  \       \    /  \  /       \   \  /   /       '   '  '
 *  |  | \  \       |  |    ||         |   \/   |         .      .
 *  |  |  |  |      |  |    ||         ||\  /|  |
 *  |  |  |  |      |  |    ||         || \/ |  |         '  '  '
 *  |  |  |  |      |  |    ||         ||    |  |         .      .
 *  |  |_/  /        \  \__//          ||    |  |
 * /_______/ynamic    \____/niversal  /__\  /____\usic   /|  .  . ibliotheque
 *                                                      /  \
 *                                                     / .  \
 * readxm.c - Code to read a Fast Tracker II          / / \  \
 *            module from an open file.              | <  /   \_
 *                                                   |  \/ /\   /
 * By Julien Cugniere. Some bits of code taken        \_  /  > /
 * from reads3m.c.                                      | \ / /
 *                                                      |  ' /
 *                                                       \__/
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "dumb.h"
#include "internal/it.h"
#include "internal/dumbfile.h"

/** TODO:

 * XM_TREMOLO                        doesn't sound quite right...
 * XM_SET_ENVELOPE_POSITION          todo.

 * VIBRATO conversion needs to be checked (sample/effect/volume). Plus check
   that effect memory is correct when using XM_VOLSLIDE_VIBRATO.
   - sample vibrato (instrument vibrato) is now handled correctly. - entheh

 * XM_E_SET_VIBRATO/TREMOLO_CONTROL: effectvalue&4 -> don't retrig wave when
   a new instrument is played. In retrigger_note()?. Is it worth implementing?

 * Lossy fadeout approximation. 0..31 converted to 0 --> won't fade at all.

 * Replace DUMB's sawtooth by ramp_down/ramp_up. Update XM loader.

 * A lot of things need to be reset when the end of the song is reached.

 * It seems that IT and XM don't behave the same way when dealing with
   mixed loops. When IT encounters multiple SBx (x>0) commands on the same
   row, it decrements the loop count for all, but only execute the loop of
   the last one (highest channel). FT2 only decrements the loop count of the
   last one. Not that I know of any modules using so convoluted combinations!

 * Maybe we could remove patterns that don't appear in the order table ? Or
   provide a function to "optimize" a DUMB_IT_SIGDATA ?

*/

#define XM_LINEAR_FREQUENCY 1 /* otherwise, use amiga slides */

#define XM_ENTRY_PACKED 128
#define XM_ENTRY_NOTE 1
#define XM_ENTRY_INSTRUMENT 2
#define XM_ENTRY_VOLUME 4
#define XM_ENTRY_EFFECT 8
#define XM_ENTRY_EFFECTVALUE 16

#define XM_NOTE_OFF 97

#define XM_ENVELOPE_ON 1
#define XM_ENVELOPE_SUSTAIN 2
#define XM_ENVELOPE_LOOP 4

#define XM_SAMPLE_NO_LOOP 0
#define XM_SAMPLE_FORWARD_LOOP 1
#define XM_SAMPLE_PINGPONG_LOOP 2
#define XM_SAMPLE_16BIT 16
#define XM_SAMPLE_STEREO 32

#define XM_VIBRATO_SINE 0
#define XM_VIBRATO_SQUARE 1
#define XM_VIBRATO_RAMP_DOWN 2
#define XM_VIBRATO_RAMP_UP 3

/* Probably useless :) */
const char xm_convert_vibrato[] = {IT_VIBRATO_SINE, IT_VIBRATO_XM_SQUARE,
                                   IT_VIBRATO_RAMP_DOWN, IT_VIBRATO_RAMP_UP,
                                   IT_VIBRATO_RANDOM};

#define XM_MAX_SAMPLES_PER_INSTRUMENT 16

/* Extra data that doesn't fit inside IT_INSTRUMENT */
typedef struct XM_INSTRUMENT_EXTRA {
    int n_samples;
    int vibrato_type;
    int vibrato_sweep; /* 0-0xFF */
    int vibrato_depth; /* 0-0x0F */
    int vibrato_speed; /* 0-0x3F */
    int sample_header_size;
} XM_INSTRUMENT_EXTRA;

/* Trims off trailing white space, usually added by the tracker on file creation
 */
static void trim_whitespace(char *ptr, size_t size) {
    char *p = ptr + size - 1;
    while (p >= ptr && *p <= 0x20)
        *p-- = '\0';
}

/* Frees the original block if it can't resize it or if size is 0, and acts
 * as malloc if ptr is NULL.
 */
static void *safe_realloc(void *ptr, size_t size) {
    if (ptr == NULL)
        return malloc(size);

    if (size == 0) {
        free(ptr);
        return NULL;
    } else {
        void *new_block = realloc(ptr, size);
        if (!new_block)
            free(ptr);
        return new_block;
    }
}

/* The interpretation of the XM volume column is left to the player. Here, we
 * just filter bad values.
 */
// This function is so tiny now, should we inline it?
static void it_xm_convert_volume(int volume, IT_ENTRY *entry) {
    entry->mask |= IT_ENTRY_VOLPAN;
    entry->volpan = volume;

    switch (volume >> 4) {
    case 0xA: /* set vibrato speed */
    case 0xB: /* vibrato */
    case 0xF: /* tone porta */
    case 0x6: /* vol slide up */
    case 0x7: /* vol slide down */
    case 0x8: /* fine vol slide up */
    case 0x9: /* fine vol slide down */
    case 0xC: /* set panning */
    case 0xD: /* pan slide left */
    case 0xE: /* pan slide right */
    case 0x1: /* set volume */
    case 0x2: /* set volume */
    case 0x3: /* set volume */
    case 0x4: /* set volume */
        break;

    case 0x5:
        if (volume == 0x50)
            break; /* set volume */
                   /* else fall through */

    default:
        entry->mask &= ~IT_ENTRY_VOLPAN;
        break;
    }
}

static int it_xm_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, int n_channels,
                              unsigned char *buffer, int version) {
    int size;
    int pos;
    int channel;
    int row;
    int effect, effectvalue;
    IT_ENTRY *entry;

    /* pattern header size */
    if (dumbfile_igetl(f) != (version == 0x0102 ? 0x08 : 0x09)) {
        TRACE("XM error: unexpected pattern header size\n");
        return -1;
    }

    /* pattern data packing type */
    if (dumbfile_getc(f) != 0) {
        TRACE("XM error: unexpected pattern packing type\n");
        return -1;
    }

    if (version == 0x0102)
        pattern->n_rows = dumbfile_getc(f) + 1;
    else
        pattern->n_rows = dumbfile_igetw(f); /* 1..256 */
    size = dumbfile_igetw(f);
    pattern->n_entries = 0;

    if (dumbfile_error(f))
        return -1;

    if (size == 0)
        return 0;

    if (size > 1280 * n_channels) {
        TRACE("XM error: pattern data size > %d bytes\n", 1280 * n_channels);
        return -1;
    }

    if (dumbfile_getnc((char *)buffer, size, f) < size)
        return -1;

    /* compute number of entries */
    pattern->n_entries = 0;
    pos = channel = row = 0;
    while (pos < size) {
        if (!(buffer[pos] & XM_ENTRY_PACKED) || (buffer[pos] & 31))
            pattern->n_entries++;

        channel++;
        if (channel >= n_channels) {
            channel = 0;
            row++;
            pattern->n_entries++;
        }

        if (buffer[pos] & XM_ENTRY_PACKED) {
            static const char offset[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2,
                                          3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3,
                                          3, 4, 2, 3, 3, 4, 3, 4, 4, 5};
            pos += 1 + offset[buffer[pos] & 31];
        } else {
            pos += 5;
        }
    }

    if (row > pattern->n_rows) {
        TRACE("XM error: wrong number of rows in pattern data\n");
        return -1;
    }

    /* Whoops, looks like some modules may be short, a few channels, maybe even
     * rows... */

    while (row < pattern->n_rows) {
        pattern->n_entries++;
        row++;
    }

    pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry));
    if (!pattern->entry)
        return -1;

    /* read the entries */
    entry = pattern->entry;
    pos = channel = row = 0;
    while (pos < size) {
        unsigned char mask;

        if (buffer[pos] & XM_ENTRY_PACKED)
            mask = buffer[pos++] & 31;
        else
            mask = 31;

        if (mask) {
            ASSERT(entry < pattern->entry + pattern->n_entries);

            entry->channel = channel;
            entry->mask = 0;

            if (mask & XM_ENTRY_NOTE) {
                int note = buffer[pos++]; /* 1-96 <=> C0-B7 */
                entry->note =
                    (note == XM_NOTE_OFF) ? (IT_NOTE_OFF) : (note - 1);
                entry->mask |= IT_ENTRY_NOTE;
            }

            if (mask & XM_ENTRY_INSTRUMENT) {
                entry->instrument = buffer[pos++]; /* 1-128 */
                entry->mask |= IT_ENTRY_INSTRUMENT;
            }

            if (mask & XM_ENTRY_VOLUME)
                it_xm_convert_volume(buffer[pos++], entry);

            effect = effectvalue = 0;
            if (mask & XM_ENTRY_EFFECT)
                effect = buffer[pos++];
            if (mask & XM_ENTRY_EFFECTVALUE)
                effectvalue = buffer[pos++];
            _dumb_it_xm_convert_effect(effect, effectvalue, entry, 0);

            entry++;
        }

        channel++;
        if (channel >= n_channels) {
            channel = 0;
            row++;
            IT_SET_END_ROW(entry);
            entry++;
        }
    }

    while (row < pattern->n_rows) {
        row++;
        IT_SET_END_ROW(entry);
        entry++;
    }

    return 0;
}

static int it_xm_make_envelope(IT_ENVELOPE *envelope,
                               const unsigned short *data, int y_offset) {
    int i, pos, val;
    if (envelope->n_nodes > 12) {
        /* XXX
        TRACE("XM error: wrong number of envelope nodes (%d)\n",
        envelope->n_nodes); envelope->n_nodes = 0; return -1; */
        envelope->n_nodes = 12;
    }

    if (envelope->sus_loop_start >= 12)
        envelope->flags &= ~IT_ENVELOPE_SUSTAIN_LOOP;
    if (envelope->loop_end >= 12)
        envelope->loop_end = 0;
    if (envelope->loop_start >= envelope->loop_end)
        envelope->flags &= ~IT_ENVELOPE_LOOP_ON;

    pos = 0;
    for (i = 0; i < envelope->n_nodes; i++) {
        envelope->node_t[i] = data[pos++];
        val = data[pos++];
        if (val > 64) {
            TRACE("XM error: out-of-range envelope node (node_y[%d]=%d)\n", i,
                  val);
            /* FT2 seems to simply clip the value */
            val = 64;
        }
        envelope->node_y[i] = (signed char)(val + y_offset);
    }

    return 0;
}

typedef struct LIMITED_XM LIMITED_XM;

struct LIMITED_XM {
    unsigned char *buffered;
    long ptr, limit, allocated;
    DUMBFILE *remaining;
};

static int limit_xm_resize(void *f, long n) {
    DUMBFILE *df = f;
    LIMITED_XM *lx = df->file;
    if (n < 0)
        return -1;
    if (lx->buffered || n) {
        if (n > lx->allocated) {
            unsigned char *buffered = realloc(lx->buffered, n);
            if (!buffered)
                return -1;
            lx->buffered = buffered;
            memset(buffered + lx->allocated, 0, n - lx->allocated);
            lx->allocated = n;
        }
        if (dumbfile_getnc((char *)lx->buffered, n, lx->remaining) < n)
            return -1;
    } else if (!n) {
        if (lx->buffered)
            free(lx->buffered);
        lx->buffered = NULL;
        lx->allocated = 0;
    }
    lx->limit = n;
    lx->ptr = 0;
    return 0;
}

static int limit_xm_skip_end(void *f, long n) {
    DUMBFILE *df = f;
    LIMITED_XM *lx = df->file;
    return dumbfile_skip(lx->remaining, n);
}

static int limit_xm_skip(void *f, dumb_off_t n) {
    LIMITED_XM *lx = f;
    lx->ptr += n;
    return 0;
}

static int limit_xm_getc(void *f) {
    LIMITED_XM *lx = f;
    if (lx->ptr >= lx->allocated) {
        return 0;
    }
    return lx->buffered[lx->ptr++];
}

static dumb_ssize_t limit_xm_getnc(char *ptr, size_t n, void *f) {
    LIMITED_XM *lx = f;
    dumb_ssize_t left;
    left = lx->allocated - lx->ptr;
    if ((dumb_ssize_t)n > left) {
        if (left > 0) {
            memcpy(ptr, lx->buffered + lx->ptr, left);
            memset(ptr + left, 0, n - left);
        } else {
            memset(ptr, 0, n);
        }
    } else {
        memcpy(ptr, lx->buffered + lx->ptr, n);
    }
    lx->ptr += n;
    return n;
}

static void limit_xm_close(void *f) {
    LIMITED_XM *lx = f;
    if (lx->buffered)
        free(lx->buffered);
    /* Do NOT close lx->remaining */
    free(f);
}

/* These two can be stubs since this implementation doesn't use seeking */
static int limit_xm_seek(void *f, dumb_off_t n) {
    USED(f);
    USED(n);
    return 1;
}

static dumb_off_t limit_xm_get_size(void *f) {
    USED(f);
    return 0;
}

DUMBFILE_SYSTEM limit_xm_dfs = {NULL,
                                limit_xm_skip,
                                limit_xm_getc,
                                limit_xm_getnc,
                                limit_xm_close,
                                limit_xm_seek,
                                limit_xm_get_size};

static DUMBFILE *dumbfile_limit_xm(DUMBFILE *f) {
    LIMITED_XM *lx = malloc(sizeof(*lx));
    lx->remaining = f;
    lx->buffered = NULL;
    lx->ptr = 0;
    lx->limit = 0;
    lx->allocated = 0;
    return dumbfile_open_ex(lx, &limit_xm_dfs);
}

static int it_xm_read_instrument(IT_INSTRUMENT *instrument,
                                 XM_INSTRUMENT_EXTRA *extra, DUMBFILE *f) {
    unsigned long size, bytes_read;
    unsigned short vol_points[24];
    unsigned short pan_points[24];
    int i, type;
    const unsigned long max_size =
        4 + 22 + 1 + 2 + 4 + 96 + 48 + 48 + 1 * 14 + 2 + 2;
    unsigned long skip_end = 0;

    /* Header size. Tends to be more than the actual size of the structure.
     * So unread bytes must be skipped before reading the first sample
     * header.
     */

    if (limit_xm_resize(f, 4) < 0)
        return -1;

    size = dumbfile_igetl(f);

    if (size == 0)
        size = max_size;
    else if (size > max_size) {
        skip_end = size - max_size;
        size = max_size;
    }

    if (limit_xm_resize(f, size - 4) < 0)
        return -1;

    dumbfile_getnc((char *)instrument->name, 22, f);
    instrument->name[22] = 0;
    trim_whitespace((char *)instrument->name, 22);
    instrument->filename[0] = 0;
    dumbfile_skip(f, 1); /* Instrument type. Should be 0, but seems random. */
    extra->n_samples = dumbfile_igetw(f);

    if (dumbfile_error(f) ||
        (unsigned int)extra->n_samples > XM_MAX_SAMPLES_PER_INSTRUMENT)
        return -1;

    bytes_read = 4 + 22 + 1 + 2;

    if (extra->n_samples) {
        /* sample header size */
        /*i = dumbfile_igetl(f);
        if (!i || i > 0x28) i = 0x28;*/
        dumbfile_skip(f, 4);
        i = 0x28;
        extra->sample_header_size = i;

        /* sample map */
        for (i = 0; i < 96; i++) {
            instrument->map_sample[i] = dumbfile_getc(f) + 1;
            instrument->map_note[i] = i;
        }

        if (dumbfile_error(f))
            return 1;

        /* volume/panning envelopes */
        for (i = 0; i < 24; i++)
            vol_points[i] = dumbfile_igetw(f);
        for (i = 0; i < 24; i++)
            pan_points[i] = dumbfile_igetw(f);

        instrument->volume_envelope.n_nodes = dumbfile_getc(f);
        instrument->pan_envelope.n_nodes = dumbfile_getc(f);

        if (dumbfile_error(f))
            return -1;

        instrument->volume_envelope.sus_loop_start = dumbfile_getc(f);
        instrument->volume_envelope.loop_start = dumbfile_getc(f);
        instrument->volume_envelope.loop_end = dumbfile_getc(f);

        instrument->pan_envelope.sus_loop_start = dumbfile_getc(f);
        instrument->pan_envelope.loop_start = dumbfile_getc(f);
        instrument->pan_envelope.loop_end = dumbfile_getc(f);

        /* The envelope handler for XM files won't use sus_loop_end. */

        type = dumbfile_getc(f);
        instrument->volume_envelope.flags = 0;
        if ((type & XM_ENVELOPE_ON) && instrument->volume_envelope.n_nodes)
            instrument->volume_envelope.flags |= IT_ENVELOPE_ON;
        if (type & XM_ENVELOPE_LOOP)
            instrument->volume_envelope.flags |= IT_ENVELOPE_LOOP_ON;
#if 1
        if (type & XM_ENVELOPE_SUSTAIN)
            instrument->volume_envelope.flags |= IT_ENVELOPE_SUSTAIN_LOOP;
#else // This is now handled in itrender.c
        /* let's avoid fading out when reaching the last envelope node */
        if (!(type & XM_ENVELOPE_LOOP)) {
            instrument->volume_envelope.loop_start =
                instrument->volume_envelope.n_nodes - 1;
            instrument->volume_envelope.loop_end =
                instrument->volume_envelope.n_nodes - 1;
        }
        instrument->volume_envelope.flags |= IT_ENVELOPE_LOOP_ON;
#endif

        type = dumbfile_getc(f);
        instrument->pan_envelope.flags = 0;
        if ((type & XM_ENVELOPE_ON) && instrument->pan_envelope.n_nodes)
            instrument->pan_envelope.flags |= IT_ENVELOPE_ON;
        if (type & XM_ENVELOPE_LOOP)
            instrument->pan_envelope.flags |=
                IT_ENVELOPE_LOOP_ON; // should this be here?
        if (type & XM_ENVELOPE_SUSTAIN)
            instrument->pan_envelope.flags |= IT_ENVELOPE_SUSTAIN_LOOP;

        if (it_xm_make_envelope(&instrument->volume_envelope, vol_points, 0) !=
            0) {
            TRACE("XM error: volume envelope\n");
            if (instrument->volume_envelope.flags & IT_ENVELOPE_ON)
                return -1;
        }
        if (it_xm_make_envelope(&instrument->pan_envelope, pan_points, -32) !=
            0) {
            TRACE("XM error: pan envelope\n");
            if (instrument->pan_envelope.flags & IT_ENVELOPE_ON)
                return -1;
        }

        instrument->pitch_envelope.flags = 0;

        extra->vibrato_type = dumbfile_getc(f);
        extra->vibrato_sweep = dumbfile_getc(f);
        extra->vibrato_depth = dumbfile_getc(f);
        extra->vibrato_speed = dumbfile_getc(f);

        if (dumbfile_error(f) || extra->vibrato_type > 4) // XXX
            return -1;

        /** WARNING: lossy approximation */
        instrument->fadeout = (dumbfile_igetw(f) * 128 + 64) / 0xFFF;

        dumbfile_skip(f, 2); /* reserved */

        bytes_read += 4 + 96 + 48 + 48 + 14 * 1 + 2 + 2;
    } else
        for (i = 0; i < 96; i++)
            instrument->map_sample[i] = 0;

    if (size > bytes_read && dumbfile_skip(f, size - bytes_read))
        return -1;

    if (skip_end && limit_xm_skip_end(f, skip_end))
        return -1;

    instrument->new_note_action = NNA_NOTE_CUT;
    instrument->dup_check_type = DCT_OFF;
    instrument->dup_check_action = DCA_NOTE_CUT;
    instrument->pp_separation = 0;
    instrument->pp_centre = 60; /* C-5 */
    instrument->global_volume = 128;
    instrument->default_pan = 32;
    instrument->random_volume = 0;
    instrument->random_pan = 0;
    instrument->filter_cutoff = 0;
    instrument->filter_resonance = 0;

    return 0;
}

/* I (entheh) have two XM files saved by a very naughty program. After a
 * 16-bit sample, it saved a rogue byte. The length of the sample was indeed
 * an odd number, incremented to include the rogue byte.
 *
 * In this function we are converting sample lengths and loop points so they
 * are measured in samples. This means we forget about the extra bytes, and
 * they don't get skipped. So we fail trying to read the next instrument.
 *
 * To get around this, this function returns the number of rogue bytes that
 * won't be accounted for by reading sample->length samples. It returns a
 * negative number on failure.
 */
static int it_xm_read_sample_header(IT_SAMPLE *sample, DUMBFILE *f) {
    int type;
    int relative_note_number; /* relative to C4 */
    int finetune;
    int roguebytes;
    int roguebytesmask;
    int reserved;

    sample->length = dumbfile_igetl(f);
    sample->loop_start = dumbfile_igetl(f);
    sample->loop_end = sample->loop_start + dumbfile_igetl(f);
    sample->global_volume = 64;
    sample->default_volume = dumbfile_getc(f);
    finetune = (signed char)dumbfile_getc(
        f); /* -128..127 <=> -1 semitone .. +127/128 of a semitone */
    type = dumbfile_getc(f);
    sample->default_pan = dumbfile_getc(f); /* 0-255 */
    relative_note_number = (signed char)dumbfile_getc(f);

    reserved = dumbfile_getc(f);

    dumbfile_getnc((char *)sample->name, 22, f);
    sample->name[22] = 0;
    trim_whitespace((char *)sample->name, 22);

    sample->filename[0] = 0;

    if (dumbfile_error(f))
        return -1;

    sample->C5_speed =
        (long)(16726.0 * pow(DUMB_SEMITONE_BASE,
                             relative_note_number) /**pow(DUMB_PITCH_BASE, )*/);
    sample->finetune = finetune * 2;

    sample->flags = IT_SAMPLE_EXISTS;

    if (reserved == 0xAD && (!(type & (XM_SAMPLE_16BIT | XM_SAMPLE_STEREO)))) {
        /* F U Olivier Lapicque */
        roguebytes = 4;
        roguebytesmask = 4 << 2;
    } else {
        roguebytes = (int)sample->length;
        roguebytesmask = 3;
    }

    if (type & XM_SAMPLE_16BIT) {
        sample->flags |= IT_SAMPLE_16BIT;
        sample->length >>= 1;
        sample->loop_start >>= 1;
        sample->loop_end >>= 1;
    } else
        roguebytesmask >>= 1;

    if (type & XM_SAMPLE_STEREO) {
        sample->flags |= IT_SAMPLE_STEREO;
        sample->length >>= 1;
        sample->loop_start >>= 1;
        sample->loop_end >>= 1;
    } else
        roguebytesmask >>= 1;

    roguebytes &= roguebytesmask;

    if ((unsigned int)sample->loop_start < (unsigned int)sample->loop_end) {
        if (type & XM_SAMPLE_FORWARD_LOOP)
            sample->flags |= IT_SAMPLE_LOOP;
        if (type & XM_SAMPLE_PINGPONG_LOOP)
            sample->flags |= IT_SAMPLE_LOOP | IT_SAMPLE_PINGPONG_LOOP;
    }

    if (sample->length <= 0)
        sample->flags &= ~IT_SAMPLE_EXISTS;
    else if ((unsigned int)sample->loop_end > (unsigned int)sample->length)
        sample->flags &= ~IT_SAMPLE_LOOP;
    else if ((unsigned int)sample->loop_start >= (unsigned int)sample->loop_end)
        sample->flags &= ~IT_SAMPLE_LOOP;

    return roguebytes;
}

static int it_xm_read_sample_data(IT_SAMPLE *sample, unsigned char roguebytes,
                                  DUMBFILE *f) {
    int old;
    long i;
    long truncated_size;
    int n_channels;
    long datasize;

    if (!(sample->flags & IT_SAMPLE_EXISTS))
        return dumbfile_skip(f, roguebytes);

    /* let's get rid of the sample data coming after the end of the loop */
    if ((sample->flags & IT_SAMPLE_LOOP) && sample->loop_end < sample->length &&
        roguebytes != 4) {
        truncated_size = sample->length - sample->loop_end;
        sample->length = sample->loop_end;
    } else {
        truncated_size = 0;
    }

    n_channels = sample->flags & IT_SAMPLE_STEREO ? 2 : 1;
    datasize = sample->length * n_channels;

    sample->data = malloc(datasize * (sample->flags & IT_SAMPLE_16BIT ? 2 : 1));
    if (!sample->data)
        return -1;

    if (roguebytes == 4) {
        if (_dumb_it_read_sample_data_adpcm4(sample, f) < 0)
            return -1;
        roguebytes = 0;
    } else {
        /* sample data is stored as signed delta values */
        old = 0;
        if (sample->flags & IT_SAMPLE_16BIT)
            for (i = 0; i < sample->length; i++)
                ((short *)sample->data)[i * n_channels] = old +=
                    dumbfile_igetw(f);
        else
            for (i = 0; i < sample->length; i++)
                ((signed char *)sample->data)[i * n_channels] = old +=
                    dumbfile_getc(f);
    }

    /* skip truncated data */
    dumbfile_skip(f, (sample->flags & IT_SAMPLE_16BIT) ? (2 * truncated_size)
                                                       : (truncated_size));

    if (sample->flags & IT_SAMPLE_STEREO) {
        old = 0;
        if (sample->flags & IT_SAMPLE_16BIT)
            for (i = 1; i < datasize; i += 2)
                ((short *)sample->data)[i] = old += dumbfile_igetw(f);
        else
            for (i = 1; i < datasize; i += 2)
                ((signed char *)sample->data)[i] = old += dumbfile_getc(f);

        /* skip truncated data */
        dumbfile_skip(f, (sample->flags & IT_SAMPLE_16BIT)
                             ? (2 * truncated_size)
                             : (truncated_size));
    }

    dumbfile_skip(f, roguebytes);

    if (dumbfile_error(f))
        return -1;

    return 0;
}

/* "Real programmers don't document. If it was hard to write,
 *  it should be hard to understand."
 *
 * (Never trust the documentation provided with a tracker.
 *  Real files are the only truth...)
 */
static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f, int *version) {
    DUMB_IT_SIGDATA *sigdata;
    char id_text[18];

    size_t header_size;
    int flags;
    int n_channels;
    int total_samples;
    int i, j;

    /* check ID text */
    if (dumbfile_getnc(id_text, 17, f) < 17)
        return NULL;
    id_text[17] = 0;
    if (strcmp(id_text, "Extended Module: ") != 0) {
        TRACE("XM error: Not an Extended Module\n");
        return NULL;
    }

    sigdata = malloc(sizeof(*sigdata));
    if (!sigdata)
        return NULL;

    /* song name */
    if (dumbfile_getnc((char *)sigdata->name, 20, f) < 20) {
        free(sigdata);
        return NULL;
    }
    sigdata->name[20] = 0;
    trim_whitespace((char *)sigdata->name, 20);

    if (dumbfile_getc(f) != 0x1A) {
        TRACE("XM error: 0x1A not found\n");
        free(sigdata);
        return NULL;
    }

    /* tracker name */
    if (dumbfile_skip(f, 20)) {
        free(sigdata);
        return NULL;
    }

    /* version number */
    *version = dumbfile_igetw(f);
    if (*version > 0x0104 || *version < 0x0102) {
        TRACE("XM error: wrong format version\n");
        free(sigdata);
        return NULL;
    }

    /*
            ------------------
            ---   Header   ---
            ------------------
    */

    /* header size */
    header_size = dumbfile_igetl(f);
    if (header_size < (4 + 2 * 8 + 1) || header_size > 0x114) {
        TRACE("XM error: unexpected header size\n");
        free(sigdata);
        return NULL;
    }

    sigdata->song_message = NULL;
    sigdata->order = NULL;
    sigdata->instrument = NULL;
    sigdata->sample = NULL;
    sigdata->pattern = NULL;
    sigdata->midi = NULL;
    sigdata->checkpoint = NULL;

    sigdata->n_samples = 0;
    sigdata->n_orders = dumbfile_igetw(f);
    sigdata->restart_position = dumbfile_igetw(f);
    n_channels = dumbfile_igetw(f); /* max 32 but we'll be lenient */
    sigdata->n_pchannels = n_channels;
    sigdata->n_patterns = dumbfile_igetw(f);
    sigdata->n_instruments = dumbfile_igetw(f);
    /* max 128 */ /* XXX upped to 256 */
    flags = dumbfile_igetw(f);
    sigdata->speed = dumbfile_igetw(f);
    if (sigdata->speed == 0)
        sigdata->speed = 6; // Should we? What about tempo?
    sigdata->tempo = dumbfile_igetw(f);

    // FT2 always clips restart position against the song length
    if (sigdata->restart_position > sigdata->n_orders)
        sigdata->restart_position = sigdata->n_orders;
    // And FT2 starts playback on order 0, regardless of length,
    // and only checks if the next order is greater than or equal
    // to this, not the current pattern. Work around this with
    // DUMB's playback core by overriding a zero length with one.
    if (sigdata->n_orders == 0)
        sigdata->n_orders = 1;

    /* sanity checks */
    // XXX
    i = (int)(header_size - 4 - 2 * 8); /* Maximum number of orders expected */
    if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_orders > i ||
        !sigdata->n_patterns || sigdata->n_patterns > 256 ||
        !sigdata->n_instruments || sigdata->n_instruments > 256 ||
        n_channels > DUMB_IT_N_CHANNELS) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    // if (sigdata->restart_position >= sigdata->n_orders)
    // sigdata->restart_position = 0;

    /* order table */
    sigdata->order = malloc(sigdata->n_orders * sizeof(*sigdata->order));
    if (!sigdata->order) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }
    dumbfile_getnc((char *)sigdata->order, sigdata->n_orders, f);
    dumbfile_skip(f, i - sigdata->n_orders);

    if (dumbfile_error(f)) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    if (*version > 0x103) {
        /*
                --------------------
                ---   Patterns   ---
                --------------------
        */

        sigdata->pattern =
            malloc(sigdata->n_patterns * sizeof(*sigdata->pattern));
        if (!sigdata->pattern) {
            _dumb_it_unload_sigdata(sigdata);
            return NULL;
        }
        for (i = 0; i < sigdata->n_patterns; i++)
            sigdata->pattern[i].entry = NULL;

        {
            unsigned char *buffer =
                malloc(1280 * n_channels); /* 256 rows * 5 bytes */
            if (!buffer) {
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }
            for (i = 0; i < sigdata->n_patterns; i++) {
                if (it_xm_read_pattern(&sigdata->pattern[i], f, n_channels,
                                       buffer, *version) != 0) {
                    free(buffer);
                    _dumb_it_unload_sigdata(sigdata);
                    return NULL;
                }
            }
            free(buffer);
        }

        /*
        -----------------------------------
        ---   Instruments and Samples   ---
        -----------------------------------
        */

        sigdata->instrument =
            malloc(sigdata->n_instruments * sizeof(*sigdata->instrument));
        if (!sigdata->instrument) {
            _dumb_it_unload_sigdata(sigdata);
            return NULL;
        }

        /* With XM, samples are not global, they're part of an instrument. In a
         * file, each instrument is stored with its samples. Because of this, I
         * don't know how to find how many samples are present in the file. Thus
         * I have to do n_instruments reallocation on sigdata->sample.
         * Looking at FT2, it doesn't seem possible to have more than 16 samples
         * per instrument (even though n_samples is stored as 2 bytes). So maybe
         * we could allocate a 128*16 array of samples, and shrink it back to
         * the correct size when we know it? Alternatively, I could allocate
         * samples by blocks of N (still O(n)), or double the number of
         * allocated samples when I need more (O(log n)).
         */
        total_samples = 0;
        sigdata->sample = NULL;

        for (i = 0; i < sigdata->n_instruments; i++) {
            XM_INSTRUMENT_EXTRA extra;

            DUMBFILE *lf = dumbfile_limit_xm(f);
            if (!lf) {
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }

            if (it_xm_read_instrument(&sigdata->instrument[i], &extra, lf) <
                0) {
                // XXX
                if (!i) {
                    TRACE("XM error: instrument %d\n", i + 1);
                    dumbfile_close(lf);
                    _dumb_it_unload_sigdata(sigdata);
                    return NULL;
                } else {
                    dumbfile_close(lf);
                    sigdata->n_instruments = i;
                    break;
                }
            }

            if (extra.n_samples) {
                unsigned char roguebytes[XM_MAX_SAMPLES_PER_INSTRUMENT];

                /* adjust instrument sample map (make indices absolute) */
                for (j = 0; j < 96; j++)
                    sigdata->instrument[i].map_sample[j] += total_samples;

                sigdata->sample = safe_realloc(
                    sigdata->sample, sizeof(*sigdata->sample) *
                                         (total_samples + extra.n_samples));
                if (!sigdata->sample) {
                    dumbfile_close(lf);
                    _dumb_it_unload_sigdata(sigdata);
                    return NULL;
                }
                for (j = total_samples; j < total_samples + extra.n_samples;
                     j++)
                    sigdata->sample[j].data = NULL;

                if (limit_xm_resize(lf, 0) < 0) {
                    dumbfile_close(lf);
                    _dumb_it_unload_sigdata(sigdata);
                    return NULL;
                }

                /* read instrument's samples */
                for (j = 0; j < extra.n_samples; j++) {
                    IT_SAMPLE *sample = &sigdata->sample[total_samples + j];
                    int b;
                    if (limit_xm_resize(lf, extra.sample_header_size) < 0) {
                        dumbfile_close(lf);
                        _dumb_it_unload_sigdata(sigdata);
                        return NULL;
                    }
                    b = it_xm_read_sample_header(sample, lf);
                    if (b < 0) {
                        dumbfile_close(lf);
                        _dumb_it_unload_sigdata(sigdata);
                        return NULL;
                    }
                    roguebytes[j] = b;
                    // Any reason why these can't be set inside
                    // it_xm_read_sample_header()?
                    sample->vibrato_speed = extra.vibrato_speed;
                    sample->vibrato_depth = extra.vibrato_depth;
                    sample->vibrato_rate = extra.vibrato_sweep;
                    /* Rate and sweep don't match, but the difference is
                     * accounted for in itrender.c.
                     */
                    sample->vibrato_waveform =
                        xm_convert_vibrato[extra.vibrato_type];
                    sample->max_resampling_quality = -1;
                }
                for (j = 0; j < extra.n_samples; j++) {
                    if (it_xm_read_sample_data(
                            &sigdata->sample[total_samples + j], roguebytes[j],
                            f) != 0) {
                        dumbfile_close(lf);
                        _dumb_it_unload_sigdata(sigdata);
                        return NULL;
                    }
                }
                total_samples += extra.n_samples;
            }

            dumbfile_close(lf);
        }

        sigdata->n_samples = total_samples;
    } else {
        // ahboy! old layout!
        // first instruments and sample headers, then patterns, then sample
        // data!

        /*
        -----------------------------------
        ---   Instruments and Samples   ---
        -----------------------------------
        */

        unsigned char *roguebytes =
            malloc(sigdata->n_instruments * XM_MAX_SAMPLES_PER_INSTRUMENT);
        if (!roguebytes) {
            _dumb_it_unload_sigdata(sigdata);
            return NULL;
        }

        sigdata->instrument =
            malloc(sigdata->n_instruments * sizeof(*sigdata->instrument));
        if (!sigdata->instrument) {
            free(roguebytes);
            _dumb_it_unload_sigdata(sigdata);
            return NULL;
        }

        total_samples = 0;
        sigdata->sample = NULL;

        for (i = 0; i < sigdata->n_instruments; i++) {
            XM_INSTRUMENT_EXTRA extra;

            DUMBFILE *lf = dumbfile_limit_xm(f);
            if (!lf) {
                free(roguebytes);
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }

            if (it_xm_read_instrument(&sigdata->instrument[i], &extra, lf) <
                0) {
                TRACE("XM error: instrument %d\n", i + 1);
                dumbfile_close(lf);
                free(roguebytes);
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }

            if (extra.n_samples) {
                /* adjust instrument sample map (make indices absolute) */
                for (j = 0; j < 96; j++)
                    sigdata->instrument[i].map_sample[j] += total_samples;

                sigdata->sample = safe_realloc(
                    sigdata->sample, sizeof(*sigdata->sample) *
                                         (total_samples + extra.n_samples));
                if (!sigdata->sample) {
                    dumbfile_close(lf);
                    free(roguebytes);
                    _dumb_it_unload_sigdata(sigdata);
                    return NULL;
                }
                for (j = total_samples; j < total_samples + extra.n_samples;
                     j++)
                    sigdata->sample[j].data = NULL;

                if (limit_xm_resize(lf, 0) < 0) {
                    dumbfile_close(lf);
                    free(roguebytes);
                    _dumb_it_unload_sigdata(sigdata);
                    return NULL;
                }

                /* read instrument's samples */
                for (j = 0; j < extra.n_samples; j++) {
                    IT_SAMPLE *sample = &sigdata->sample[total_samples + j];
                    int b;
                    if (limit_xm_resize(lf, extra.sample_header_size) < 0) {
                        dumbfile_close(lf);
                        free(roguebytes);
                        _dumb_it_unload_sigdata(sigdata);
                        return NULL;
                    }
                    b = it_xm_read_sample_header(sample, lf);
                    if (b < 0) {
                        free(roguebytes);
                        _dumb_it_unload_sigdata(sigdata);
                        return NULL;
                    }
                    roguebytes[total_samples + j] = b;
                    // Any reason why these can't be set inside
                    // it_xm_read_sample_header()?
                    sample->vibrato_speed = extra.vibrato_speed;
                    sample->vibrato_depth = extra.vibrato_depth;
                    sample->vibrato_rate = extra.vibrato_sweep;
                    /* Rate and sweep don't match, but the difference is
                     * accounted for in itrender.c.
                     */
                    sample->vibrato_waveform =
                        xm_convert_vibrato[extra.vibrato_type];
                    sample->max_resampling_quality = -1;
                }
                total_samples += extra.n_samples;
            }

            dumbfile_close(lf);
        }

        sigdata->n_samples = total_samples;

        /*
                --------------------
                ---   Patterns   ---
                --------------------
        */

        sigdata->pattern =
            malloc(sigdata->n_patterns * sizeof(*sigdata->pattern));
        if (!sigdata->pattern) {
            free(roguebytes);
            _dumb_it_unload_sigdata(sigdata);
            return NULL;
        }
        for (i = 0; i < sigdata->n_patterns; i++)
            sigdata->pattern[i].entry = NULL;

        {
            unsigned char *buffer =
                malloc(1280 * n_channels); /* 256 rows * 5 bytes */
            if (!buffer) {
                free(roguebytes);
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }
            for (i = 0; i < sigdata->n_patterns; i++) {
                if (it_xm_read_pattern(&sigdata->pattern[i], f, n_channels,
                                       buffer, *version) != 0) {
                    free(buffer);
                    free(roguebytes);
                    _dumb_it_unload_sigdata(sigdata);
                    return NULL;
                }
            }
            free(buffer);
        }

        // and now we load the sample data
        for (j = 0; j < total_samples; j++) {
            if (it_xm_read_sample_data(&sigdata->sample[j], roguebytes[j], f) !=
                0) {
                free(roguebytes);
                _dumb_it_unload_sigdata(sigdata);
                return NULL;
            }
        }

        free(roguebytes);
    }

    sigdata->flags = IT_WAS_AN_XM | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX |
                     IT_STEREO | IT_USE_INSTRUMENTS;
    // Are we OK with IT_COMPATIBLE_GXX off?
    //
    // When specifying note + instr + tone portamento, and an old note is still
    // playing (even after note off):
    // - If Compatible Gxx is on, the new note will be triggered only if the
    // instrument _changes_.
    // - If Compatible Gxx is off, the new note will always be triggered,
    // provided the instrument is specified.
    // - FT2 seems to do the latter (unconfirmed).

    // Err, wait. XM playback has its own code. The change made to the IT
    // playbackc code didn't affect XM playback. Forget this then. There's
    // still a bug in XM playback though, and it'll need some investigation...
    // tomorrow...

    // UPDATE: IT_COMPATIBLE_GXX is required to be on, so that tone porta has
    // separate memory from portamento.

    if (flags & XM_LINEAR_FREQUENCY)
        sigdata->flags |= IT_LINEAR_SLIDES;

    sigdata->global_volume = 128;
    sigdata->mixing_volume = 48;
    sigdata->pan_separation = 128;

    memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS);
    memset(sigdata->channel_pan, 32, DUMB_IT_N_CHANNELS);

    if (_dumb_it_fix_invalid_orders(sigdata) < 0) {
        _dumb_it_unload_sigdata(sigdata);
        return NULL;
    }

    return sigdata;
}

#if 0 // no fucking way, dude!

/* The length returned is the time required to play from the beginning of the
 * file to the last row of the last order (which is when the player will
 * loop). Depending on the song, the sound might stop sooner.
 * Due to fixed point roundoffs, I think this is only reliable to the second.
 * Full precision could be achieved by using a double during the computation,
 * or maybe a LONG_LONG.
 */
long it_compute_length(const DUMB_IT_SIGDATA *sigdata)
{
	IT_PATTERN *pattern;
	int tempo, speed;
	int loop_start[IT_N_CHANNELS];
	char loop_count[IT_N_CHANNELS];
	int order, entry;
	int row_first_entry = 0;
	int jump, jump_dest;
	int delay, fine_delay;
	int i;
	long t;

	if (!sigdata)
		return 0;

	tempo = sigdata->tempo;
	speed = sigdata->speed;
	order = entry = 0;
	jump = jump_dest = 0;
	t = 0;

	/* for each PATTERN */
	for (order = 0; order < sigdata->n_orders; order++) {

		if (sigdata->order[order] == IT_ORDER_END) break;
		if (sigdata->order[order] == IT_ORDER_SKIP) continue;

		for (i = 0; i < IT_N_CHANNELS; i++)
			loop_count[i] = -1;

		pattern = &sigdata->pattern[ sigdata->order[order] ];
		entry = 0;
		if (jump == IT_BREAK_TO_ROW) {
			int row = 0;
			while (row < jump_dest)
				if (pattern->entry[entry++].channel >= IT_N_CHANNELS)
					row++;
		}

		/* for each ROW */
		while (entry < pattern->n_entries) {
			row_first_entry = entry;
			delay = fine_delay = 0;
			jump = 0;

			/* for each note NOTE */
			while (entry < pattern->n_entries && pattern->entry[entry].channel < IT_N_CHANNELS) {
				int value   = pattern->entry[entry].effectvalue;
				int channel = pattern->entry[entry].channel;

				switch (pattern->entry[entry].effect) {

					case IT_SET_SPEED: speed = value; break;

					case IT_JUMP_TO_ORDER:
						if (value <= order) /* infinite loop */
							return 0;
						jump = IT_JUMP_TO_ORDER;
						jump_dest = value;
						break;

					case IT_BREAK_TO_ROW:
						jump = IT_BREAK_TO_ROW;
						jump_dest = value;
						break;

					case IT_S:
						switch (HIGH(value)) {
							case IT_S_PATTERN_DELAY:      delay      = LOW(value); break;
							case IT_S_FINE_PATTERN_DELAY: fine_delay = LOW(value); break;
							case IT_S_PATTERN_LOOP:
								if (LOW(value) == 0) {
									loop_start[channel] = row_first_entry;
								} else {
									if (loop_count[channel] == -1)
										loop_count[channel] = LOW(value);

									if (loop_count[channel]) {
										jump = IT_S_PATTERN_LOOP;
										jump_dest = loop_start[channel];
									}
									loop_count[channel]--;
								}
								break;
						}
						break;

					case IT_SET_SONG_TEMPO:
						switch (HIGH(value)) { /* slides happen every non-row frames */
							case 0:  tempo = tempo - LOW(value)*(speed-1); break;
							case 1:  tempo = tempo + LOW(value)*(speed-1); break;
							default: tempo = value;
						}
						tempo = MID(32, tempo, 255);
						break;
				}

				entry++;
			}

			/* end of ROW */
			entry++;
			t += TICK_TIME_DIVIDEND * (speed*(1+delay) + fine_delay) / tempo;

			if (jump == IT_JUMP_TO_ORDER) {
				order = jump_dest - 1;
				break;
			} else if (jump == IT_BREAK_TO_ROW)
				break;
			else if (jump == IT_S_PATTERN_LOOP)
				entry = jump_dest - 1;
		}

		/* end of PATTERN */
	}

	return t;
}

#endif /* 0 */

static char hexdigit(int in) {
    if (in < 10)
        return in + '0';
    else
        return in + 'A' - 10;
}

DUH *dumb_read_xm_quick(DUMBFILE *f) {
    sigdata_t *sigdata;
    int ver;

    DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it;

    sigdata = it_xm_load_sigdata(f, &ver);

    if (!sigdata)
        return NULL;

    {
        char version[16];
        const char *tag[2][2];
        tag[0][0] = "TITLE";
        tag[0][1] = (const char *)(((DUMB_IT_SIGDATA *)sigdata)->name);
        tag[1][0] = "FORMAT";
        version[0] = 'X';
        version[1] = 'M';
        version[2] = ' ';
        version[3] = 'v';
        version[4] = hexdigit((ver >> 8) & 15);
        version[5] = '.';
        version[6] = hexdigit((ver >> 4) & 15);
        version[7] = hexdigit(ver & 15);
        version[8] = 0;
        tag[1][1] = (const char *)version;
        return make_duh(-1, 2, (const char *const(*)[2])tag, 1, &descptr,
                        &sigdata);
    }
}