view jloadxm.c @ 510:43ea59887c69

Start work on making C64 formats encoding possible by changing DMDecodeOps to DMEncDecOps and adding fields and op enums for custom encode functions, renaming, etc. Split generic op sanity checking into a separate function in preparation for its use in generic encoding function.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 19 Nov 2012 15:06:01 +0200
parents a89500f26dde
children d4cee32e7050
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 jssXMConvertNote(int val)
{
    if (val < 1 || val > 97)
        return jsetNotSet;
    else if (val == 97)
        return jsetNoteOff;
    else
        return val - 1;
}


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);
                pnote->note = jssXMConvertNote(tmp);
            }

            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
            pnote->note = jssXMConvertNote(packb & 0x7f);

            // 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
    for (i = 0; i < XM_MaxEnvPoints; i++)
    {
        d->points[i].frame = s->points[i].frame;
        d->points[i].value = s->points[i].value;
    }
    
    // Convert other values
    d->npoints = s->npoints;
    d->sustain = s->sustain;
    d->loopS = s->loopS;
    d->loopE = s->loopE;
    
    // 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("xmI1#1 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("xmI1#2 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, remainder;
        XMPattern xmP;

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

        // 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)
        remainder = xmP.headSize - (dmftell(inFile) - pos);
        if (remainder > 0)
        {
            JSSDEBUG("xmP Skipping: %li\n", remainder);
            dmfseek(inFile, remainder, 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;
}