/*
 * 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;
} XMEnvPoint;


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


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

    Uint16 fadeOut, ARESERVED;
} XMInstrument2;


typedef struct
{
    Uint32 size, loopS, loopL;
    Uint8 volume;
    Sint8 fineTune;
    Uint8 type, panning;
    Sint8 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 (%d bytes), possibly broken file.\n", size);
    }

    return DMERR_OK;
}


/* Convert XM envelope structure to JSS envelope structure
 */
static int jssXMConvertEnvelope(JSSEnvelope * d, XMEnvelope * 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#%d/%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#%d/%s-env: iSustain > nPoints (%d > %d), 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#%d/%s-env: loopE > nPoints (%d > %d), 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#%d/%s-env: loopS > loopE (%d > %d), 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);
    if (!dmf_read_le32(inFile, &xmI1.headSize) ||
        !dmf_read_str(inFile, &xmI1.instName, sizeof(xmI1.instName)) ||
        !dmf_read_byte(inFile, &xmI1.instType) ||
        !dmf_read_le16(inFile, &xmI1.nsamples))
        return DMERR_FREAD;

    // 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 #%d\n", ninst);
        }

        module->extInstruments[ninst] = pEInst;

        // Get instrument header #2
        if (!dmf_read_le32(inFile, &xmI2.headSize) ||
            !dmf_read_str(inFile, &xmI2.sNumForNotes, sizeof(xmI2.sNumForNotes)))
        {
            JSSERROR(DMERR_FREAD, DMERR_FREAD,
            "Could not read secondary instrument header #1 for #%d.\n", ninst);
        }

        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);
        }

        if (!dmf_read_byte(inFile, &xmI2.volumeEnv.npoints) ||
            !dmf_read_byte(inFile, &xmI2.panningEnv.npoints) ||

            !dmf_read_byte(inFile, &xmI2.volumeEnv.sustain) ||
            !dmf_read_byte(inFile, &xmI2.volumeEnv.loopS) ||
            !dmf_read_byte(inFile, &xmI2.volumeEnv.loopE) ||

            !dmf_read_byte(inFile, &xmI2.panningEnv.sustain) ||
            !dmf_read_byte(inFile, &xmI2.panningEnv.loopS) ||
            !dmf_read_byte(inFile, &xmI2.panningEnv.loopE) ||

            !dmf_read_byte(inFile, &xmI2.volumeEnv.flags) ||
            !dmf_read_byte(inFile, &xmI2.panningEnv.flags) ||

            !dmf_read_byte(inFile, &xmI2.vibratoType) ||
            !dmf_read_byte(inFile, &xmI2.vibratoSweep) ||
            !dmf_read_byte(inFile, &xmI2.vibratoDepth) ||
            !dmf_read_byte(inFile, &xmI2.vibratoRate) ||

            !dmf_read_le16(inFile, &xmI2.fadeOut) ||
            !dmf_read_le16(inFile, &xmI2.ARESERVED))
        {
            JSSERROR(DMERR_FREAD, DMERR_FREAD,
            "Could not read secondary instrument header #2 for #%d.\n", ninst);
        }

        // 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
            if (!dmf_read_le32(inFile, &xmS.size) ||
                !dmf_read_le32(inFile, &xmS.loopS) ||
                !dmf_read_le32(inFile, &xmS.loopL) ||
                !dmf_read_byte(inFile, &xmS.volume) ||
                !dmf_read_byte(inFile, (Uint8 *) &xmS.fineTune) ||
                !dmf_read_byte(inFile, &xmS.type) ||
                !dmf_read_byte(inFile, &xmS.panning) ||
                !dmf_read_byte(inFile, (Uint8 *) &xmS.relNote) ||
                !dmf_read_byte(inFile, &xmS.ARESERVED) ||
                !dmf_read_str(inFile, &xmS.sampleName, sizeof(xmS.sampleName)))
            {
                JSSERROR(DMERR_FREAD, DMERR_FREAD,
                "Error reading instrument sample header #%d/%d [%d]",
                ninst, nsample, module->ninstruments);
            }

            if (xmS.size > 0)
            {
                // Allocate sample instrument
                JSSDEBUG("Allocating sample #%d/%d [%d]\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 #%d/%d [%d]\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 #%d/%d: 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 #%d/%d: 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 #%d/%d: 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 #%d/%d: 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 #%d/%d.\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....: %d\n"
                     "loopS...: %d\n"
                     "loopE...: %d\n"
                     "volume..: %d\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 #%d/%d, %d 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 #%d/%d, %d 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;

    // Try to get the XM-header
    if (!dmf_read_str(inFile, &xmH.idMagic, sizeof(xmH.idMagic)) ||
        !dmf_read_str(inFile, &xmH.songName, sizeof(xmH.songName)) ||
        !dmf_read_byte(inFile, &xmH.unUsed1A) ||
        !dmf_read_str(inFile, &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))
        return DMERR_FREAD;


    // 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 %d > %d, 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 %d > %d, 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, %d (should be 1 - %d).\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, %d (should be 1 - %d).\n",
        xmH.ninstruments, XM_MaxInstruments);
    }

    // Read orders list
    if (!dmf_read_str(inFile, &xmH.orderList, sizeof(xmH.orderList)))
    {
        JSSERROR(DMERR_FREAD, DMERR_FREAD,
        "Error reading pattern order list.\n");
    }

    /* 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);
        
        if (!dmf_read_le32(inFile, &xmP.headSize) ||
            !dmf_read_byte(inFile, &xmP.packing) ||
            !dmf_read_le16(inFile, &xmP.nrows) ||
            !dmf_read_le16(inFile, &xmP.size))
            JSSERROR(DMERR_FREAD, DMERR_FREAD,
            "Could not read pattern header data #%d.\n",
            index);
        

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

        if (xmP.nrows == 0)
            JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
            "Pattern #%d has %d 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 #%d\n", index);

            result = jssXMUnpackPattern(inFile, xmP.size, module->patterns[index]);
            if (result != 0)
                JSSERROR(result, result, "Error in unpacking pattern #%d 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 #%d\n", index);
    }

    return DMERR_OK;
}
