/*
 * 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 "jssplr.h"
#include "dmlib.h"
#include "dmargs.h"
#include "dmres.h"
#include "dmmutex.h"


#define jmpNMODEffectTable (36)
static const char jmpMODEffectTable[jmpNMODEffectTable] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";


char  *optInFilename = NULL, *optOutFilename = 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 },
    { 1, 'v', "verbose",        "Be more verbose", OPT_NONE },
    { 2, 'i', "ignore",         "Ignore errors", OPT_NONE },
    { 3, 'p', "patterns",       "Pattern storage mode", OPT_ARGREQ },
    { 4, 'E', "strip-ext-instr","Strip ext. instruments (implies -I -S)", OPT_NONE },
    { 5, 'I', "strip-instr",    "Strip instruments (implies -S)", OPT_NONE },
    { 6, '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 },
    { 9, 'O', "optimize",       "Optimize module", OPT_NONE },
};

const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp()
{
    int i;

    dmPrintBanner(stdout, dmProgName, "[options] <input.xm> <output.jmod>");
    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:
        dmVerbosity++;
        break;

    case 2:
        optIgnoreErrors = TRUE;
        break;

    case 3:
        optPatternMode = atoi(optArg);
        if (optPatternMode <= 0 || optPatternMode >= PATMODE_LAST)
        {
            dmError("Unknown pattern conversion mode %d\n", optPatternMode);
            return FALSE;
        }
        break;

    case 4: optStripExtInstr = TRUE; break;
    case 5: optStripInstr = TRUE; break;
    case 6: optStripSamples = TRUE; break;

    case 7: optSampMode8 = atoi(optArg) & SAMPMODE_MASK; break;
    case 8: optSampMode16 = atoi(optArg) & SAMPMODE_MASK; break;

    case 9: optOptimize = TRUE; break;

    default:
        dmError("Unknown argument '%s'.\n", currArg);
        return FALSE;
    }
    
    return TRUE;
}


BOOL argHandleFile(char *currArg)
{
    // Was not option argument
    if (!optInFilename)
        optInFilename = currArg;
    else
    if (!optOutFilename)
        optOutFilename = currArg;
    else
    {
        dmError("Too many filename arguments specified, '%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;
    tmp = (note->param != jsetNotSet) ? note->param : 0;
    JSPUTBYTE(tmp);
    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 (optStripExtInstr || (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] && !optStripInstr)
        {
            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 && !optStripExtInstr)
        {
            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++)
        {
            char effect;

            if (!optStripExtInstr)
            {
                if (n->instrument >= 0 && n->instrument <= jsetMaxInstruments)
                    n->instrument = mapExtInstruments[n->instrument];

                if (n->instrument != jsetNotSet && r->extInstruments[n->instrument] == NULL)
                    dmError("Non-existing instrument used #%d.\n", n->instrument);
            }

            JMPGETEFFECT(effect, n->effect);
            
            switch (effect)
            {
                case 'C': // Cxx = Set volume
                    if (n->volume == jsetNotSet)
                    {
                        n->volume = n->param;
                        n->effect = jsetNotSet;
                        n->param = jsetNotSet;
                    }
                    break;
            }
        }
    }
    
    // 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 *inFile = NULL;
    FILE *outFile = 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);

    // Check arguments
    if (optInFilename == NULL || optOutFilename == NULL)
    {
        dmError("Input or output file not specified. Try --help.\n");
        return 1;
    }

    // Read the source file
    if ((result = dmf_create_stdio(optInFilename, "rb", &inFile)) != DMERR_OK)
    {
        dmError("Error opening input file '%s', %d: %s\n",
            optInFilename, result, dmErrorStr(result));
        return 1;
    }

    // Initialize miniJSS
    jssInit();

    // Read file
    dmMsg(1, "Reading XM-format file ...\n");
    result = jssLoadXM(inFile, &sm);
    dmf_close(inFile);
    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 ((outFile = fopen(optOutFilename, "wb")) == NULL)
    {
        dmError("Error creating output file '%s', %d: %s\n",
            optOutFilename, errno, 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(outFile, dm, optPatternMode, optSampMode8, optSampMode16);
    
    fclose(outFile);
    
    if (result != 0)
    {
        dmError("Error while saving JSSMOD file, %d: %s\n",
            result, dmErrorStr(result));
        dmError("WARNING: The resulting file may be broken!\n");
    }
    else
    {
        dmMsg(1, "Conversion complete.\n");
    }
    return 0;
}
