Mercurial > hg > dmlib
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; +}