Mercurial > hg > dmlib
view xm2jss.c @ 526:f7df57cafdd9
Add support for Interpaint (unpacked) and Doodle (unpacked) hires formats.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 22 Nov 2012 01:07:45 +0200 |
parents | cd57ba1130eb |
children |
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 "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 *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); // Check arguments if (optInFilename == NULL || optOutFilename == NULL) { dmError("Input or output file not specified. Try --help.\n"); return 1; } // Read the source file if ((sfile = dmf_create_stdio(optInFilename, "rb")) == NULL) { dmError("Error opening input file '%s', %d: %s\n", optInFilename, 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 ((dfile = 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(dfile, dm, optPatternMode, optSampMode8, optSampMode16); fclose(dfile); 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; }