diff tools/xm2jss.c @ 2278:40ccc09f09be

Implement empty channel removal in xm2jss and make JSSMOD format support channel remapping for this.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 18 Jun 2019 12:12:51 +0300
parents 026c3aa0e48f
children fc58f62f100c
line wrap: on
line diff
--- 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;
 }