diff xm2jss.c @ 0:32250b436bca

Initial re-import.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 28 Sep 2012 01:54:23 +0300
parents
children c42ee907de9c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xm2jss.c	Fri Sep 28 01:54:23 2012 +0300
@@ -0,0 +1,997 @@
+/*
+ * xm2jss - Convert XM module to JSSMOD
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2006-2009 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#include <stdio.h>
+#include <errno.h>
+#include "jss.h"
+#include "jssmod.h"
+#include "dmlib.h"
+#include "dmargs.h"
+#include "dmres.h"
+
+
+char  *srcFilename = NULL, *destFilename = NULL;
+BOOL  optIgnoreErrors = FALSE,
+      optStripExtInstr = FALSE,
+      optStripInstr = FALSE,
+      optStripSamples = FALSE,
+      optOptimize = FALSE;
+
+int   optPatternMode = PATMODE_COMP_HORIZ,
+      optSampMode16 = jsampDelta,
+      optSampMode8 = jsampFlipSign | jsampDelta;
+
+#define SAMPMODE_MASK (jsampFlipSign | jsampSwapEndianess | jsampSplit | jsampDelta)
+
+
+static const char* patModeTable[PATMODE_LAST] =
+{
+    "Raw horizontal",
+    "Compressed horizontal (similar to XM modules)",
+    "Raw vertical",
+    "Compressed vertical",
+    "Raw vertical for each element",
+};
+
+
+DMOptArg optList[] = {
+    { 0, '?', "help",           "Show this help", OPT_NONE },
+    { 5, 'v', "verbose",        "Be more verbose", OPT_NONE },
+    { 6, 'o', "output",         "Output file", OPT_ARGREQ },
+    { 1, 'i', "ignore",         "Ignore errors", OPT_NONE },
+    { 2, 'p', "patterns",       "Pattern storage mode", OPT_ARGREQ },
+    { 3, 'E', "strip-ext-instr","Strip ext. instruments (implies -I -S)", OPT_NONE },
+    { 9, 'I', "strip-instr",    "Strip instruments (implies -S)", OPT_NONE },
+    { 4, 'S', "strip-samples",  "Strip instr. sampledata", OPT_NONE },
+    { 7, '8', "smode8",         "8-bit sample conversion flags", OPT_ARGREQ },
+    { 8, '1', "smode16",        "16-bit sample conversion flags", OPT_ARGREQ },
+    {10, 'O', "optimize",       "Optimize module", OPT_NONE },
+};
+
+const int optListN = sizeof(optList) / sizeof(optList[0]);
+
+
+void argShowHelp()
+{
+    int i;
+
+    dmPrintBanner(stdout, dmProgName, "[options] <module.xm>");
+    dmArgsPrintHelp(stdout, optList, optListN);
+
+    printf("\n"
+    "Pattern storage modes:\n");
+    
+    for (i = 1; i < PATMODE_LAST; i++)
+        printf("  %d = %s\n", i, patModeTable[i-1]);
+    
+    printf(
+    "\n"
+    "Sample data conversion flags (summative):\n"
+    "  1 = Delta encoding (DEF 8 & 16)\n"
+    "  2 = Flip signedness (DEF 8)\n"
+    "  4 = Swap endianess (affects 16-bit only)\n"
+    "  8 = Split and de-interleave hi/lo bytes (affects 16-bit only)\n"
+    "\n"
+    );
+}
+
+BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
+{
+    (void) optArg;
+    
+    switch (optN)
+    {
+    case 0:
+        argShowHelp();
+        exit(0);
+        break;
+
+    case 1:
+        optIgnoreErrors = TRUE;
+        break;
+
+    case 2:
+        optPatternMode = atoi(optArg);
+        if (optPatternMode <= 0 || optPatternMode >= PATMODE_LAST)
+        {
+            dmError("Unknown pattern conversion mode %d\n", optPatternMode);
+            return FALSE;
+        }
+        break;
+
+    case 7: optSampMode8 = atoi(optArg) & SAMPMODE_MASK; break;
+    case 8: optSampMode16 = atoi(optArg) & SAMPMODE_MASK; break;
+
+    case 4: optStripSamples = TRUE; break;
+    case 3: optStripExtInstr = TRUE; break;
+    case 9: optStripInstr = TRUE; break;
+    case 10: optOptimize = TRUE; break;
+
+    case 5:
+        dmVerbosity++;
+        break;
+    
+    case 6:
+        destFilename = optArg;
+        break;
+
+    default:
+        dmError("Unknown argument '%s'.\n", currArg);
+        return FALSE;
+    }
+    
+    return TRUE;
+}
+
+
+BOOL argHandleFile(char *currArg)
+{
+    // Was not option argument
+    if (!srcFilename)
+        srcFilename = currArg;
+    else
+    {
+        dmError("Gay error '%s'.\n", currArg);
+        return FALSE;
+    }
+    
+    return TRUE;
+}
+
+
+/* These functions and the macro mess are meant to make the
+ * conversion routines themselves clearer and simpler.
+ */
+BOOL jsPutByte(Uint8 *patBuf, size_t patBufSize, size_t *npatBuf, Uint8 val)
+{
+    if (*npatBuf >= patBufSize)
+        return FALSE;
+    else
+    {
+        patBuf[*npatBuf] = val;
+        (*npatBuf)++;
+        return TRUE;
+    }
+}
+
+#define JSPUTBYTE(x) do { if (!jsPutByte(patBuf, patBufSize, patSize, x)) return DMERR_BOUNDS; } while (0)
+
+#define JSCOMP(x,z) do { if ((x) != jsetNotSet) { qflags |= (z); qcomp++; } } while (0)
+
+#define JSCOMPPUT(xf,xv,qv)  do {                       \
+    if (qflags & (xf)) {                                \
+        if ((xv) < 0 || (xv) > 255)                     \
+            JSSERROR(DMERR_BOUNDS, DMERR_BOUNDS,        \
+            "%s value out of bounds %d.\n", qv, (xv));  \
+        JSPUTBYTE(xv);                                  \
+    }                                                   \
+} while (0)
+
+#define JSCONVPUT(xv,qv)    do {                        \
+    if ((xv) != jsetNotSet) {                           \
+        if ((xv) < 0 || (xv) > 254)                     \
+            JSSERROR(DMERR_BOUNDS, DMERR_BOUNDS,        \
+            "%s value out of bounds %d.\n", qv, (xv));  \
+        JSPUTBYTE((xv) + 1);                            \
+    } else {                                            \
+        JSPUTBYTE(0);                                   \
+    }                                                   \
+} while (0)
+
+
+/* Convert a note
+ */
+static int jssConvertNote(Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSNote *note)
+{
+    Uint8 tmp;
+    if (note->note == jsetNotSet)
+        tmp = 0;
+    else if (note->note == jsetNoteOff)
+        tmp = 127;
+    else
+        tmp = note->note + 1;
+
+    if (tmp > 0x7f)
+        JSSERROR(DMERR_BOUNDS, DMERR_BOUNDS, "Note value out of bounds %d > 0x7f.\n", tmp);
+
+    JSPUTBYTE(tmp & 0x7f);
+    
+    JSCONVPUT(note->instrument, "Instrument");
+    JSCONVPUT(note->volume, "Volume");
+    JSCONVPUT(note->effect, "Effect");
+    
+    tmp = (note->param != jsetNotSet) ? note->param : 0;
+    JSPUTBYTE(tmp);
+    
+    return DMERR_OK;
+}
+
+
+/* Compress a note
+ */
+static int jssCompressNote(Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSNote *note)
+{
+    Uint8 qflags = 0;
+    int qcomp = 0;
+    
+    JSCOMP(note->note,       COMP_NOTE);
+    JSCOMP(note->instrument, COMP_INSTRUMENT);
+    JSCOMP(note->volume,     COMP_VOLUME);
+    JSCOMP(note->effect,     COMP_EFFECT);
+    if (note->param != jsetNotSet && note->param != 0)
+    {
+        qflags |= COMP_PARAM;
+        qcomp++;
+    }
+    
+    if (qcomp < 4)
+    {
+        JSPUTBYTE(qflags | 0x80);
+        
+        if (note->note != jsetNotSet)
+        {
+            Uint8 tmp = (note->note != jsetNoteOff) ? note->note : 127;
+            if (tmp > 0x7f)
+                JSSERROR(DMERR_BOUNDS, DMERR_BOUNDS, "Note value out of bounds %d > 0x7f.\n", tmp);
+            JSPUTBYTE(tmp);
+        }
+        
+        JSCOMPPUT(COMP_INSTRUMENT, note->instrument, "Instrument");
+        JSCOMPPUT(COMP_VOLUME, note->volume, "Volume");
+        JSCOMPPUT(COMP_EFFECT, note->effect, "Effect");
+        JSCOMPPUT(COMP_PARAM, note->param, "Param");
+    } else
+        return jssConvertNote(patBuf, patBufSize, patSize, note);
+    
+    return DMERR_OK;
+}
+
+
+/* Compress pattern
+ */
+static int jssConvertPatternCompHoriz(Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSPattern *pattern)
+{
+    int row, channel;
+    *patSize = 0;
+    
+    for (row = 0; row < pattern->nrows; row++)
+    for (channel = 0; channel < pattern->nchannels; channel++)
+    {
+        const JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
+        const int res = jssCompressNote(patBuf, patBufSize, patSize, note);
+        if (res != DMERR_OK)
+        {
+            JSSERROR(res, res, "Note compression failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
+            patBuf, patBufSize, *patSize, row, channel);
+            return res;
+        }
+    }
+    
+    return DMERR_OK;
+}
+
+
+static int jssConvertPatternCompVert(Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSPattern *pattern)
+{
+    int row, channel;
+    *patSize = 0;
+    
+    for (channel = 0; channel < pattern->nchannels; channel++)
+    for (row = 0; row < pattern->nrows; row++)
+    {
+        const JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
+        const int res = jssCompressNote(patBuf, patBufSize, patSize, note);
+        if (res != DMERR_OK)
+        {
+            JSSERROR(res, res, "Note compression failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
+            patBuf, patBufSize, *patSize, row, channel);
+            return res;
+        }
+    }
+    
+    return DMERR_OK;
+}
+
+
+/* Convert a pattern
+ */
+static int jssConvertPatternRawHoriz(Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSPattern *pattern)
+{
+    int row, channel;
+    *patSize = 0;
+    
+    for (row = 0; row < pattern->nrows; row++)
+    for (channel = 0; channel < pattern->nchannels; channel++)
+    {
+        const JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
+        const int res = jssConvertNote(patBuf, patBufSize, patSize, note);
+        if (res != DMERR_OK)
+        {
+            JSSERROR(res, res, "Note conversion failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
+            patBuf, patBufSize, *patSize, row, channel);
+            return res;
+        }
+    }
+    
+    return DMERR_OK;
+}
+
+
+static int jssConvertPatternRawVert(Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSPattern *pattern)
+{
+    int row, channel;
+    *patSize = 0;
+    
+    for (channel = 0; channel < pattern->nchannels; channel++)
+    for (row = 0; row < pattern->nrows; row++)
+    {
+        const JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
+        const int res = jssConvertNote(patBuf, patBufSize, patSize, note);
+        if (res != DMERR_OK)
+        {
+            JSSERROR(res, res, "Note conversion failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
+            patBuf, patBufSize, *patSize, row, channel);
+            return res;
+        }
+    }
+    
+    return DMERR_OK;
+}
+
+
+#define JSFOREACHNOTE1                                                              \
+  for (channel = 0; channel < pattern->nchannels; channel++)                        \
+  for (row = 0; row < pattern->nrows; row++) {                                      \
+  const JSSNote *note = &pattern->data[(pattern->nchannels * row) + channel];
+
+#define JSFOREACHNOTE2 }
+
+static int jssConvertPatternRawElem(Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSPattern *pattern)
+{
+    Uint8 tmp;
+    int row, channel;
+    *patSize = 0;
+    
+    JSFOREACHNOTE1;
+    if (note->note == jsetNotSet)
+        tmp = 0;
+    else if (note->note == jsetNoteOff)
+        tmp = 127;
+    else
+        tmp = note->note + 1;
+    if (tmp > 0x7f)
+        JSSERROR(DMERR_BOUNDS, DMERR_BOUNDS, "Note value out of bounds %d > 0x7f.\n", tmp);
+    JSPUTBYTE(tmp);
+    JSFOREACHNOTE2;
+    
+    JSFOREACHNOTE1;
+    JSCONVPUT(note->instrument, "Instrument");
+    JSFOREACHNOTE2;
+    
+    JSFOREACHNOTE1;
+    JSCONVPUT(note->volume, "Volume");
+    JSFOREACHNOTE2;
+    
+    JSFOREACHNOTE1;
+    JSCONVPUT(note->effect, "Effect");
+    JSFOREACHNOTE2;
+    
+    JSFOREACHNOTE1;
+    JSCONVPUT(note->param, "Param");
+    JSFOREACHNOTE2;
+    
+    return DMERR_OK;
+}
+
+#undef JSFOREACHNOTE1
+#undef JSFOREACHNOTE2
+
+
+static void jssCopyEnvelope(JSSMODEnvelope *je, JSSEnvelope *e)
+{
+    int i;
+    
+    je->flags   = e->flags;
+    je->npoints = e->npoints;
+    je->sustain = e->sustain;
+    je->loopS   = e->loopS;
+    je->loopE   = e->loopE;
+    
+    for (i = 0; i < e->npoints; i++)
+    {
+        je->points[i].frame = e->points[i].frame;
+        je->points[i].value = e->points[i].value;
+    }
+}
+
+
+/* Save a JSSMOD file
+ */
+int jssSaveJSSMOD(FILE *outFile, JSSModule *m, int patMode, int flags8, int flags16)
+{
+    JSSMODHeader jssH;
+    int i, pattern, order, instr;
+    const size_t patBufSize = 64*1024; // 64kB pattern buffer
+    Uint8 *patBuf;
+
+    // Check the module
+    if (m == NULL)
+        JSSERROR(DMERR_NULLPTR, DMERR_NULLPTR, "Module pointer was NULL\n");
+
+    if ((m->nchannels < 1) || (m->npatterns < 1) || (m->norders < 1))
+        JSSERROR(DMERR_BOUNDS, DMERR_BOUNDS,
+        "Module had invalid values (nchannels=%i, npatterns=%i, norders=%i)\n",
+        m->nchannels, m->npatterns, m->norders);
+
+    // Create the JSSMOD header
+    dmMsg(2," * Writing JSSMOD-header 0x%04x.\n", JSSMOD_VERSION);
+    jssH.idMagic[0]         = 'J';
+    jssH.idMagic[1]         = 'M';
+    jssH.idVersion          = JSSMOD_VERSION;
+    jssH.norders            = m->norders;
+    jssH.npatterns          = m->npatterns;
+    jssH.nchannels          = m->nchannels;
+    jssH.nextInstruments    = m->nextInstruments;
+    jssH.ninstruments       = m->ninstruments;
+    jssH.defFlags           = m->defFlags;
+    jssH.intVersion         = m->intVersion;
+    jssH.defRestartPos      = m->defRestartPos;
+    jssH.defSpeed           = m->defSpeed;
+    jssH.defTempo           = m->defTempo;
+    jssH.patMode            = patMode;
+    
+    // Write header
+    if (fwrite(&jssH, sizeof(jssH), 1, outFile) != 1)
+        JSSERROR(DMERR_FWRITE, DMERR_FWRITE, "Could not write JSSMOD header!\n");
+
+    // Write orders list
+    dmMsg(2," * Writing %d item orders list.\n", m->norders);
+    for (order = 0; order < m->norders; order++)
+    {
+        Uint16 tmp = m->orderList[order];
+        if (fwrite(&tmp, sizeof(tmp), 1, outFile) != 1)
+            JSSERROR(DMERR_FWRITE, DMERR_FWRITE, "Could not write JSSMOD orders list.\n");
+    }
+
+    // Allocate pattern compression buffer
+    if ((patBuf = dmMalloc(patBufSize)) == NULL)
+        JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
+        "Error allocating memory for pattern compression buffer.\n");
+    
+    // Write patterns
+    dmMsg(2," * Writing %d patterns.\n", m->npatterns);
+    for (pattern = 0; pattern < m->npatterns; pattern++)
+    {
+        JSSMODPattern patHead;
+        size_t finalSize = 0;
+        i = -1;
+        
+        switch (patMode)
+        {
+            case PATMODE_RAW_HORIZ:
+                i = jssConvertPatternRawHoriz(patBuf, patBufSize, &finalSize, m->patterns[pattern]);
+                break;
+            case PATMODE_COMP_HORIZ:
+                i = jssConvertPatternCompHoriz(patBuf, patBufSize, &finalSize, m->patterns[pattern]);
+                break;
+            case PATMODE_RAW_VERT:
+                i = jssConvertPatternRawVert(patBuf, patBufSize, &finalSize, m->patterns[pattern]);
+                break;
+            case PATMODE_COMP_VERT:
+                i = jssConvertPatternCompVert(patBuf, patBufSize, &finalSize, m->patterns[pattern]);
+                break;
+            case PATMODE_RAW_ELEM:
+                i = jssConvertPatternRawElem(patBuf, patBufSize, &finalSize, m->patterns[pattern]);
+                break;
+            default:
+                dmFree(patBuf);
+                JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
+                "Unsupported pattern conversion mode %d.\n", patMode);
+                break;
+        }
+
+        if (i != DMERR_OK)
+        {
+            dmFree(patBuf);
+            JSSERROR(i, i, "Error converting pattern data #%i\n", pattern);
+        }
+        else
+        {
+            dmMsg(3, " - Pattern %d size %d bytes\n", pattern, finalSize);
+            patHead.nrows = m->patterns[pattern]->nrows;
+            patHead.size = finalSize;
+
+            if (fwrite(&patHead, sizeof(patHead), 1, outFile) != 1)
+            {
+                dmFree(patBuf);
+                JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
+                "Error writing pattern #%d header\n", pattern);
+            }
+
+            if (fwrite(patBuf, sizeof(Uint8), finalSize, outFile) != finalSize)
+            {
+                dmFree(patBuf);
+                JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
+                "Error writing pattern #%d data\n", pattern);
+            }
+        }
+    }
+    
+    dmFree(patBuf);
+
+    // Write extended instruments
+    dmMsg(2," * Writing %d Extended Instruments.\n", m->nextInstruments);
+    for (instr = 0; instr < m->nextInstruments; instr++)
+    {
+        JSSMODExtInstrument jssE;
+        JSSExtInstrument *einst = m->extInstruments[instr];
+        
+        memset(&jssE, 0, sizeof(jssE));
+
+        if (einst)
+        {
+            // Create header
+            jssE.nsamples = einst->nsamples;
+            for (i = 0; i < jsetNNotes; i++)
+            {
+                int snum = einst->sNumForNotes[i];
+                jssE.sNumForNotes[i] = (snum != jsetNotSet) ? snum : 0;
+            }
+            
+            jssCopyEnvelope(&jssE.volumeEnv, &(einst->volumeEnv));
+            jssCopyEnvelope(&jssE.panningEnv, &(einst->panningEnv));
+            jssE.vibratoType  = einst->vibratoType;
+            jssE.vibratoSweep = einst->vibratoSweep;
+            jssE.vibratoDepth = einst->vibratoDepth;
+            jssE.fadeOut      = einst->fadeOut;
+        } else
+            JSSWARNING(DMERR_NULLPTR, DMERR_NULLPTR, "Extended instrument #%i NULL!\n", instr);
+        
+        // Write to file
+        if (fwrite(&jssE, sizeof(jssE), 1, outFile) != 1)
+            JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
+            "Could not write JSSMOD extended instrument #%i to file!\n", instr);
+    }
+
+
+    // Write sample instrument headers
+    dmMsg(2," * Writing %d Instrument headers.\n", m->ninstruments);
+    for (instr = 0; instr < m->ninstruments; instr++)
+    {
+        JSSMODInstrument jssI;
+        JSSInstrument *pInst = m->instruments[instr];
+
+        memset(&jssI, 0, sizeof(jssI));
+
+        // Create header
+        if (pInst)
+        {
+            jssI.size         = pInst->size;
+            jssI.loopS        = pInst->loopS;
+            jssI.loopE        = pInst->loopE;
+            jssI.volume       = pInst->volume;
+            jssI.flags        = pInst->flags;
+            jssI.C4BaseSpeed  = pInst->C4BaseSpeed;
+            jssI.ERelNote     = pInst->ERelNote;
+            jssI.EFineTune    = pInst->EFineTune;
+            jssI.EPanning     = pInst->EPanning;
+            jssI.hasData      = (pInst->data != NULL) ? TRUE : FALSE;
+            jssI.convFlags = (pInst->flags & jsf16bit) ? flags16 : flags8;
+        }
+        else 
+            JSSWARNING(DMERR_NULLPTR, DMERR_NULLPTR, "Instrument #%i NULL!\n", instr);
+        
+        // Write to file
+        if (fwrite(&jssI, sizeof(jssI), 1, outFile) != 1)
+            JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
+             "Could not write JSSMOD instrument #%i to file!\n", instr);
+    }
+    
+    // Write sample data
+    dmMsg(2," * Writing %d samples.\n", m->ninstruments);
+    for (instr = 0; instr < m->ninstruments; instr++)
+    if (m->instruments[instr])
+    {
+        JSSInstrument *inst = m->instruments[instr];
+        if (inst->data != NULL)
+        {
+            size_t res;
+            if (inst->flags & jsf16bit)
+            {
+                jssEncodeSample16(inst->data, inst->size, flags16);
+                res = fwrite(inst->data, sizeof(Uint16), inst->size, outFile);
+            }
+            else
+            {
+                jssEncodeSample8(inst->data, inst->size, flags8);
+                res = fwrite(inst->data, sizeof(Uint8), inst->size, outFile);
+            }
+
+            if (res != inst->size)
+                JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
+                 "Could not write JSSMOD sample #%i to file!\n", instr);
+        }
+    }
+    
+    return DMERR_OK;
+}
+
+
+/* Optimize a given module
+ */
+JSSModule *optimizeModule(JSSModule *m)
+{
+    BOOL usedPatterns[jsetMaxPatterns + 1],
+         usedInstruments[jsetMaxInstruments + 1],
+         usedExtInstruments[jsetMaxInstruments + 1];
+    int  mapExtInstruments[jsetMaxInstruments + 1],
+         mapInstruments[jsetMaxInstruments + 1],
+         mapPatterns[jsetMaxPatterns + 1];
+    JSSModule *r = NULL;
+    int i, n8, n16;
+
+    // Allocate a new module
+    if ((r = jssAllocateModule()) == NULL)
+        return NULL;
+
+    // Allocate tables
+    
+    // Copy things
+    r->moduleType       = m->moduleType;
+    r->moduleName       = dm_strdup(m->moduleName);
+    r->trackerName      = dm_strdup(m->trackerName);
+    r->defSpeed         = m->defSpeed;
+    r->defTempo         = m->defTempo;
+    r->defFlags         = m->defFlags;
+    r->defRestartPos    = m->defRestartPos;
+    r->intVersion       = m->intVersion;
+    r->nchannels        = m->nchannels;
+    r->norders          = m->norders;
+    for (i = 0; i < jsetNChannels; i++)
+        r->defPanning[i] = m->defPanning[i];
+    
+    // Initialize values
+    for (i = 0; i <= jsetMaxInstruments; i++)
+    {
+        usedExtInstruments[i] = FALSE;
+        usedInstruments[i] = FALSE;
+        mapExtInstruments[i] = jsetNotSet;
+        mapInstruments[i] = jsetNotSet;
+    }
+    
+    for (i = 0; i <= jsetMaxPatterns; i++)
+    {
+        usedPatterns[i] = FALSE;
+        mapPatterns[i] = jsetNotSet;
+    }
+
+    // Find out all used patterns and ext.instruments
+    for (i = 0; i < m->norders; i++)
+    {
+        int pattern = m->orderList[i];
+        if (pattern >= 0 && pattern < m->npatterns)
+        {
+            JSSPattern *p = m->patterns[pattern];
+            if (p != NULL)
+            {
+                int row, channel;
+                JSSNote *n = p->data;
+                
+                // Mark pattern as used
+                usedPatterns[pattern] = TRUE;
+                
+                // Check all notes
+                for (row = 0; row < p->nrows; row++)
+                for (channel = 0; channel < p->nchannels; channel++, n++)
+                {
+                    if (n->instrument != jsetNotSet)
+                    {
+                        if (n->instrument >= 0 && n->instrument < m->nextInstruments) 
+                            usedExtInstruments[n->instrument] = TRUE;
+                        else
+                            dmMsg(3, "Pattern 0x%x, row=0x%x, chn=%d has invalid instrument 0x%x\n",
+                            pattern, row, channel, n->instrument);
+                    }
+                }
+            }
+            else
+            {
+                dmError("Pattern 0x%x is used on order 0x%x, but has no data!\n",
+                pattern, i);
+            }
+        }
+        else
+        if (pattern != jsetMaxPatterns)
+        {
+            dmError("Order 0x%x has invalid pattern number 0x%x!\n",
+            i, pattern);
+        }
+    }
+    
+    // Find out used instruments
+    for (i = 0; i <= jsetMaxInstruments; i++)
+    if (usedExtInstruments[i] && m->extInstruments[i] != NULL)
+    {
+        int note;
+        JSSExtInstrument *e = m->extInstruments[i];
+        
+        for (note = 0; note < jsetNNotes; note++)
+        if (e->sNumForNotes[note] != jsetNotSet)
+        {
+            int q = e->sNumForNotes[note];
+            if (q >= 0 && q < m->ninstruments)
+            {
+                usedInstruments[q] = TRUE;
+            }
+            else
+            {
+                dmError("Ext.instrument #%d sNumForNotes[%d] value out range (%d < %d).\n",
+                i, m->ninstruments, q);
+            }
+        }
+    }
+    
+    // Create pattern mappings
+    r->npatterns = 0;
+    dmMsg(2, "Unused patterns: ");
+        
+    for (i = 0; i <= jsetMaxPatterns; i++)
+    if (m->patterns[i] != NULL)
+    {
+        if (!usedPatterns[i])
+        {
+            dmPrint(2, "0x%x, ", i);
+        }
+        else
+        {
+            if (i >= m->npatterns)
+                dmError("Pattern 0x%x >= 0x%x, but used!\n", i, m->npatterns);
+            
+            mapPatterns[i] = r->npatterns;
+            r->patterns[r->npatterns] = m->patterns[i];
+            (r->npatterns)++;
+        }
+    }
+    dmPrint(2, "\n");
+    
+    dmMsg(1, "%d used patterns, %d unused.\n",
+    r->npatterns, m->npatterns - r->npatterns);
+    
+    
+    // Re-map instruments
+    dmMsg(2, "Unused instruments: ");
+    for (n8 = n16 = i = 0; i <= jsetMaxInstruments; i++)
+    if (m->instruments[i] != NULL)
+    {
+        if (!usedInstruments[i])
+        {
+            dmPrint(2, "0x%x, ", i);
+        }
+        else
+        {
+            JSSInstrument *ip = m->instruments[i];
+            if (i >= m->ninstruments)
+                dmError("Instrument 0x%x >= 0x%x, but used!\n", i, m->ninstruments);
+            
+            mapInstruments[i] = r->ninstruments;
+            r->instruments[r->ninstruments] = ip;
+            (r->ninstruments)++;
+
+            if (ip->flags & jsf16bit)
+                n16++;
+            else
+                n8++;
+        }
+    }
+    dmPrint(2, "\n");
+    dmMsg(1, "Total of (%d)  16-bit, (%d) 8-bit samples, (%d) instruments.\n",
+    n16, n8, r->ninstruments);
+    
+    // Re-map ext.instruments
+    dmMsg(2, "Unused ext.instruments: ");
+    for (i = 0; i < jsetMaxInstruments; i++)
+    if (usedExtInstruments[i])
+    {
+        if (i >= m->nextInstruments)
+        {
+            dmError("Ext.instrument 0x%x >= 0x%x, but used!\n",
+            i, m->nextInstruments);
+        }
+        else
+        if (m->extInstruments[i] != NULL)
+        {
+            JSSExtInstrument *e = m->extInstruments[i];
+            int note;
+            
+            mapExtInstruments[i] = r->nextInstruments;
+            r->extInstruments[r->nextInstruments] = e;
+            (r->nextInstruments)++;
+            
+            // Re-map sNumForNotes
+            for (note = 0; note < jsetNNotes; note++)
+            {
+                int q = e->sNumForNotes[note];
+                if (q != jsetNotSet)
+                {
+                    int map;
+                    if (q >= 0 && q <= jsetMaxInstruments)
+                    {
+                        map = mapInstruments[q];
+                    }
+                    else
+                    {
+                        map = jsetNotSet;
+                        dmError("e=%d, note=%d, q=%d/%d\n", i, note, q, r->ninstruments);
+                    }
+                    e->sNumForNotes[note] = map;
+                }
+            }
+        }
+        else
+        {
+            dmPrint(2, "[0x%x==NULL], ", i);
+            mapExtInstruments[i] = jsetNotSet;
+        }
+    }
+    else
+    {
+        if (i < m->nextInstruments && m->extInstruments[i] != NULL)
+        {
+            dmPrint(2, "0x%x, ", i);
+        }
+    }
+    dmPrint(2, "\n");
+    dmMsg(1, "%d extended instruments.\n", r->nextInstruments);
+    
+    
+    // Remap pattern instrument data
+    for (i = 0; i < r->npatterns; i++)
+    {
+        int row, channel;
+        JSSPattern *p = r->patterns[i];
+        JSSNote *n = p->data;
+        
+        for (row = 0; row < p->nrows; row++)
+        for (channel = 0; channel < p->nchannels; channel++, n++)
+        if (n->instrument >= 0 && n->instrument <= jsetMaxInstruments)
+        {
+            n->instrument = mapExtInstruments[n->instrument];
+        }
+    }
+    
+    // Remap orders list
+    for (i = 0; i < m->norders; i++)
+    {
+        r->orderList[i] = mapPatterns[m->orderList[i]];
+    }
+
+    return r;
+}
+
+
+int main(int argc, char *argv[])
+{
+    DMResource *sfile = NULL;
+    FILE *dfile = NULL;
+    JSSModule *sm, *dm;
+    int result;
+
+    dmInitProg("xm2jss", "XM to JSSMOD converter", "0.6", NULL, NULL);
+    dmVerbosity = 0;
+
+    // Parse arguments
+    if (!dmArgsProcess(argc, argv, optList, optListN,
+        argHandleOpt, argHandleFile, TRUE))
+        exit(1);
+
+
+    // Read the source file
+    if (srcFilename == NULL)
+        sfile = dmf_create_stdio_stream(stdin);
+    else
+    if ((sfile = dmf_create_stdio(srcFilename)) == NULL)
+    {
+        dmError("Error opening input file '%s'. (%s)\n",
+            srcFilename, strerror(errno));
+        return 1;
+    }
+
+    // Initialize miniJSS
+    jssInit();
+
+    // Read file
+    dmMsg(1, "Reading XM-format file ...\n");
+    result = jssLoadXM(sfile, &sm);
+    dmf_close(sfile);
+    if (result != 0)
+    {
+        dmError("Error while loading XM file (%i), ", result);
+        if (optIgnoreErrors)
+            fprintf(stderr, "ignoring. This may cause problems.\n");
+        else
+        {
+            fprintf(stderr, "giving up. Use --ignore if you want to try to convert anyway.\n");
+            return 2;
+        }
+    }
+
+    // Check stripping settings
+    if (optStripExtInstr) optStripInstr = TRUE;
+    if (optStripInstr) optStripSamples = TRUE;
+    
+    // Remove samples
+    if (optStripSamples)
+    {
+        int i;
+        
+        dmMsg(1, "Stripping samples...\n");
+        for (i = 0; i < sm->ninstruments; i++)
+        {
+            dmFree(sm->instruments[i]->data);
+            sm->instruments[i]->data = NULL;
+        }
+    }
+
+    // Remove instruments
+    if (optStripInstr)
+    {
+        int i;
+        
+        dmMsg(1, "Stripping instruments...\n");
+        for (i = 0; i < sm->ninstruments; i++)
+        {
+            dmFree(sm->instruments[i]);
+            sm->instruments[i] = NULL;
+        }
+        sm->ninstruments = 0;
+    }
+
+    // Remove ext.instruments
+    if (optStripExtInstr)
+    {
+        int i;
+        
+        dmMsg(1, "Stripping ext.instruments...\n");
+        for (i = 0; i < sm->nextInstruments; i++)
+        {
+            dmFree(sm->extInstruments[i]);
+            sm->extInstruments[i] = NULL;
+        }
+        sm->nextInstruments = 0;
+    }
+    // Run the optimization procedure
+    if (optOptimize)
+    {
+        dmMsg(1, "Optimizing module data...\n");
+        dm = optimizeModule(sm);
+    } else
+        dm = sm;
+
+    // Write output file
+    if (destFilename == NULL)
+        dfile = stdout;
+    else if ((dfile = fopen(destFilename, "wb")) == NULL)
+    {
+        dmError("Error creating output file '%s'. (%s)\n", destFilename, strerror(errno));
+        return 1;
+    }
+
+    dmMsg(1, "Writing JSSMOD-format file [patMode=0x%x, samp8=0x%2x, samp16=0x%2x]\n",
+        optPatternMode, optSampMode8, optSampMode16);
+    
+    result = jssSaveJSSMOD(dfile, dm, optPatternMode, optSampMode8, optSampMode16);
+    
+    fclose(dfile);
+    
+    if (result != 0)
+    {
+        dmError("Error while saving JSSMOD file (%i), the resulting file may be broken!\n", result);
+    }
+
+    dmMsg(1, "Conversion complete.\n");
+    return 0;
+}