comparison 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
comparison
equal deleted inserted replaced
2277:026c3aa0e48f 2278:40ccc09f09be
145 145
146 return TRUE; 146 return TRUE;
147 } 147 }
148 148
149 149
150 static inline const JSSNote * jssGetNotePtr(const JSSPattern *pattern, const int channel, const int row)
151 {
152 if (!pattern->used[channel])
153 return NULL;
154 else
155 return pattern->data + (pattern->nchannels * row) + channel;
156 }
157
158
150 /* These functions and the macro mess are meant to make the 159 /* These functions and the macro mess are meant to make the
151 * conversion routines themselves clearer and simpler. 160 * conversion routines themselves clearer and simpler.
152 */ 161 */
153 BOOL jsPutByte(Uint8 *patBuf, size_t patBufSize, size_t *npatBuf, Uint8 val) 162 BOOL jsPutByte(Uint8 *patBuf, size_t patBufSize, size_t *npatBuf, Uint8 val)
154 { 163 {
187 } while (0) 196 } while (0)
188 197
189 198
190 /* Convert a note 199 /* Convert a note
191 */ 200 */
192 static int jssConvertNote( 201 static int jssDoConvertNote(
193 Uint8 *patBuf, const size_t patBufSize, 202 Uint8 *patBuf, const size_t patBufSize,
194 size_t *patSize, const JSSNote *pnote) 203 size_t *patSize, const JSSNote *pnote)
195 { 204 {
196 Uint8 tmp; 205 Uint8 tmp;
197 if (pnote->note == jsetNotSet) 206 if (pnote->note == jsetNotSet)
218 } 227 }
219 228
220 229
221 /* Compress a note 230 /* Compress a note
222 */ 231 */
223 static int jssCompressNote( 232 static int jssDoCompressNote(
224 Uint8 *patBuf, const size_t patBufSize, 233 Uint8 *patBuf, const size_t patBufSize,
225 size_t *patSize, const JSSNote *pnote) 234 size_t *patSize, const JSSNote *pnote)
226 { 235 {
227 Uint8 qflags = 0; 236 Uint8 qflags = 0;
228 int qcomp = 0; 237 int qcomp = 0;
254 263
255 JSCOMPPUT(JM_COMP_INSTRUMENT, pnote->instrument, "Instrument"); 264 JSCOMPPUT(JM_COMP_INSTRUMENT, pnote->instrument, "Instrument");
256 JSCOMPPUT(JM_COMP_VOLUME, pnote->volume, "Volume"); 265 JSCOMPPUT(JM_COMP_VOLUME, pnote->volume, "Volume");
257 JSCOMPPUT(JM_COMP_EFFECT, pnote->effect, "Effect"); 266 JSCOMPPUT(JM_COMP_EFFECT, pnote->effect, "Effect");
258 JSCOMPPUT(JM_COMP_PARAM, pnote->param, "Param"); 267 JSCOMPPUT(JM_COMP_PARAM, pnote->param, "Param");
268
269 return DMERR_OK;
259 } 270 }
260 else 271 else
261 { 272 {
262 // Was 4 bytes or more, just dump it all in .. 273 // Was 4 bytes or more, just dump it all in ..
263 return jssConvertNote(patBuf, patBufSize, patSize, pnote); 274 return jssDoConvertNote(patBuf, patBufSize, patSize, pnote);
264 } 275 }
265 276 }
277
278
279 static int jssCompressNote(
280 Uint8 *patBuf, const size_t patBufSize,
281 size_t *patSize, const JSSPattern *pattern,
282 const int channel, const int row)
283 {
284 const JSSNote *pnote = jssGetNotePtr(pattern, channel, row);
285 if (pnote != NULL)
286 {
287 int res = jssDoCompressNote(patBuf, patBufSize, patSize, pnote);
288 if (res != DMERR_OK)
289 {
290 JSSERROR(res, res, "Note compression failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
291 patBuf, patBufSize, *patSize, row, channel);
292 return res;
293 }
294 }
266 return DMERR_OK; 295 return DMERR_OK;
267 } 296 }
268 297
269 298
270 /* Compress pattern 299 /* Compress pattern
276 *patSize = 0; 305 *patSize = 0;
277 306
278 for (int row = 0; row < pattern->nrows; row++) 307 for (int row = 0; row < pattern->nrows; row++)
279 for (int channel = 0; channel < pattern->nchannels; channel++) 308 for (int channel = 0; channel < pattern->nchannels; channel++)
280 { 309 {
281 const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; 310 int res = jssCompressNote(patBuf, patBufSize, patSize, pattern, channel, row);
282 const int res = jssCompressNote(patBuf, patBufSize, patSize, pnote);
283 if (res != DMERR_OK) 311 if (res != DMERR_OK)
284 {
285 JSSERROR(res, res, "Note compression failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
286 patBuf, patBufSize, *patSize, row, channel);
287 return res; 312 return res;
288 }
289 } 313 }
290 314
291 return DMERR_OK; 315 return DMERR_OK;
292 } 316 }
293 317
299 *patSize = 0; 323 *patSize = 0;
300 324
301 for (int channel = 0; channel < pattern->nchannels; channel++) 325 for (int channel = 0; channel < pattern->nchannels; channel++)
302 for (int row = 0; row < pattern->nrows; row++) 326 for (int row = 0; row < pattern->nrows; row++)
303 { 327 {
304 const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; 328 int res = jssCompressNote(patBuf, patBufSize, patSize, pattern, channel, row);
305 const int res = jssCompressNote(patBuf, patBufSize, patSize, pnote);
306 if (res != DMERR_OK) 329 if (res != DMERR_OK)
307 { 330 return res;
308 JSSERROR(res, res, "Note compression failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n", 331 }
332
333 return DMERR_OK;
334 }
335
336
337 /* Convert a pattern
338 */
339 static int jssConvertNote(
340 Uint8 *patBuf, const size_t patBufSize,
341 size_t *patSize, const JSSPattern *pattern,
342 const int channel, const int row)
343 {
344 const JSSNote *pnote = jssGetNotePtr(pattern, channel, row);
345 if (pnote != NULL)
346 {
347 int res = jssDoConvertNote(patBuf, patBufSize, patSize, pnote);
348 if (res != DMERR_OK)
349 {
350 JSSERROR(res, res, "Note conversion failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
309 patBuf, patBufSize, *patSize, row, channel); 351 patBuf, patBufSize, *patSize, row, channel);
310 return res; 352 return res;
311 } 353 }
312 } 354 }
313
314 return DMERR_OK; 355 return DMERR_OK;
315 } 356 }
316 357
317 358
318 /* Convert a pattern
319 */
320 static int jssConvertPatternRawHoriz( 359 static int jssConvertPatternRawHoriz(
321 Uint8 *patBuf, const size_t patBufSize, 360 Uint8 *patBuf, const size_t patBufSize,
322 size_t *patSize, const JSSPattern *pattern) 361 size_t *patSize, const JSSPattern *pattern)
323 { 362 {
324 *patSize = 0; 363 *patSize = 0;
325 364
326 for (int row = 0; row < pattern->nrows; row++) 365 for (int row = 0; row < pattern->nrows; row++)
327 for (int channel = 0; channel < pattern->nchannels; channel++) 366 for (int channel = 0; channel < pattern->nchannels; channel++)
328 { 367 {
329 const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; 368 int res = jssConvertNote(patBuf, patBufSize, patSize, pattern, channel, row);
330 const int res = jssConvertNote(patBuf, patBufSize, patSize, pnote);
331 if (res != DMERR_OK) 369 if (res != DMERR_OK)
332 {
333 JSSERROR(res, res, "Note conversion failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
334 patBuf, patBufSize, *patSize, row, channel);
335 return res; 370 return res;
336 }
337 } 371 }
338 372
339 return DMERR_OK; 373 return DMERR_OK;
340 } 374 }
341 375
347 *patSize = 0; 381 *patSize = 0;
348 382
349 for (int channel = 0; channel < pattern->nchannels; channel++) 383 for (int channel = 0; channel < pattern->nchannels; channel++)
350 for (int row = 0; row < pattern->nrows; row++) 384 for (int row = 0; row < pattern->nrows; row++)
351 { 385 {
352 const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; 386 int res = jssConvertNote(patBuf, patBufSize, patSize, pattern, channel, row);
353 const int res = jssConvertNote(patBuf, patBufSize, patSize, pnote);
354 if (res != DMERR_OK) 387 if (res != DMERR_OK)
355 {
356 JSSERROR(res, res, "Note conversion failed [patBuf=%p, patBufSize=%d, patSize=%d, row=%d, chn=%d]\n",
357 patBuf, patBufSize, *patSize, row, channel);
358 return res; 388 return res;
359 }
360 } 389 }
361 390
362 return DMERR_OK; 391 return DMERR_OK;
363 } 392 }
364 393
365 394
366 #define JSFOREACHNOTE1 \ 395 #define JSFOREACHNOTE1 \
367 for (channel = 0; channel < pattern->nchannels; channel++) \ 396 for (channel = 0; channel < pattern->nchannels; channel++) \
368 for (row = 0; row < pattern->nrows; row++) { \ 397 for (row = 0; row < pattern->nrows; row++) { \
369 const JSSNote *pnote = &pattern->data[(pattern->nchannels * row) + channel]; 398 const JSSNote *pnote = jssGetNotePtr(pattern, channel, row); \
370 399 if (pnote != NULL) {
371 #define JSFOREACHNOTE2 } 400
401 #define JSFOREACHNOTE2 } }
402
372 403
373 static int jssConvertPatternRawElem( 404 static int jssConvertPatternRawElem(
374 Uint8 *patBuf, const size_t patBufSize, 405 Uint8 *patBuf, const size_t patBufSize,
375 size_t *patSize, const JSSPattern *pattern) 406 size_t *patSize, const JSSPattern *pattern)
376 { 407 {
591 dmMsg(3, " - Pattern %d size %d bytes\n", index, dataSize); 622 dmMsg(3, " - Pattern %d size %d bytes\n", index, dataSize);
592 totalSize += dataSize + sizeof(JSSMODPattern); 623 totalSize += dataSize + sizeof(JSSMODPattern);
593 624
594 if (!dm_fwrite_le32(outFile, dataSize) || 625 if (!dm_fwrite_le32(outFile, dataSize) ||
595 !dm_fwrite_le16(outFile, pattern->nrows) || 626 !dm_fwrite_le16(outFile, pattern->nrows) ||
596 !dm_fwrite_str(outFile, patBuf, dataSize)) 627 !dm_fwrite_le16(outFile, pattern->nmap))
597 { 628 {
598 JSSERROR(DMERR_FWRITE, DMERR_FWRITE, 629 JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
599 "Error writing JSSMOD pattern #%d.\n", 630 "Error writing JSSMOD pattern header #%d.\n",
631 index);
632 }
633
634 if (pattern->nmap != pattern->nchannels)
635 {
636 if (!dm_fwrite_str(outFile, pattern->map,
637 sizeof(pattern->map[0]) * pattern->nmap))
638 {
639 JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
640 "Error writing JSSMOD channel map for pattern #%d.\n",
641 index);
642 }
643 }
644
645 if (!dm_fwrite_str(outFile, patBuf, dataSize))
646 {
647 JSSERROR(DMERR_FWRITE, DMERR_FWRITE,
648 "Error writing JSSMOD pattern data #%d.\n",
600 index); 649 index);
601 } 650 }
602 } 651 }
603 else 652 else
604 { 653 {
742 791
743 792
744 /* Scan given pattern for used instruments and channels. 793 /* Scan given pattern for used instruments and channels.
745 * Also checks if the pattern is empty. 794 * Also checks if the pattern is empty.
746 */ 795 */
747 void scanPattern(const JSSModule *module, const JSSPattern *pattern, 796 BOOL jssScanPattern(const JSSModule *module, const JSSPattern *pattern,
748 const int npattern, BOOL *usedExtInstruments, BOOL *usedChannels, BOOL *empty) 797 const int npattern, BOOL *usedExtInstruments, BOOL *usedChannels)
749 { 798 {
750 JSSNote *n = pattern->data; 799 JSSNote *n = pattern->data;
751 *empty = FALSE; 800 BOOL empty = TRUE;
752 801
753 // Check all notes in this pattern 802 // Check all notes in this pattern
754 for (int row = 0; row < pattern->nrows; row++) 803 for (int row = 0; row < pattern->nrows; row++)
755 for (int channel = 0; channel < pattern->nchannels; channel++, n++) 804 for (int channel = 0; channel < pattern->nchannels; channel++, n++)
756 { 805 {
757 // Is the instrument set? 806 // Is the instrument set?
758 if (n->instrument != jsetNotSet) 807 if (usedExtInstruments != NULL &&
808 n->instrument != jsetNotSet)
759 { 809 {
760 // Is it valid? 810 // Is it valid?
761 if (n->instrument >= 0 && n->instrument < module->nextInstruments) 811 if (n->instrument >= 0 && n->instrument < module->nextInstruments)
762 usedExtInstruments[n->instrument] = TRUE; 812 usedExtInstruments[n->instrument] = TRUE;
763 else 813 else
772 n->instrument != jsetNotSet || 822 n->instrument != jsetNotSet ||
773 n->volume != jsetNotSet || 823 n->volume != jsetNotSet ||
774 n->effect != jsetNotSet || 824 n->effect != jsetNotSet ||
775 n->param != jsetNotSet) 825 n->param != jsetNotSet)
776 { 826 {
777 usedChannels[channel] = TRUE; 827 if (usedChannels != NULL)
778 *empty = FALSE; 828 usedChannels[channel] = TRUE;
779 } 829
780 } 830 empty = FALSE;
831 }
832 }
833
834 return empty;
781 } 835 }
782 836
783 837
784 /* Check if two given patterns are dupes 838 /* Check if two given patterns are dupes
785 */ 839 */
796 */ 850 */
797 JSSModule *jssOptimizeModule(JSSModule *m) 851 JSSModule *jssOptimizeModule(JSSModule *m)
798 { 852 {
799 BOOL usedPatterns[jsetMaxPatterns + 1], 853 BOOL usedPatterns[jsetMaxPatterns + 1],
800 usedInstruments[jsetMaxInstruments + 1], 854 usedInstruments[jsetMaxInstruments + 1],
801 usedExtInstruments[jsetMaxInstruments + 1], 855 usedExtInstruments[jsetMaxInstruments + 1];
802 usedChannels[jsetMaxChannels];
803 int mapExtInstruments[jsetMaxInstruments + 1], 856 int mapExtInstruments[jsetMaxInstruments + 1],
804 mapInstruments[jsetMaxInstruments + 1], 857 mapInstruments[jsetMaxInstruments + 1],
805 mapPatterns[jsetMaxPatterns + 1], 858 mapPatterns[jsetMaxPatterns + 1],
806 dupPatterns[jsetMaxPatterns + 1]; 859 dupPatterns[jsetMaxPatterns + 1];
807 860
825 r->norders = m->norders; 878 r->norders = m->norders;
826 879
827 for (int i = 0; i < jsetNChannels; i++) 880 for (int i = 0; i < jsetNChannels; i++)
828 { 881 {
829 r->defPanning[i] = m->defPanning[i]; 882 r->defPanning[i] = m->defPanning[i];
830 usedChannels[i] = FALSE;
831 } 883 }
832 884
833 // Initialize values 885 // Initialize values
834 for (int i = 0; i <= jsetMaxInstruments; i++) 886 for (int i = 0; i <= jsetMaxInstruments; i++)
835 { 887 {
857 if (npat >= 0 && npat < m->npatterns) 909 if (npat >= 0 && npat < m->npatterns)
858 { 910 {
859 JSSPattern *pattern = m->patterns[npat]; 911 JSSPattern *pattern = m->patterns[npat];
860 if (pattern != NULL) 912 if (pattern != NULL)
861 { 913 {
862 BOOL empty; 914 // Scan for used instruments etc
863 915 BOOL empty = jssScanPattern(m, pattern, npat, usedExtInstruments, NULL);
864 // Mark this pattern as used
865 usedPatterns[npat] = TRUE;
866
867 // Scan for used instruments and channels
868 scanPattern(m, pattern, npat, usedExtInstruments, usedChannels, &empty);
869 916
870 // Empty patterns with known number of rows are "removed" 917 // Empty patterns with known number of rows are "removed"
871 if (empty && pattern->nrows == jsetDefaultRows) 918 if (empty && pattern->nrows == jsetDefaultRows)
872 { 919 {
873 m->orderList[norder] = jsetNotSet; 920 m->orderList[norder] = jsetNotSet;
874 usedPatterns[npat] = FALSE; 921 usedPatterns[npat] = FALSE;
875 } 922 }
923 else
924 usedPatterns[npat] = TRUE;
876 } 925 }
877 else 926 else
878 { 927 {
879 dmErrorMsg("Pattern 0x%x is used on order 0x%x, but has no data!\n", 928 dmErrorMsg("Pattern 0x%x is used on order 0x%x, but has no data!\n",
880 npat, norder); 929 npat, norder);
1075 dmPrint(2, "\n"); 1124 dmPrint(2, "\n");
1076 dmMsg(1, "%d extended instruments (%d unused).\n", 1125 dmMsg(1, "%d extended instruments (%d unused).\n",
1077 r->nextInstruments, nunused); 1126 r->nextInstruments, nunused);
1078 1127
1079 // 1128 //
1080 // Check for actually used channels
1081 // XXX TODO: Actually remove the unused channels.
1082 //
1083 nunused = 0;
1084 for (int i = 0; i < r->nchannels; i++)
1085 {
1086 if(!usedChannels[i])
1087 nunused++;
1088 }
1089 dmMsg(1, "%d channels (%d unused).\n",
1090 r->nchannels - nunused, nunused);
1091
1092 //
1093 // Remap pattern data with remapped instrument data 1129 // Remap pattern data with remapped instrument data
1094 // 1130 //
1095 for (int i = 0; i < r->npatterns; i++) 1131 for (int i = 0; i < r->npatterns; i++)
1096 { 1132 {
1097 JSSPattern *p = r->patterns[i]; 1133 JSSPattern *p = r->patterns[i];
1148 r->orderList[i] = map; 1184 r->orderList[i] = map;
1149 } 1185 }
1150 if (nunused) 1186 if (nunused)
1151 dmPrint(2, "\n"); 1187 dmPrint(2, "\n");
1152 1188
1189 //
1190 // Do final pass on patterns to remove unused channels
1191 //
1192 for (int i = 0; i < r->npatterns; i++)
1193 {
1194 JSSPattern *p = r->patterns[i];
1195
1196 jssScanPattern(r, p, i, NULL, p->used);
1197
1198 p->nmap = 0;
1199 for (int i = 0; i < r->nchannels; i++)
1200 {
1201 if (p->used[i])
1202 p->map[p->nmap++] = i;
1203 }
1204
1205 if (p->nmap != p->nchannels)
1206 {
1207 dmMsg(2, "Pattern %d: %d/%d used channels (%d unused).\n",
1208 i, p->nchannels - p->nmap, p->nchannels, p->nmap);
1209 }
1210 }
1211
1153 return r; 1212 return r;
1154 } 1213 }
1155 1214
1156 1215
1157 int main(int argc, char *argv[]) 1216 int main(int argc, char *argv[])