view jloadxm.c @ 0:32250b436bca

Initial re-import.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 28 Sep 2012 01:54:23 +0300
parents
children aa9fbdbcea70
line wrap: on
line source

/*
 * miniJSS - Fast Tracker ][ (XM) module loader
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2006-2007 Tecnic Software productions (TNSP)
 *
 * TO DO:
 * - Add support for 1.02/1.03 XM-format versions.
 *   (Not very useful, but if it's not too hard, then do it)
 */
#include "jssmod.h"
#include <string.h>


/* XM value limit definitions
 */
#define XM_MaxChannels      (32)
#define XM_MaxPatterns      (256)
#define XM_MaxOrders        (255)
#define XM_MaxInstruments   (128)
#define XM_MaxInstSamples   (16)
#define XM_MaxEnvPoints     (12)
#define XM_MaxNotes         (96)
#define XM_MaxSampleVolume  (64)


/* XM format structures
 */
typedef struct
{
    char    idMagic[17];       // XM header ID "Extended Module: "
    char    songName[20];      // Module song name
    Uint8   unUsed1A;          // ALWAYS 0x1a
    char    trackerName[20];   // ID-string of tracker software
    Uint16  version;           // XM-version 0x0104
    Uint32  headSize;          // Module header size, FROM THIS POINT!
    Uint16  norders,           // Number of orders
            defRestartPos,     // Default song restart position
            nchannels,         // Number of channels
            npatterns,         // Number of patterns
            ninstruments,      // Number of instruments
            flags,             /* Module flags:
                                  bit0: 0 = Amiga frequency table
                                        1 = Linear frequency table
                                */
            defSpeed,          // Default speed
            defTempo;          // Default tempo
    Uint8   orderList[256];    // Order list
} XMHeader;


typedef struct
{
    Uint32  headSize;          // Instrument header size (see docs!)
    char    instName[22];      // Name/description
    Uint8   instType;          // Type
    Uint16  nsamples;          // Number of samples
} XMInstrument1;


typedef struct
{
    Uint16 frame, value;
} xm_envpoint_t;


typedef struct
{
    Uint8 flags, npoints, sustain, loopS, loopE;
    xm_envpoint_t points[XM_MaxEnvPoints];
} xm_envelope_t;


typedef struct
{
    Uint32 headSize;                  // Header size
    Uint8 sNumForNotes[XM_MaxNotes];  // Sample numbers for notes
    xm_envelope_t volumeEnv, panningEnv;
    Uint8 vibratoType, vibratoSweep, vibratoDepth, vibratoRate;

    Uint16 fadeOut, ARESERVED;
} XMInstrument2;


typedef struct
{
    Uint32 size, loopS, loopL;
    Uint8 volume;
    int fineTune;
    Uint8 type, panning;
    int relNote;
    Uint8 ARESERVED;
    char sampleName[22];
} XMSample;


typedef struct
{
    Uint32 headSize;
    Uint8 packing;
    Uint16 nrows, size;
} XMPattern;



/* Unpack XM-format pattern from file-stream into JSS-pattern structure
 */
#define JSGETBYTE(XV) do {                          \
    size--;                                         \
    if (size < 0)                                   \
    JSSERROR(DMERR_OUT_OF_DATA, DMERR_OUT_OF_DATA,  \
    "Unexpected end of packed pattern data.\n");    \
    XV = dmfgetc(inFile);                           \
} while (0)


static int jssXMUnpackPattern(DMResource *inFile, int size, JSSPattern *pattern)
{
    int packb, row, channel, tmp;
    JSSNote *pnote;
    assert(pattern != NULL);

    pnote = pattern->data;

    for (row = 0; (row < pattern->nrows) && (size > 0); row++)
    for (channel = 0; (channel < pattern->nchannels) && (size > 0); channel++)
    {
        JSGETBYTE(packb);
        if (packb & 0x80)
        {
            if (packb & 0x01)
            {
                // PACK 0x01: Read note
                JSGETBYTE(tmp);
                if (tmp < 1 || tmp > 97)
                    pnote->note = jsetNotSet;
                else if (tmp == 97)
                    pnote->note = jsetNoteOff;
                else
                    pnote->note = tmp - 1;
            }

            if (packb & 0x02)
            {
                // PACK 0x02: Read instrument
                JSGETBYTE(tmp);
                pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;
            }

            if (packb & 0x04)
            {
                // PACK 0x04: Read volume
                JSGETBYTE(tmp);
                pnote->volume = (tmp >= 0x10) ? tmp - 0x10 : jsetNotSet;
            }

            if (packb & 0x08)
            {
                // PACK 0x08: Read effect
                JSGETBYTE(pnote->effect);
                pnote->param = 0;
            }

            if (packb & 0x10)
            {
                // PACK 0x10: Read effect parameter
                JSGETBYTE(pnote->param);
                if (pnote->effect == jsetNotSet && pnote->param != 0)
                    pnote->effect = 0;
            }
        }
        else
        {
            // All data available
            tmp = (packb & 0x7f);

            if (tmp < 1 || tmp > 97)
                pnote->note = jsetNotSet;
            else if (tmp == 97)
                pnote->note = jsetNoteOff;
            else
                pnote->note = tmp - 1;

            // Get instrument
            JSGETBYTE(tmp);
            pnote->instrument = (tmp > 0) ? tmp - 1 : jsetNotSet;

            // Get volume
            JSGETBYTE(tmp);
            pnote->volume = (tmp >= 0x10) ? tmp - 0x10 : jsetNotSet;

            // Get effect
            JSGETBYTE(pnote->effect);

            // Get parameter
            JSGETBYTE(pnote->param);
            if (pnote->effect == 0 && pnote->param == 0)
                pnote->effect = pnote->param = jsetNotSet;
        }
        pnote++;
    }

    // Check the state
    if (size > 0)
    {
        // Some data left unparsed
        JSSWARNING(DMERR_EXTRA_DATA, DMERR_EXTRA_DATA,
        "Unparsed data after pattern (%i bytes), possibly broken file.\n", size);
    }

    return DMERR_OK;
}


/* Convert XM envelope structure to JSS envelope structure
 */
static int jssXMConvertEnvelope(JSSEnvelope * d, xm_envelope_t * s, char * e, int instr)
{
    int i;
    (void) e; (void) instr;

    // Convert envelope points
    d->points[0].frame = s->points[0].frame;
    d->points[0].value = s->points[0].value;
    for (i = 0; i < XM_MaxEnvPoints; i++)
    {
        d->points[i + 1].frame = s->points[i].frame + 1;
        d->points[i + 1].value = s->points[i].value;
    }
    
    // Convert other values
    d->npoints = s->npoints + 1;
    d->sustain = s->sustain + 1;
    d->loopS = s->loopS + 1;
    d->loopE = s->loopE + 1;
    
    // Check if the envelope is used
    if (s->flags & 0x01)
    {
        // Convert envelope flags
        d->flags = jenvfUsed;
        if (s->flags & 0x02)
            d->flags |= jenvfSustain;

        if (s->flags & 0x04)
            d->flags |= jenvfLooped;

        // Check other values
        if (s->npoints > XM_MaxEnvPoints)
        {
            JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Inst#%i/%s-env: nPoints > MAX, possibly broken file.\n", instr, e);
            s->npoints = XM_MaxEnvPoints;
        }

        if ((d->flags & jenvfSustain) && s->sustain > s->npoints)
        {
            JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Inst#%i/%s-env: iSustain > nPoints (%i > %i), possibly broken file.\n",
            instr, e, s->sustain, s->npoints);
            s->sustain = s->npoints;
        }

        if ((d->flags & jenvfLooped) && s->loopE > s->npoints)
        {
            JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Inst#%i/%s-env: loopE > nPoints (%i > %i), possibly broken file.\n",
            instr, e, s->loopE, s->npoints);
            s->loopE = s->npoints;
        }

        if ((d->flags & jenvfLooped) && s->loopS > s->loopE)
        {
            JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Inst#%i/%s-env: loopS > loopE (%i > %i), possibly broken file.\n",
            instr, e, s->loopS, s->loopE);
            s->loopS = 0;
        }
    }

    return DMERR_OK;
}


/* Load XM-format extended instrument from file-stream into JSS module's given inst
 */
static int jssXMLoadExtInstrument(DMResource *inFile, int ninst, JSSModule *module)
{
    XMInstrument1 xmI1;
    off_t pos, remainder;

    // Get instrument header #1
    pos = dmftell(inFile);
    dmf_read_le32(inFile, &xmI1.headSize);
    dmf_read_str(inFile, (Uint8 *) &xmI1.instName, sizeof(xmI1.instName));
    xmI1.instType = dmfgetc(inFile);
    dmf_read_le16(inFile, &xmI1.nsamples);

    // If there are samples, there is header #2
    if (xmI1.nsamples > 0)
    {
        int i, nsample, tmp;
        int xmConvTable[XM_MaxInstruments + 1];
        JSSExtInstrument *pEInst;
        JSSInstrument *pInst;
        XMInstrument2 xmI2;

        // Allocate instrument
        if ((pEInst = jssAllocateExtInstrument()) == NULL)
        {
            JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
            "Could not allocate extended instrument structure #%i\n", ninst);
        }

        module->extInstruments[ninst] = pEInst;

        // Get instrument header #2
        dmf_read_le32(inFile, &xmI2.headSize);
        dmf_read_str(inFile, (Uint8 *) &xmI2.sNumForNotes, sizeof(xmI2.sNumForNotes));

        for (i = 0; i < XM_MaxEnvPoints; i++)
        {
            dmf_read_le16(inFile, &xmI2.volumeEnv.points[i].frame);
            dmf_read_le16(inFile, &xmI2.volumeEnv.points[i].value);
        }

        for (i = 0; i < XM_MaxEnvPoints; i++)
        {
            dmf_read_le16(inFile, &xmI2.panningEnv.points[i].frame);
            dmf_read_le16(inFile, &xmI2.panningEnv.points[i].value);
        }

        xmI2.volumeEnv.npoints = dmfgetc(inFile);
        xmI2.panningEnv.npoints = dmfgetc(inFile);

        xmI2.volumeEnv.sustain = dmfgetc(inFile);
        xmI2.volumeEnv.loopS = dmfgetc(inFile);
        xmI2.volumeEnv.loopE = dmfgetc(inFile);

        xmI2.panningEnv.sustain = dmfgetc(inFile);
        xmI2.panningEnv.loopS = dmfgetc(inFile);
        xmI2.panningEnv.loopE = dmfgetc(inFile);

        xmI2.volumeEnv.flags = dmfgetc(inFile);
        xmI2.panningEnv.flags = dmfgetc(inFile);

        xmI2.vibratoType = dmfgetc(inFile);
        xmI2.vibratoSweep = dmfgetc(inFile);
        xmI2.vibratoDepth = dmfgetc(inFile);
        xmI2.vibratoRate = dmfgetc(inFile);

        dmf_read_le16(inFile, &xmI2.fadeOut);
        dmf_read_le16(inFile, &xmI2.ARESERVED);

        // Skip the extra data after header #2
        remainder = xmI1.headSize - (dmftell(inFile) - pos);
        if (remainder > 0)
        {
            JSSDEBUG("Skipping: %li\n", remainder);
            dmfseek(inFile, remainder, SEEK_CUR);
        }

        // Check and convert all ext instrument information
#ifndef JSS_LIGHT
        pEInst->desc = jssASCIItoStr(xmI1.instName, 0, sizeof(xmI1.instName));
#endif
        jssXMConvertEnvelope(&pEInst->volumeEnv, &xmI2.volumeEnv, "vol", ninst);
        jssXMConvertEnvelope(&pEInst->panningEnv, &xmI2.panningEnv, "pan", ninst);

        switch (xmI2.vibratoType)
        {
            case 0: pEInst->vibratoType = jvibSine; break;
            case 1: pEInst->vibratoType = jvibRamp; break;
            case 2: pEInst->vibratoType = jvibSquare; break;
            case 3: pEInst->vibratoType = jvibRandom; break;
            default:
                pEInst->vibratoType = jvibSine;
                JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                "Invalid extinstrument vibrato type %d for inst #%d\n", ninst);
                break;
        }
        pEInst->vibratoSweep = xmI2.vibratoSweep;
        pEInst->vibratoDepth = xmI2.vibratoDepth;
        pEInst->vibratoRate = xmI2.vibratoRate;
        pEInst->fadeOut = xmI2.fadeOut;
        pEInst->nsamples = xmI1.nsamples;

        // Initialize the SNumForNotes conversion table
        for (i = 0; i < XM_MaxInstruments; i++)
            xmConvTable[i] = jsetNotSet;

        // Read sample headers
        for (nsample = 0; nsample < xmI1.nsamples; nsample++)
        {
            XMSample xmS;

            // Read header data
            dmf_read_le32(inFile, &xmS.size);
            dmf_read_le32(inFile, &xmS.loopS);
            dmf_read_le32(inFile, &xmS.loopL);
            xmS.volume = dmfgetc(inFile);
            xmS.fineTune = (signed char) dmfgetc(inFile);
            xmS.type = dmfgetc(inFile);
            xmS.panning = dmfgetc(inFile);
            xmS.relNote = (signed char) dmfgetc(inFile);
            xmS.ARESERVED = dmfgetc(inFile);
            dmf_read_str(inFile, (Uint8 *) &xmS.sampleName, sizeof(xmS.sampleName));

            if (xmS.size > 0)
            {
                // Allocate sample instrument
                JSSDEBUG("Allocating sample #%i/%i [%i]\n",
                ninst, nsample, module->ninstruments);

                xmConvTable[nsample] = module->ninstruments;
                pInst = module->instruments[module->ninstruments] = jssAllocateInstrument();
                if (pInst == NULL)
                {
                    JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
                    "Could not allocate sample #%i/%i [%i]\n",
                    ninst, nsample, module->ninstruments);
                }
                module->ninstruments++;
            } else
                pInst = NULL;

            // Check and convert sample information
            if (pInst != NULL)
            {
                // Copy values
                if (xmS.volume > XM_MaxSampleVolume)
                {
                    JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                           "Samp #%i/%i: volume > MAX\n", ninst, nsample);
                    xmS.volume = XM_MaxSampleVolume;
                }

                pInst->volume = xmS.volume;
                pInst->ERelNote = xmS.relNote;
                pInst->EFineTune = xmS.fineTune;
                pInst->EPanning = xmS.panning;
#ifndef JSS_LIGHT
                pInst->desc = jssASCIItoStr(xmS.sampleName, 0, sizeof(xmS.sampleName));
#endif

                // Convert flags
                switch (xmS.type & 0x03)
                {
                    case 0: pInst->flags = 0; break;
                    case 1: pInst->flags = jsfLooped; break;
                    case 2: pInst->flags = jsfLooped | jsfBiDi; break;
                    default:
                        pInst->flags = 0;
                        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                        "Samp #%i/%i: Invalid sample type 0x%x\n",
                        ninst, nsample, xmS.type);
                        break;
                }

                if (xmS.type & 0x10)
                {
                    // 16-bit sample
                    JSFSET(pInst->flags, jsf16bit);

                    pInst->size = xmS.size / sizeof(Uint16);
                    pInst->loopS = xmS.loopS / sizeof(Uint16);
                    pInst->loopE = ((xmS.loopS + xmS.loopL) / sizeof(Uint16));
                }
                else
                {
                    // 8-bit sample
                    pInst->size = xmS.size;
                    pInst->loopS = xmS.loopS;
                    pInst->loopE = (xmS.loopS + xmS.loopL);
                }
                
                if (xmS.loopL == 0)
                {
                    // Always unset loop, if loop length is zero
                    JSFUNSET(pInst->flags, jsfLooped);
                }

                if (pInst->flags & jsfLooped)
                {
                    if (pInst->loopS >= pInst->size)
                    {
                        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                        "Samp #%i/%i: loopS >= size (%d >= %d)\n",
                        ninst, nsample, pInst->loopS, pInst->size);
                        JSFUNSET(pInst->flags, jsfLooped);
                    }

                    if (pInst->loopE > pInst->size)
                    {
                        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                        "Samp #%i/%i: loopE > size (%d > %d)\n",
                        ninst, nsample, pInst->loopE, pInst->size);
                        JSFUNSET(pInst->flags, jsfLooped);
                    }
                }


                // Allocate memory for sample data
                if (pInst->flags & jsf16bit)
                    pInst->data = dmCalloc(pInst->size, sizeof(Uint16));
                else
                    pInst->data = dmCalloc(pInst->size, sizeof(Uint8));

                if (pInst->data == NULL)
                    JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
                    "Could not allocate sample data #%i/%i.\n", ninst, nsample);
            }
        }

        // Read sample data
        for (nsample = 0; nsample < xmI1.nsamples; nsample++)
        if (xmConvTable[nsample] != jsetNotSet)
        {
            pInst = module->instruments[xmConvTable[nsample]];
            if (pInst)
            {
                JSSDEBUG("desc....: '%s'\n"
                     "size....: %i\n"
                     "loopS...: %i\n"
                     "loopE...: %i\n"
                     "volume..: %i\n"
                     "flags...: %x\n",
                     pInst->desc,
                     pInst->size, pInst->loopS, pInst->loopE,
                     pInst->volume, pInst->flags);

                if (pInst->flags & jsf16bit)
                {
                    // Read sampledata
                    if (dmfread(pInst->data, sizeof(Uint16), pInst->size, inFile) != (size_t) pInst->size)
                        JSSERROR(DMERR_FREAD, DMERR_FREAD,
                        "Error reading sampledata for instrument #%i/%i, %i words.\n",
                        ninst, nsample, pInst->size);

                    // Convert data
                    jssDecodeSample16((Uint16 *) pInst->data, pInst->size,
#if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
                               (jsampDelta | jsampSwapEndianess)
#else
                               (jsampDelta)
#endif
                    );
                }
                else
                {
                    // Read sampledata
                    if (dmfread(pInst->data, sizeof(Uint8), pInst->size, inFile) != (size_t) pInst->size)
                        JSSERROR(DMERR_FREAD, DMERR_FREAD,
                        "Error reading sampledata for instrument #%i/%i, %i bytes.\n",
                        ninst, nsample, pInst->size);

                    // Convert data
                    jssDecodeSample8((Uint8 *) pInst->data, pInst->size,
                        (jsampDelta | jsampFlipSign));
                }
            }
        }

        // Apply new values to sNumForNotes values
        for (i = 0; i < XM_MaxNotes; i++)
        {
            tmp = xmI2.sNumForNotes[i];
            if (tmp >= 0 && tmp < xmI1.nsamples)
                pEInst->sNumForNotes[i] = xmConvTable[tmp];
            else
            {
                pEInst->sNumForNotes[i] = jsetNotSet;
                JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
                "Ext.instrument #%d sNumForNotes[%d] out of range (%d).\n",
                ninst, i, tmp);
            }
        }
    }
    else
    {
        // We may STILL need to skip extra data after 1st instr. header
        remainder = xmI1.headSize - (dmftell(inFile) - pos);
        if (remainder > 0)
        {
            JSSDEBUG("PPM! Skipping: %li\n", remainder);
            dmfseek(inFile, remainder, SEEK_CUR);
        }
    }

    return 0;
}


/* Load XM-format module from given file-stream
 */
int jssLoadXM(DMResource *inFile, JSSModule **ppModule)
{
    JSSModule *module;
    XMHeader xmH;
    int result, index, tmp;

    assert(ppModule != NULL);
    assert(inFile != NULL);
    *ppModule = NULL;

    /* Get XM-header and check it
     */
    dmf_read_str(inFile, (Uint8 *) &xmH.idMagic, sizeof(xmH.idMagic));
    dmf_read_str(inFile, (Uint8 *) &xmH.songName, sizeof(xmH.songName));
    xmH.unUsed1A = dmfgetc(inFile);
    dmf_read_str(inFile, (Uint8 *) &xmH.trackerName, sizeof(xmH.trackerName));
    dmf_read_le16(inFile, &xmH.version);
    dmf_read_le32(inFile, &xmH.headSize);
    dmf_read_le16(inFile, &xmH.norders);
    dmf_read_le16(inFile, &xmH.defRestartPos);
    dmf_read_le16(inFile, &xmH.nchannels);
    dmf_read_le16(inFile, &xmH.npatterns);
    dmf_read_le16(inFile, &xmH.ninstruments);
    dmf_read_le16(inFile, &xmH.flags);
    dmf_read_le16(inFile, &xmH.defSpeed);
    dmf_read_le16(inFile, &xmH.defTempo);
    dmf_read_str(inFile, (Uint8 *)&xmH.orderList, sizeof(xmH.orderList));


    // Check the fields, none of these are considered fatal
    if (strncmp(xmH.idMagic, "Extended Module: ", 17) != 0)
    {
        JSSERROR(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED,
        "Not a FT2 Extended Module (XM), ident mismatch!\n");
    }

    if (xmH.version != 0x0104)
    {
        JSSWARNING(DMERR_NOT_SUPPORTED, DMERR_NOT_SUPPORTED,
        "Unsupported version of XM format 0x%04x instead of expected 0x0104.\n",
        xmH.version);
    }

    if (xmH.unUsed1A != 0x1a)
    {
        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Possibly modified or corrupted XM [%x]\n", xmH.unUsed1A);
    }

    if (xmH.norders > XM_MaxOrders)
    {
        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Number of orders %i > %i, possibly broken module.\n",
        xmH.norders, XM_MaxOrders);
        xmH.norders = XM_MaxOrders;
    }

    if (xmH.norders == 0)
    {
        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Number of orders was zero.\n");
    }

    if (xmH.npatterns > XM_MaxPatterns)
    {
        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Number of patterns %i > %i, possibly broken module.\n",
        xmH.npatterns, XM_MaxPatterns);
        xmH.npatterns = XM_MaxPatterns;
    }

    if (xmH.npatterns == 0)
    {
        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Number of patterns was zero.\n");
    }

    if (xmH.nchannels <= 0 || xmH.nchannels > XM_MaxChannels)
    {
        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Number of channels was invalid, %i (should be 1 - %i).\n",
        xmH.nchannels, XM_MaxChannels);
    }

    if (xmH.ninstruments <= 0 || xmH.ninstruments > XM_MaxInstruments)
    {
        JSSWARNING(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
        "Number of instruments was invalid, %i (should be 1 - %i).\n",
        xmH.ninstruments, XM_MaxInstruments);
    }

    /* Okay, allocate a module structure
     */
    module = jssAllocateModule();
    if (module == NULL)
    {
        JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
        "Could not allocate memory for module structure.\n");
    }
    *ppModule = module;


    // Convert and check the header data
    module->moduleType      = jmdtXM;
    module->intVersion      = xmH.version;
#ifndef JSS_LIGHT
    module->moduleName      = jssASCIItoStr(xmH.songName, 0, sizeof(xmH.songName));
    module->trackerName     = jssASCIItoStr(xmH.trackerName, 0, sizeof(xmH.trackerName));
#endif
    module->defSpeed        = xmH.defSpeed;
    module->defTempo        = xmH.defTempo;
    module->nextInstruments = xmH.ninstruments;
    module->ninstruments    = 0;
    module->npatterns       = xmH.npatterns;
    module->norders         = xmH.norders;
    module->nchannels       = xmH.nchannels;
    module->defFlags        = jmdfStereo | jmdfFT2Replay;
    module->defRestartPos   = xmH.defRestartPos;

    if ((xmH.flags & 1) == 0)
        module->defFlags |= jmdfAmigaPeriods;

    // Setup the default pannings
    for (index = 0; index < jsetNChannels; index++)
        module->defPanning[index] = jchPanMiddle;

    /* Read patterns
     */
    for (index = 0; index < module->npatterns; index++)
    {
        off_t pos;
        XMPattern xmP;

        // Get the pattern header
        dmf_read_le32(inFile, &xmP.headSize);
        xmP.packing = dmfgetc(inFile);
        dmf_read_le16(inFile, &xmP.nrows);
        dmf_read_le16(inFile, &xmP.size);
        pos = dmftell(inFile);

        // Check the header
        if (xmP.packing != 0)
            JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Pattern #%i packing type unsupported (%i)\n",
            index, xmP.packing);

        if (xmP.nrows == 0)
            JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Pattern #%i has %i rows, invalid data.\n",
            index, xmP.nrows);

        if (xmP.size > 0)
        {
            // Allocate and unpack pattern
            module->patterns[index] = jssAllocatePattern(xmP.nrows, module->nchannels);
            if (module->patterns[index] == NULL)
                JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
                "Could not allocate memory for pattern #%i\n", index);

            result = jssXMUnpackPattern(inFile, xmP.size, module->patterns[index]);
            if (result != 0)
                JSSERROR(result, result, "Error in unpacking pattern #%i data\n", index);
        }

        // Skip extra data (if the file is damaged)
        if (xmP.size > dmftell(inFile) - pos)
        {
            dmfseek(inFile, dmftell(inFile) - pos, SEEK_CUR);
        }
    }

    // Allocate the empty pattern
    module->patterns[jsetMaxPatterns] = jssAllocatePattern(64, module->nchannels);

    /* Convert song orders list by replacing nonexisting patterns
     * with pattern number jsetMaxPatterns.
     */
    for (index = 0; index < module->norders; index++)
    {
        tmp = xmH.orderList[index];
        if (tmp >= module->npatterns || module->patterns[tmp] == NULL)
            tmp = jsetMaxPatterns;

        module->orderList[index] = tmp;
    }

    /* Read instruments
     */
    for (index = 0; index < module->nextInstruments; index++)
    {
        result = jssXMLoadExtInstrument(inFile, index, module);
        if (result != 0)
            JSSERROR(result, result, "Errors while reading instrument #%i\n", index);
    }

    return DMERR_OK;
}