diff tools/xm2jss.c @ 2285:25398f2eba64

Merge.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 18 Jun 2019 14:23:02 +0300
parents 6c3c3355007d
children dcf1016f3d27
line wrap: on
line diff
--- a/tools/xm2jss.c	Tue Jun 18 07:38:05 2019 +0300
+++ b/tools/xm2jss.c	Tue Jun 18 14:23:02 2019 +0300
@@ -1,7 +1,7 @@
 /*
  * xm2jss - Convert XM module to JSSMOD
  * Programmed and designed by Matti 'ccr' Hamalainen
- * (C) Copyright 2006-2017 Tecnic Software productions (TNSP)
+ * (C) Copyright 2006-2019 Tecnic Software productions (TNSP)
  *
  * Please read file 'COPYING' for information on license and distribution.
  */
@@ -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,
@@ -546,12 +577,18 @@
 
     // Convert and write patterns
     for (totalSize = index = 0; index < module->npatterns; index++)
-    if (module->patterns[index] != NULL)
     {
         JSSPattern *pattern = module->patterns[index];
         size_t dataSize = 0;
         int ret;
 
+        if (pattern == NULL)
+        {
+            dmMsg(1,
+            "Pattern #%d is NULL.\n", index);
+            pattern = module->patterns[module->npatterns];
+        }
+
         if (pattern->nrows > jsetMaxRows)
         {
             JSSERROR(DMERR_INVALID_DATA, DMERR_INVALID_DATA,
@@ -593,17 +630,30 @@
 
         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);
         }
-    }
-    else
-    {
-        JSSERROR(DMERR_NULLPTR, DMERR_NULLPTR,
-        "Pattern #%d was NULL.\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);
+        }
     }
 
     dmFree(patBuf);
@@ -620,8 +670,8 @@
         {
             einst = &tmpEInst;
             memset(&tmpEInst, 0, sizeof(tmpEInst));
-            JSSWARNING(DMERR_NULLPTR, DMERR_NULLPTR,
-            "Extended instrument #%d NULL!\n",
+            dmMsg(1,
+            "Extended instrument #%d is NULL!\n",
             index);
         }
 
@@ -744,18 +794,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,16 +825,20 @@
             n->effect != jsetNotSet ||
             n->param != jsetNotSet)
         {
-            usedChannels[channel] = TRUE;
-            *empty = FALSE;
+            if (usedChannels != NULL)
+                usedChannels[channel] = TRUE;
+
+            empty = FALSE;
         }
     }
+
+    return empty;
 }
 
 
 /* Check if two given patterns are dupes
  */
-BOOL comparePattern(const JSSPattern *pat1, const JSSPattern *pat2)
+BOOL jssComparePattern(const JSSPattern *pat1, const JSSPattern *pat2)
 {
     return
         pat1->nrows     == pat2->nrows &&
@@ -794,12 +849,11 @@
 
 /* Optimize a given module
  */
-JSSModule *optimizeModule(JSSModule *m)
+JSSModule *jssOptimizeModule(JSSModule *m)
 {
     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 +881,6 @@
     for (int i = 0; i < jsetNChannels; i++)
     {
         r->defPanning[i] = m->defPanning[i];
-        usedChannels[i]  = FALSE;
     }
 
     // Initialize values
@@ -859,13 +912,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 +921,8 @@
                     m->orderList[norder] = jsetNotSet;
                     usedPatterns[npat] = FALSE;
                 }
+                else
+                    usedPatterns[npat] = TRUE;
             }
             else
             {
@@ -951,7 +1001,7 @@
         for (int pat2 = 0; pat2 < m->npatterns; pat2++)
         if (pat1 != pat2 && m->patterns[pat2] != NULL &&
             dupPatterns[pat2] == jsetNotSet &&
-            comparePattern(m->patterns[pat1], m->patterns[pat2]))
+            jssComparePattern(m->patterns[pat1], m->patterns[pat2]))
         {
             dmPrint(1, " * %d and %d are dupes.\n", pat1, pat2);
             dupPatterns[pat2] = pat1;
@@ -1077,19 +1127,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 +1187,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;
 }
 
@@ -1161,7 +1221,7 @@
     JSSModule *sm, *dm;
     int result;
 
-    dmInitProg("xm2jss", "XM to JSSMOD converter", "0.7", NULL, NULL);
+    dmInitProg("xm2jss", "XM to JSSMOD converter", "0.8", NULL, NULL);
     dmVerbosity = 0;
 
     // Parse arguments
@@ -1252,7 +1312,7 @@
     if (optOptimize)
     {
         dmMsg(1, "Optimizing module data...\n");
-        dm = optimizeModule(sm);
+        dm = jssOptimizeModule(sm);
     } else
         dm = sm;