diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jloadxm.c	Fri Sep 28 01:54:23 2012 +0300
@@ -0,0 +1,791 @@
+/*
+ * 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;
+}