view xm2jss.c @ 96:6bf5220fa47e

Urgh .. use memset to silence some bogus GCC warnings about using potentially uninitialized values, while that will not actually be possible. In any case, it is annoying.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 02 Oct 2012 18:52:28 +0300
parents 7108681151a4
children 50f55def91e5
line wrap: on
line source

/*
 * 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, totalSize;
    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
    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
    totalSize = sizeof(jssH);
    if (fwrite(&jssH, sizeof(jssH), 1, outFile) != 1)
        JSSERROR(DMERR_FWRITE, DMERR_FWRITE, "Could not write JSSMOD header!\n");

    dmMsg(1," * JSSMOD-header 0x%04x, %d bytes.\n", JSSMOD_VERSION, totalSize);

    // Write orders list
    for (totalSize = order = 0; order < m->norders; order++)
    {
        Uint16 tmp = m->orderList[order];
        totalSize += sizeof(tmp);
        if (fwrite(&tmp, sizeof(tmp), 1, outFile) != 1)
            JSSERROR(DMERR_FWRITE, DMERR_FWRITE, "Could not write JSSMOD orders list.\n");
    }

    dmMsg(1," * %d item orders list, %d bytes.\n",
        m->norders, totalSize);

    // Allocate pattern compression buffer
    if ((patBuf = dmMalloc(patBufSize)) == NULL)
        JSSERROR(DMERR_MALLOC, DMERR_MALLOC,
        "Error allocating memory for pattern compression buffer.\n");

    
    // Write patterns
    for (totalSize = pattern = 0; pattern < m->npatterns; pattern++)
    {
        JSSMODPattern patHead;
        size_t finalSize = 0;
        
        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:
                i = DMERR_INVALID_DATA;
                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;
            totalSize += finalSize + sizeof(patHead);

            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);
    dmMsg(1," * %d patterns, %d bytes.\n", m->npatterns, totalSize);

    // Write extended instruments
    for (totalSize = 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
        totalSize += sizeof(jssE);
        if (fwrite(&jssE, sizeof(jssE), 1, outFile) != 1)
            JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
            "Could not write JSSMOD extended instrument #%i to file!\n", instr);
    }
    dmMsg(1," * %d Extended Instruments, %d bytes.\n", m->nextInstruments, totalSize);


    // Write sample instrument headers
    for (totalSize = 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
        totalSize += sizeof(jssI);
        if (fwrite(&jssI, sizeof(jssI), 1, outFile) != 1)
            JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
             "Could not write JSSMOD instrument #%i to file!\n", instr);
    }
    dmMsg(1," * %d Instrument headers, %d bytes.\n", m->ninstruments, totalSize);
    
    // Write sample data
    for (totalSize = 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);
            }
            
            totalSize += inst->size;
            if (res != (size_t) inst->size)
                JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
                 "Could not write JSSMOD sample #%i to file!\n", instr);
        }
    }
    dmMsg(1," * %d samples, %d bytes.\n", m->ninstruments, totalSize);
    
    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(2, "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(1, "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(1, "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(1, "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, "rb")) == NULL)
    {
        dmError("Error opening input file '%s', %d: %s\n",
            srcFilename, errno, 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%04x, samp8=0x%02x, samp16=0x%02x]\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;
}