Mercurial > hg > dmlib
view xm2jss.c @ 495:30145d17aebd
Move certain editor related targets to the actual TARGETS variable, and
remove them from being separately specified in the "make clean" target,
thusly cleaning them only when editor is enabled in build configuration.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 16 Nov 2012 19:18:03 +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; }