# HG changeset patch # User Matti Hamalainen # Date 1560849171 -10800 # Node ID 40ccc09f09bec0cc2de79558df125c064ca43154 # Parent 026c3aa0e48f358d77109fe029fc9ed6658405fc Implement empty channel removal in xm2jss and make JSSMOD format support channel remapping for this. diff -r 026c3aa0e48f -r 40ccc09f09be minijss/jloadjss.c --- a/minijss/jloadjss.c Tue Jun 18 12:11:16 2019 +0300 +++ b/minijss/jloadjss.c Tue Jun 18 12:12:51 2019 +0300 @@ -16,13 +16,19 @@ #endif +static inline JSSNote * jssGetNotePtr(JSSPattern *pattern, const int channel, const int row) +{ + return pattern->data + (pattern->nchannels * row) + pattern->map[channel]; +} + + // Short helper macros for reading data #define JSGETBYTE(XV) \ if (!dmf_read_byte(inFile, XV)) \ return DMERR_OUT_OF_DATA -static int jssDoGetConvertedNote(DMResource *inFile, JSSNote *pnote, Uint8 note) +static int jssDoGetConvertedNote(DMResource *inFile, JSSNote *pnote, const Uint8 note) { Uint8 tmp; @@ -50,18 +56,29 @@ } -static inline int jssGetConvertedNote(DMResource *inFile, JSSNote *pnote) +static inline int jssGetConvertedNote(DMResource *inFile, + JSSPattern *pattern, const int channel, const int row) { Uint8 tmp; + int res; + JSGETBYTE(&tmp); - return jssDoGetConvertedNote(inFile, pnote, tmp); + if ((res = jssDoGetConvertedNote(inFile, + jssGetNotePtr(pattern, channel, row), tmp)) != DMERR_OK) + JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", + row, channel); + + return res; } #if defined(JM_SUP_PATMODE_2) || defined(JM_SUP_PATMODE_4) -static int jssGetCompressedNote(DMResource *inFile, JSSNote *pnote) +static int jssGetCompressedNoteDo(DMResource *inFile, + JSSPattern *pattern, const int channel, const int row) { + JSSNote *pnote = jssGetNotePtr(pattern, channel, row); Uint8 packb, tmp; + int res = DMERR_OK; JSGETBYTE(&packb); if (packb & 0x80) @@ -102,12 +119,23 @@ } else { - int ret; - if ((ret = jssDoGetConvertedNote(inFile, pnote, packb)) != DMERR_OK) - return ret; + res = jssDoGetConvertedNote(inFile, pnote, packb); } - return DMERR_OK; + return res; +} + + +static int jssGetCompressedNote(DMResource *inFile, + JSSPattern *pattern, const int channel, const int row) +{ + int res = jssGetCompressedNoteDo(inFile, pattern, channel, row); + + if (res != DMERR_OK) + JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", + row, channel); + + return res; } #endif @@ -115,19 +143,12 @@ #ifdef JM_SUP_PATMODE_2 static int jssGetPatternCompHoriz(DMResource *inFile, JSSPattern *pattern) { - int row, channel; - - assert(buf != NULL); - assert(pattern != NULL); - - for (row = 0; row < pattern->nrows; row++) - for (channel = 0; channel < pattern->nchannels; channel++) + for (int row = 0; row < pattern->nrows; row++) + for (int channel = 0; channel < pattern->nmap; channel++) { - JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; - int res = jssGetCompressedNote(inFile, pnote); + int res = jssGetCompressedNote(inFile, pattern, channel, row); if (res != DMERR_OK) - JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", - row, channel); + return res; } return DMERR_OK; @@ -138,19 +159,12 @@ #ifdef JM_SUP_PATMODE_4 static int jssGetPatternCompVert(DMResource *inFile, JSSPattern *pattern) { - int row, channel; - - assert(buf != NULL); - assert(pattern != NULL); - - for (channel = 0; channel < pattern->nchannels; channel++) - for (row = 0; row < pattern->nrows; row++) + for (int channel = 0; channel < pattern->nmap; channel++) + for (int row = 0; row < pattern->nrows; row++) { - JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; - int res = jssGetCompressedNote(inFile, pnote); + int res = jssGetCompressedNote(inFile, pattern, channel, row); if (res != DMERR_OK) - JSSERROR(res, res, "Error uncompressing note on row=%d, chn=%d\n", - row, channel); + return res; } return DMERR_OK; @@ -161,19 +175,12 @@ #ifdef JM_SUP_PATMODE_1 static int jssGetPatternRawHoriz(DMResource *inFile, JSSPattern *pattern) { - int row, channel; - - assert(buf != NULL); - assert(pattern != NULL); - - for (row = 0; row < pattern->nrows; row++) - for (channel = 0; channel < pattern->nchannels; channel++) + for (int row = 0; row < pattern->nrows; row++) + for (int channel = 0; channel < pattern->nmap; channel++) { - JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; - int res = jssGetConvertedNote(inFile, pnote); + int res = jssGetConvertedNote(inFile, pattern, channel, row); if (res != DMERR_OK) - JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", - row, channel); + return res; } return DMERR_OK; @@ -184,19 +191,12 @@ #ifdef JM_SUP_PATMODE_3 static int jssGetPatternRawVert(DMResource *inFile, JSSPattern *pattern) { - int row, channel; - - assert(buf != NULL); - assert(pattern != NULL); - - for (channel = 0; channel < pattern->nchannels; channel++) - for (row = 0; row < pattern->nrows; row++) + for (int channel = 0; channel < pattern->nmap; channel++) + for (int row = 0; row < pattern->nrows; row++) { - JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; - int res = jssGetConvertedNote(inFile, pnote); + int res = jssGetConvertedNote(inFile, pattern, channel, row); if (res != DMERR_OK) - JSSERROR(res, res, "Error converting note on row=%d, chn=%d\n", - row, channel); + return res; } return DMERR_OK; @@ -209,21 +209,18 @@ #undef JSGETBYTE #define JSGETBYTE(XV) if (!dmf_read_byte(inFile, XV)) return DMERR_OUT_OF_DATA -#define JSFOREACHNOTE1 \ - for (channel = 0; channel < pattern->nchannels; channel++) \ - for (row = 0; row < pattern->nrows; row++) { \ - JSSNote *pnote = pattern->data + (pattern->nchannels * row) + channel; +#define JSFOREACHNOTE1 \ + for (int channel = 0; channel < pattern->nmap; channel++) \ + for (int row = 0; row < pattern->nrows; row++) { \ + JSSNote *pnote = pattern->data + (pattern->nchannels * row) + pattern->map[channel]; #define JSFOREACHNOTE2 } + static int jssGetPatternRawVertElem(DMResource *inFile, JSSPattern *pattern) { - int row, channel; Uint8 tmp; - assert(buf != NULL); - assert(pattern != NULL); - JSFOREACHNOTE1 JSGETBYTE(&tmp); if (tmp == 0) @@ -433,7 +430,8 @@ // Read pattern header if (!dmf_read_le32(inFile, &jssP.size) || - !dmf_read_le16(inFile, &jssP.nrows)) + !dmf_read_le16(inFile, &jssP.nrows) || + !dmf_read_le16(inFile, &jssP.nchannels)) JSSERROR(DMERR_FREAD, DMERR_FREAD, "Failed to read JSSMOD pattern header #%d.\n", index); @@ -444,6 +442,11 @@ "Invalid number of rows in pattern #%d: %d.\n", index, jssP.nrows); + if (jssP.nchannels == 0 || jssP.nchannels > module->nchannels) + JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Invalid number of channels in pattern #%d: %d.\n", + index, jssP.nchannels); + // Allocate pattern pattern = module->patterns[index] = jssAllocatePattern(jssP.nrows, module->nchannels); if (pattern == NULL) @@ -453,6 +456,28 @@ index); } + // Read channel mappings, if any + pattern->nmap = jssP.nchannels; + if (jssP.nchannels != module->nchannels) + { + if (!dmf_read_str(inFile, pattern->map, + sizeof(pattern->map[0]) * jssP.nchannels)) + JSSERROR(DMERR_FREAD, DMERR_FREAD, + "Failed to read JSSMOD channel mapping data for pattern #%d.\n", + index); + + // Check mapping + for (int nch = 0; nch < jssP.nchannels; nch++) + { + if (pattern->map[nch] >= module->nchannels) + { + JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA, + "Invalid channel mapping in pattern #%d: chn %d -> %d.\n", + index, nch, pattern->map[nch]); + } + } + } + // Get pattern data switch (jssH.patMode) { diff -r 026c3aa0e48f -r 40ccc09f09be minijss/jssmod.c --- a/minijss/jssmod.c Tue Jun 18 12:11:16 2019 +0300 +++ b/minijss/jssmod.c Tue Jun 18 12:12:51 2019 +0300 @@ -343,7 +343,7 @@ // Check arguments if (nrows <= 0 || nchannels <= 0) - JSSERROR(DMERR_INVALID_ARGS, NULL, "Invalid nrows=%i or nchannels=%i.\n", nrows, nchannels); + JSSERROR(DMERR_INVALID_ARGS, NULL, "Invalid nrows=%d or nchannels=%d.\n", nrows, nchannels); // Allocate a pattern structure if ((pattern = dmMalloc0(sizeof(JSSPattern))) == NULL) @@ -353,14 +353,15 @@ pattern->data = dmMalloc(nrows * nchannels * sizeof(JSSNote)); if (pattern->data == NULL) { - dmFree(pattern); - JSSERROR(DMERR_MALLOC, NULL, "Could not allocate pattern data (nrows=%i, nchannels=%i).\n", nrows, - nchannels); + jssFreePattern(pattern); + JSSERROR(DMERR_MALLOC, NULL, "Could not allocate pattern data (nrows=%d, nchannels=%d).\n", + nrows, nchannels); } // Initialize structure pattern->nrows = nrows; pattern->nchannels = nchannels; + pattern->nmap = nchannels; pnote = pattern->data; for (int row = 0; row < nrows; row++) @@ -372,6 +373,19 @@ pnote++; } + // Initialize pattern channel map + pattern->map = dmMalloc(nchannels * sizeof(pattern->map[0])); + pattern->used = dmMalloc(nchannels * sizeof(pattern->used[0])); + if (pattern->map == NULL || pattern->used == NULL) + { + jssFreePattern(pattern); + JSSERROR(DMERR_MALLOC, NULL, "Could not allocate pattern map (nchannels=%d).\n", + nchannels); + } + + for (int chn = 0; chn < nchannels; chn++) + pattern->map[chn] = chn; + return pattern; } @@ -380,6 +394,8 @@ { if (pattern != NULL) { + dmFree(pattern->used); + dmFree(pattern->map); dmFree(pattern->data); dmFree(pattern); } diff -r 026c3aa0e48f -r 40ccc09f09be minijss/jssmod.h --- a/minijss/jssmod.h Tue Jun 18 12:11:16 2019 +0300 +++ b/minijss/jssmod.h Tue Jun 18 12:12:51 2019 +0300 @@ -143,7 +143,9 @@ typedef struct { - int nrows, nchannels; + int nrows, nchannels, nmap; + BOOL *used; + Uint8 *map; JSSNote *data; } JSSPattern; @@ -180,7 +182,7 @@ #ifdef JSS_SUP_JSSMOD -#define JSSMOD_VERSION (0x20) +#define JSSMOD_VERSION (0x30) enum { @@ -290,6 +292,9 @@ { Uint32 size; Uint16 nrows; + Uint16 nchannels; // may differ from JSSMODHeader::nchannels + // IF nchannels != JSSMODHeader::nchannels, then: + // Uint8 map[JSSMODPattern::nchannels]; } JSSMODPattern; #endif diff -r 026c3aa0e48f -r 40ccc09f09be tools/xm2jss.c --- a/tools/xm2jss.c Tue Jun 18 12:11:16 2019 +0300 +++ b/tools/xm2jss.c Tue Jun 18 12:12:51 2019 +0300 @@ -147,6 +147,15 @@ } +static inline const JSSNote * jssGetNotePtr(const JSSPattern *pattern, const int channel, const int row) +{ + if (!pattern->used[channel]) + return NULL; + else + return pattern->data + (pattern->nchannels * row) + channel; +} + + /* These functions and the macro mess are meant to make the * conversion routines themselves clearer and simpler. */ @@ -189,7 +198,7 @@ /* Convert a note */ -static int jssConvertNote( +static int jssDoConvertNote( Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSNote *pnote) { @@ -220,7 +229,7 @@ /* Compress a note */ -static int jssCompressNote( +static int jssDoCompressNote( Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSNote *pnote) { @@ -256,13 +265,33 @@ JSCOMPPUT(JM_COMP_VOLUME, pnote->volume, "Volume"); JSCOMPPUT(JM_COMP_EFFECT, pnote->effect, "Effect"); JSCOMPPUT(JM_COMP_PARAM, pnote->param, "Param"); + + return DMERR_OK; } else { // Was 4 bytes or more, just dump it all in .. - return jssConvertNote(patBuf, patBufSize, patSize, pnote); + return jssDoConvertNote(patBuf, patBufSize, patSize, pnote); } +} + +static int jssCompressNote( + Uint8 *patBuf, const size_t patBufSize, + size_t *patSize, const JSSPattern *pattern, + const int channel, const int row) +{ + const JSSNote *pnote = jssGetNotePtr(pattern, channel, row); + if (pnote != NULL) + { + int res = jssDoCompressNote(patBuf, patBufSize, patSize, pnote); + 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; } @@ -278,14 +307,9 @@ for (int row = 0; row < pattern->nrows; row++) for (int channel = 0; channel < pattern->nchannels; channel++) { - const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; - const int res = jssCompressNote(patBuf, patBufSize, patSize, pnote); + int res = jssCompressNote(patBuf, patBufSize, patSize, pattern, channel, row); 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; @@ -301,14 +325,9 @@ for (int channel = 0; channel < pattern->nchannels; channel++) for (int row = 0; row < pattern->nrows; row++) { - const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; - const int res = jssCompressNote(patBuf, patBufSize, patSize, pnote); + int res = jssCompressNote(patBuf, patBufSize, patSize, pattern, channel, row); 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; @@ -317,6 +336,26 @@ /* Convert a pattern */ +static int jssConvertNote( + Uint8 *patBuf, const size_t patBufSize, + size_t *patSize, const JSSPattern *pattern, + const int channel, const int row) +{ + const JSSNote *pnote = jssGetNotePtr(pattern, channel, row); + if (pnote != NULL) + { + int res = jssDoConvertNote(patBuf, patBufSize, patSize, pnote); + 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 jssConvertPatternRawHoriz( Uint8 *patBuf, const size_t patBufSize, size_t *patSize, const JSSPattern *pattern) @@ -326,14 +365,9 @@ for (int row = 0; row < pattern->nrows; row++) for (int channel = 0; channel < pattern->nchannels; channel++) { - const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; - const int res = jssConvertNote(patBuf, patBufSize, patSize, pnote); + int res = jssConvertNote(patBuf, patBufSize, patSize, pattern, channel, row); 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; @@ -349,14 +383,9 @@ for (int channel = 0; channel < pattern->nchannels; channel++) for (int row = 0; row < pattern->nrows; row++) { - const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; - const int res = jssConvertNote(patBuf, patBufSize, patSize, pnote); + int res = jssConvertNote(patBuf, patBufSize, patSize, pattern, channel, row); 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; @@ -366,9 +395,11 @@ #define JSFOREACHNOTE1 \ for (channel = 0; channel < pattern->nchannels; channel++) \ for (row = 0; row < pattern->nrows; row++) { \ - const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; + const JSSNote *pnote = jssGetNotePtr(pattern, channel, row); \ + if (pnote != NULL) { -#define JSFOREACHNOTE2 } +#define JSFOREACHNOTE2 } } + static int jssConvertPatternRawElem( Uint8 *patBuf, const size_t patBufSize, @@ -593,10 +624,28 @@ if (!dm_fwrite_le32(outFile, dataSize) || !dm_fwrite_le16(outFile, pattern->nrows) || - !dm_fwrite_str(outFile, patBuf, dataSize)) + !dm_fwrite_le16(outFile, pattern->nmap)) { JSSERROR(DMERR_FWRITE, DMERR_FWRITE, - "Error writing JSSMOD pattern #%d.\n", + "Error writing JSSMOD pattern header #%d.\n", + index); + } + + if (pattern->nmap != pattern->nchannels) + { + if (!dm_fwrite_str(outFile, pattern->map, + sizeof(pattern->map[0]) * pattern->nmap)) + { + JSSERROR(DMERR_FWRITE, DMERR_FWRITE, + "Error writing JSSMOD channel map for pattern #%d.\n", + index); + } + } + + if (!dm_fwrite_str(outFile, patBuf, dataSize)) + { + JSSERROR(DMERR_FWRITE, DMERR_FWRITE, + "Error writing JSSMOD pattern data #%d.\n", index); } } @@ -744,18 +793,19 @@ /* Scan given pattern for used instruments and channels. * Also checks if the pattern is empty. */ -void scanPattern(const JSSModule *module, const JSSPattern *pattern, - const int npattern, BOOL *usedExtInstruments, BOOL *usedChannels, BOOL *empty) +BOOL jssScanPattern(const JSSModule *module, const JSSPattern *pattern, + const int npattern, BOOL *usedExtInstruments, BOOL *usedChannels) { JSSNote *n = pattern->data; - *empty = FALSE; + BOOL empty = TRUE; // Check all notes in this pattern for (int row = 0; row < pattern->nrows; row++) for (int channel = 0; channel < pattern->nchannels; channel++, n++) { // Is the instrument set? - if (n->instrument != jsetNotSet) + if (usedExtInstruments != NULL && + n->instrument != jsetNotSet) { // Is it valid? if (n->instrument >= 0 && n->instrument < module->nextInstruments) @@ -774,10 +824,14 @@ n->effect != jsetNotSet || n->param != jsetNotSet) { - usedChannels[channel] = TRUE; - *empty = FALSE; + if (usedChannels != NULL) + usedChannels[channel] = TRUE; + + empty = FALSE; } } + + return empty; } @@ -798,8 +852,7 @@ { BOOL usedPatterns[jsetMaxPatterns + 1], usedInstruments[jsetMaxInstruments + 1], - usedExtInstruments[jsetMaxInstruments + 1], - usedChannels[jsetMaxChannels]; + usedExtInstruments[jsetMaxInstruments + 1]; int mapExtInstruments[jsetMaxInstruments + 1], mapInstruments[jsetMaxInstruments + 1], mapPatterns[jsetMaxPatterns + 1], @@ -827,7 +880,6 @@ for (int i = 0; i < jsetNChannels; i++) { r->defPanning[i] = m->defPanning[i]; - usedChannels[i] = FALSE; } // Initialize values @@ -859,13 +911,8 @@ JSSPattern *pattern = m->patterns[npat]; if (pattern != NULL) { - BOOL empty; - - // Mark this pattern as used - usedPatterns[npat] = TRUE; - - // Scan for used instruments and channels - scanPattern(m, pattern, npat, usedExtInstruments, usedChannels, &empty); + // Scan for used instruments etc + BOOL empty = jssScanPattern(m, pattern, npat, usedExtInstruments, NULL); // Empty patterns with known number of rows are "removed" if (empty && pattern->nrows == jsetDefaultRows) @@ -873,6 +920,8 @@ m->orderList[norder] = jsetNotSet; usedPatterns[npat] = FALSE; } + else + usedPatterns[npat] = TRUE; } else { @@ -1077,19 +1126,6 @@ r->nextInstruments, nunused); // - // Check for actually used channels - // XXX TODO: Actually remove the unused channels. - // - nunused = 0; - for (int i = 0; i < r->nchannels; i++) - { - if(!usedChannels[i]) - nunused++; - } - dmMsg(1, "%d channels (%d unused).\n", - r->nchannels - nunused, nunused); - - // // Remap pattern data with remapped instrument data // for (int i = 0; i < r->npatterns; i++) @@ -1150,6 +1186,29 @@ if (nunused) dmPrint(2, "\n"); + // + // Do final pass on patterns to remove unused channels + // + for (int i = 0; i < r->npatterns; i++) + { + JSSPattern *p = r->patterns[i]; + + jssScanPattern(r, p, i, NULL, p->used); + + p->nmap = 0; + for (int i = 0; i < r->nchannels; i++) + { + if (p->used[i]) + p->map[p->nmap++] = i; + } + + if (p->nmap != p->nchannels) + { + dmMsg(2, "Pattern %d: %d/%d used channels (%d unused).\n", + i, p->nchannels - p->nmap, p->nchannels, p->nmap); + } + } + return r; }