[Scummvm-git-logs] scummvm-tools master -> 250ebc33a82afeff3f3b1fb1339b0c1a8b21c8cd
criezy
criezy at scummvm.org
Mon Feb 22 00:04:31 UTC 2021
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm-tools' repo located at https://github.com/scummvm/scummvm-tools .
Summary:
c3f8ebebe5 SUPERNOVA: Add tool to convert MOD files to game data files
928966fe94 SUPERNOVA: Add conversion from game music file to MOD
d8901863e4 SUPERNOVA: Cleanup, annotate and fix minor errors in conv_mod tool
19b3fc7967 SUPERNOVA: Fix getting instrument length from MSN data file
c323e5054c SUPERNOVA: Fix writing song length when converting mod to msn data
250ebc33a8 SUPERNOVA: Remove some left over debug code
Commit: c3f8ebebe592add9541215e8d182e6fcf14a50a2
https://github.com/scummvm/scummvm-tools/commit/c3f8ebebe592add9541215e8d182e6fcf14a50a2
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2021-02-21T23:44:34Z
Commit Message:
SUPERNOVA: Add tool to convert MOD files to game data files
This is based on the konvmod1 and konvmod2 tools.
Changed paths:
A engines/supernova/convert_mod.cpp
A engines/supernova/convert_mod.h
.gitignore
Makefile.common
diff --git a/.gitignore b/.gitignore
index 88a2b72ba..d523da144 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@
/desword2
/extract_mohawk
/gob_loadcalc
+/msn_convert_mod
/pegasus_save_types
/ScummVM Tools.app
/scummvm-tools
diff --git a/Makefile.common b/Makefile.common
index 1fd3e3c43..baf26b81d 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -34,6 +34,7 @@ PROGRAMS = \
extract_mohawk \
extract_ngi \
construct_mohawk \
+ msn_convert_mod \
scummvm-tools-cli \
extract_hadesch \
extract_lokalizator
@@ -168,6 +169,11 @@ construct_mohawk_OBJS := \
engines/mohawk/utils.o \
$(UTILS)
+msn_convert_mod_OBJS := \
+ common/file.o \
+ common/util.o \
+ engines/supernova/convert_mod.o
+
pegasus_save_types_OBJS := \
engines/pegasus/pegasus_save_types.o \
$(UTILS)
diff --git a/engines/supernova/convert_mod.cpp b/engines/supernova/convert_mod.cpp
new file mode 100644
index 000000000..e7173c541
--- /dev/null
+++ b/engines/supernova/convert_mod.cpp
@@ -0,0 +1,190 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <common/endian.h>
+#include <common/util.h>
+#include "convert_mod.h"
+
+ModReader::ModReader(const Common::Filename& fileName) {
+ modFile.open(fileName, "rb");
+ if (!modFile.isOpen()) {
+ warning("Data file '%s' not found", fileName.getFullName().c_str());
+ return;
+ }
+
+ modFile.read_throwsOnError(songName, 20);
+ for (int i = 0 ; i < 31 ; ++i) {
+ modFile.read_throwsOnError(instr[i].iname, 22);
+ instr[i].length = modFile.readUint16BE();
+ instr[i].finetune = (char)modFile.readChar();
+ instr[i].volume = (char)modFile.readChar();
+ instr[i].loopStart = modFile.readUint16BE();
+ instr[i].loopLength = modFile.readUint16BE();
+ }
+ songLength = (char)modFile.readChar();
+ ciaaSpeed = (char)modFile.readChar();
+ modFile.read_throwsOnError(arrangement, 128);
+ modFile.read_throwsOnError(mark, 4);
+
+ uint32 l = modFile.size() - 1084;
+ char i_new = 0;
+ for (int i = 0 ; i < 31 ; ++i) {
+ l -= (uint32)(instr[i].length) << 1;
+ if (instr[i].length) {
+ convInstr[i] = i_new;
+ ++i_new;
+ } else
+ convInstr[i] = 31;
+ }
+
+ modFile.seek(1084L, SEEK_SET);
+ patternNumber = l / 1024;
+ for (int p = 0 ; p < patternNumber ; ++p) {
+ for (int n = 0 ; n < 64 ; ++n) {
+ for (int k = 0 ; k < 4 ; ++k) {
+ note[p][n][k] = modFile.readSint32BE();
+ }
+ }
+ }
+
+ notice("\nName: %s\nInstruments: %d\nPatterns: %d\n", songName, (int)i_new, patternNumber);
+}
+
+ModReader::~ModReader() {
+ if (modFile.isOpen())
+ modFile.close();
+}
+
+bool ModReader::convertToMsn(const Common::Filename& fileName, int version) {
+ if (!modFile.isOpen())
+ return false;
+
+ /* MSN-FORMAT */
+ struct {
+ uint16 seg;
+ uint16 start;
+ uint16 end;
+ uint16 loopStart;
+ uint16 loopEnd;
+ char volume;
+ char dummy[5];
+ } instr2[22];
+ int nbInstr2 = version == 1 ? 22 : 15;
+ int32 note2[28][64][4];
+
+ int pos = (version == 1 ? 484 : 372) + patternNumber * 1024;
+ for (int i = 0; i < 31; ++i) {
+ int i_new = convInstr[i];
+ if (i_new != 31) {
+ if (instr[i].length > 30000)
+ warning("\nInstrument too big");
+ instr2[i_new].seg = pos >> 4;
+ instr2[i_new].start = pos & 15;
+ instr2[i_new].end = (pos & 15) + (instr[i].length << 1);
+ instr2[i_new].loopStart = (pos & 15) + (instr[i].loopStart << 1);
+ instr2[i_new].loopEnd = (pos & 15) + (instr[i].loopStart << 1) + (instr[i].loopLength << 1);
+ instr2[i_new].volume = instr[i].volume;
+ notice("Nr: %d Start: %d", i_new, pos);
+ pos += instr[i].length << 1;
+ }
+ }
+
+ const char convEff[16] = {0,1,2,3, 0,0,0,0, 0,0,4,0, 5,6,0,7};
+
+ for (int p = 0; p < patternNumber; ++p) {
+ for (int n = 0; n < 64; ++n) {
+ for (int k = 0; k < 4; ++k) {
+ int32* l = &(note2[p][n][k]);
+ *l = note[p][n][k];
+ int32 i = ((*l >> 12) & 0x0FL) + ((*l >> 24) & 0xF0L) - 1;
+ if (i == -1)
+ i = 31;
+ else
+ i = convInstr[i];
+ int32 h = (*l >> 16) & 0x0fff;
+ if (h)
+ h = (0xe000L / h) - 256;
+ if (version == 1) {
+ int32 e1 = (*l >> 8) & 15;
+ //int32 op = (*l & 255);
+ int32 e = convEff[e1];
+ *l &= 0x0fff00ffL;
+ *l |= (i << 11) | (e << 8); // | (h << 16);
+ } else {
+ if (i == 31)
+ i = 15;
+ *l &= 0x00000fffL;
+ *l |= (i << 12) | (h << 16);
+ }
+ }
+ }
+ }
+
+
+ // Write file
+ Common::File msnFile(fileName, "wb");
+ if (!msnFile.isOpen()) {
+ warning("Cannot create file '%s'", fileName.getFullName().c_str());
+ return false;
+ }
+
+ for (int i = 0 ; i < nbInstr2 ; ++i) {
+ msnFile.writeUint16LE(instr2[i].seg);
+ msnFile.writeUint16LE(instr2[i].start);
+ msnFile.writeUint16LE(instr2[i].end);
+ msnFile.writeUint16LE(instr2[i].loopStart);
+ msnFile.writeUint16LE(instr2[i].loopEnd);
+ msnFile.writeChar(instr2[i].volume);
+ msnFile.write(instr2[i].dummy, 5);
+ }
+ msnFile.writeUint16LE(songLength);
+ msnFile.write(arrangement, 128);
+ msnFile.writeUint16LE(patternNumber);
+ for (int p = 0 ; p < patternNumber ; ++p) {
+ for (int n = 0 ; n < 64 ; ++n) {
+ for (int k = 0 ; k < 4 ; ++k) {
+ msnFile.writeUint32LE(*((uint32*)(note2[p][n]+k))); // writeSInt32LE(note[p][n][k])
+ }
+ }
+ }
+
+ size_t nb;
+ char buffer[4096];
+ while ((nb = modFile.read_noThrow(buffer, 4096)) > 0)
+ msnFile.write(buffer, nb);
+
+ msnFile.close();
+
+ return true;
+}
+
+
+int main(int argc, char *argv[]) {
+ printf("What do you want to do?\n");
+ printf(" 1: convert HUMMEL1.MOD to MSN_DATA.052\n");
+ printf(" 2: convert SNTHEME2.MOD to MS2_DATA.056\n");
+// printf(" 3: convert MSN_DATA.052 to HUMMEL1.MOD\n");
+// printf(" 4: convert MS2_DATA.056 to SNTHEME2.MOD\n");
+
+ int mode = -1;
+ scanf("%d", &mode);
+ if (mode < 1 || mode > 4)
+ return -1;
+
+ if (mode == 1) {
+ ModReader reader("HUMMEL1.MOD");
+ if (!reader.convertToMsn("MSN_DATA.052", 1))
+ return 1;
+ } else if (mode == 2) {
+ ModReader reader("SNTHEME2.MOD");
+ if (!reader.convertToMsn("MS2_DATA.056", 2))
+ return 1;
+ } else if (mode == 3) {
+ // TODO
+
+ } else if (mode == 4) {
+ // TODO
+
+ }
+
+ return 0;
+}
diff --git a/engines/supernova/convert_mod.h b/engines/supernova/convert_mod.h
new file mode 100644
index 000000000..6d11e3e4f
--- /dev/null
+++ b/engines/supernova/convert_mod.h
@@ -0,0 +1,36 @@
+#include <common/scummsys.h>
+#include <common/file.h>
+
+
+class ModReader {
+public:
+ ModReader(const Common::Filename&);
+ ~ModReader();
+
+ bool convertToMsn(const Common::Filename& fileName, int version = 1);
+
+private:
+ Common::File modFile;
+
+ char songName[20]; // liedname
+
+ struct {
+ char iname[22];
+ uint16 length; // laenge
+ char finetune;
+ char volume; // lautst
+ uint16 loopStart; // wdh_start
+ uint16 loopLength; // wdh_laenge
+ } instr[31];
+
+ char songLength; // liedlaenge
+ char ciaaSpeed;
+ char arrangement[128];
+ char mark[4]; // markierung
+
+ int16 patternNumber; // patternzahl
+ int32 note[28][64][4];
+
+ char convInstr[31]; // konv_instr
+};
+
Commit: 928966fe946f75fa4d19522196985afe102cc53b
https://github.com/scummvm/scummvm-tools/commit/928966fe946f75fa4d19522196985afe102cc53b
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2021-02-21T23:45:10Z
Commit Message:
SUPERNOVA: Add conversion from game music file to MOD
This is based on inverting what is done to create the game music file
from a mod file, but some information is missing and has to be guessed.
As it is it produced a MOD file that somewhat ressembles the music
but is not fully correct.
Changed paths:
engines/supernova/convert_mod.cpp
engines/supernova/convert_mod.h
diff --git a/engines/supernova/convert_mod.cpp b/engines/supernova/convert_mod.cpp
index e7173c541..18ebb9b95 100644
--- a/engines/supernova/convert_mod.cpp
+++ b/engines/supernova/convert_mod.cpp
@@ -4,7 +4,7 @@
#include <common/util.h>
#include "convert_mod.h"
-ModReader::ModReader(const Common::Filename& fileName) {
+ModReader::ModReader(const Common::Filename &fileName) {
modFile.open(fileName, "rb");
if (!modFile.isOpen()) {
warning("Data file '%s' not found", fileName.getFullName().c_str());
@@ -25,18 +25,20 @@ ModReader::ModReader(const Common::Filename& fileName) {
modFile.read_throwsOnError(arrangement, 128);
modFile.read_throwsOnError(mark, 4);
+ char mark_str[0];
+ strncpy(mark_str, mark, 4);
+ mark_str[4] = 0;
+ notice("Mark: '%s'", mark_str);
+
uint32 l = modFile.size() - 1084;
- char i_new = 0;
+ int nb_instr = 0;
for (int i = 0 ; i < 31 ; ++i) {
l -= (uint32)(instr[i].length) << 1;
- if (instr[i].length) {
- convInstr[i] = i_new;
- ++i_new;
- } else
- convInstr[i] = 31;
+ if (instr[i].length)
+ ++nb_instr;
}
- modFile.seek(1084L, SEEK_SET);
+ modFile.seek(1084, SEEK_SET);
patternNumber = l / 1024;
for (int p = 0 ; p < patternNumber ; ++p) {
for (int n = 0 ; n < 64 ; ++n) {
@@ -46,7 +48,7 @@ ModReader::ModReader(const Common::Filename& fileName) {
}
}
- notice("\nName: %s\nInstruments: %d\nPatterns: %d\n", songName, (int)i_new, patternNumber);
+ notice("\nName: %s\nInstruments: %d\nPatterns: %d\n", songName, nb_instr, (int)patternNumber);
}
ModReader::~ModReader() {
@@ -54,7 +56,7 @@ ModReader::~ModReader() {
modFile.close();
}
-bool ModReader::convertToMsn(const Common::Filename& fileName, int version) {
+bool ModReader::convertToMsn(const Common::Filename &fileName, int version) {
if (!modFile.isOpen())
return false;
@@ -71,17 +73,28 @@ bool ModReader::convertToMsn(const Common::Filename& fileName, int version) {
int nbInstr2 = version == 1 ? 22 : 15;
int32 note2[28][64][4];
+ char convInstr[31];
+ int i_new = 0;
+ for (int i = 0 ; i < 31 ; ++i) {
+ if (instr[i].length) {
+ convInstr[i] = (char)i_new;
+ ++i_new;
+ } else
+ convInstr[i] = 31;
+ }
+
+
int pos = (version == 1 ? 484 : 372) + patternNumber * 1024;
for (int i = 0; i < 31; ++i) {
- int i_new = convInstr[i];
+ i_new = convInstr[i];
if (i_new != 31) {
if (instr[i].length > 30000)
warning("\nInstrument too big");
instr2[i_new].seg = pos >> 4;
- instr2[i_new].start = pos & 15;
- instr2[i_new].end = (pos & 15) + (instr[i].length << 1);
- instr2[i_new].loopStart = (pos & 15) + (instr[i].loopStart << 1);
- instr2[i_new].loopEnd = (pos & 15) + (instr[i].loopStart << 1) + (instr[i].loopLength << 1);
+ instr2[i_new].start = pos & 0x0F;
+ instr2[i_new].end = (pos & 0x0F) + (instr[i].length << 1);
+ instr2[i_new].loopStart = (pos & 0x0F) + (instr[i].loopStart << 1);
+ instr2[i_new].loopEnd = instr2[i_new].loopStart + (instr[i].loopLength << 1);
instr2[i_new].volume = instr[i].volume;
notice("Nr: %d Start: %d", i_new, pos);
pos += instr[i].length << 1;
@@ -95,25 +108,24 @@ bool ModReader::convertToMsn(const Common::Filename& fileName, int version) {
for (int k = 0; k < 4; ++k) {
int32* l = &(note2[p][n][k]);
*l = note[p][n][k];
- int32 i = ((*l >> 12) & 0x0FL) + ((*l >> 24) & 0xF0L) - 1;
- if (i == -1)
- i = 31;
- else
- i = convInstr[i];
- int32 h = (*l >> 16) & 0x0fff;
- if (h)
- h = (0xe000L / h) - 256;
+ int32 i = ((*l >> 12) & 0x0F) + ((*l >> 24) & 0xF0) - 1;
+ i_new = 31;
+ if (i != -1)
+ i_new = convInstr[i];
if (version == 1) {
- int32 e1 = (*l >> 8) & 15;
+ int32 e1 = (*l >> 8) & 0x0F;
//int32 op = (*l & 255);
int32 e = convEff[e1];
- *l &= 0x0fff00ffL;
- *l |= (i << 11) | (e << 8); // | (h << 16);
+ *l &= 0x0fff00ff;
+ *l |= (i_new << 11) | (e << 8);
} else {
- if (i == 31)
- i = 15;
- *l &= 0x00000fffL;
- *l |= (i << 12) | (h << 16);
+ if (i_new == 31)
+ i_new = 15;
+ int32 h = (*l >> 16) & 0x0fff;
+ if (h)
+ h = (0xe000 / h) - 256;
+ *l &= 0x00000fff;
+ *l |= (i_new << 12) | (h << 16);
}
}
}
@@ -136,9 +148,9 @@ bool ModReader::convertToMsn(const Common::Filename& fileName, int version) {
msnFile.writeChar(instr2[i].volume);
msnFile.write(instr2[i].dummy, 5);
}
- msnFile.writeUint16LE(songLength);
+ msnFile.writeUint16LE(*((uint16*)&songLength)); // writeSint16LE(songLength)
msnFile.write(arrangement, 128);
- msnFile.writeUint16LE(patternNumber);
+ msnFile.writeUint16LE(*((uint16*)&patternNumber)); // writeSint16LE(patternNumber)
for (int p = 0 ; p < patternNumber ; ++p) {
for (int n = 0 ; n < 64 ; ++n) {
for (int k = 0 ; k < 4 ; ++k) {
@@ -157,13 +169,186 @@ bool ModReader::convertToMsn(const Common::Filename& fileName, int version) {
return true;
}
+MsnReader::MsnReader(const Common::Filename &fileName, int version) {
+ nbInstr2 = version == 1 ? 22 : 15;
+
+ msnFile.open(fileName, "rb");
+ if (!msnFile.isOpen()) {
+ warning("Data file '%s' not found", fileName.getFullName().c_str());
+ return;
+ }
+
+ for (int i = 0 ; i < nbInstr2 ; ++i) {
+ instr2[i].seg = msnFile.readUint16LE();
+ instr2[i].start = msnFile.readUint16LE();
+ instr2[i].end = msnFile.readUint16LE();
+ instr2[i].loopStart = msnFile.readUint16LE();
+ instr2[i].loopEnd = msnFile.readUint16LE();
+ instr2[i].volume = msnFile.readChar();
+ msnFile.read_throwsOnError(instr2[i].dummy, 5);
+ }
+ songLength = msnFile.readSint16LE();
+ msnFile.read_throwsOnError(arrangement, 128);
+ patternNumber = msnFile.readSint16LE();
+ for (int p = 0 ; p < patternNumber ; ++p) {
+ for (int n = 0 ; n < 64 ; ++n) {
+ for (int k = 0 ; k < 4 ; ++k) {
+ note2[p][n][k] = msnFile.readSint32LE();
+ }
+ }
+ }
+ notice("Song length: %d\nPatterns: %d\n", (int)songLength, (int)patternNumber);
+}
+
+MsnReader::~MsnReader() {
+ if (msnFile.isOpen())
+ msnFile.close();
+}
+
+bool MsnReader::convertToMod(const Common::Filename &fileName) {
+ if (!msnFile.isOpen())
+ return false;
+
+ /* MOD format */
+ struct {
+ char iname[22];
+ uint16 length;
+ char finetune;
+ char volume;
+ uint16 loopStart;
+ uint16 loopLength;
+ } instr[31];
+ int32 note[28][64][4];
+
+ char convInstr[31];
+ for (int i = 0 ; i < 31 ; ++i)
+ convInstr[i] = 31;
+
+ // We can't recover the position of 0 since it appears several times. So just assume it is the first one.
+ const char invConvEff[8] = {0, 1, 2, 3, 10, 12, 13 ,15};
+
+ // Can we recover the instruments mapping? I don't think so as part of the original instrument index is cleared.
+ // Assume the mapping is 1<-> 1.
+ for (int p = 0; p < patternNumber; ++p) {
+ for (int n = 0; n < 64; ++n) {
+ for (int k = 0; k < 4; ++k) {
+ int32* l = &(note[p][n][k]);
+ *l = note2[p][n][k];
+ int32 i_new = 0;
+ if (nbInstr2 == 22) { // version 1
+ i_new = ((*l & 0xFF00) >> 11);
+ int32 e = ((*l & 0x0700) >> 8);
+ int32 e1 = invConvEff[e];
+ *l &= 0x0fff00ff;
+ *l |= (e1 << 8);
+ } else { // version 2
+ int32 h = (*l >> 16);
+ i_new = ((*l & 0xFFFF) >> 12);
+ *l &= 0x00000fff;
+ if (h)
+ h = 0xe000 / (h + 256);
+ *l |= (h << 16);
+ if (i_new == 15)
+ i_new = 31;
+ }
+
+ if (i_new != 31) {
+ // Get the part of the instrument that is present in the MSN file.
+ int32 i = ((*l >> 24) & 0xF0) - 1;
+ if (i < i_new) {
+ // Write the missing par of the MOD instrument index
+ *l |= ((i_new - i) << 12);
+ i = i_new;
+ }
+ notice("MOD instrument: %d, MSN instrument: %d", (int)i, (int)i_new);
+ convInstr[i] = i_new;
+ }
+ }
+ }
+ }
+
+ int pos = (nbInstr2 == 22 ? 484 : 372) + patternNumber * 1024;
+ for (int i = 0; i < 31; ++i) {
+ // iname is not stored in the mod file. Just set it to 'instrument#'
+ // finetune is not stored either. Assume 0.
+ memset(instr[i].iname, 0, 22);
+ sprintf(instr[i].iname, "instrument%d", i+1);
+ instr[i].length = 0;
+ instr[i].finetune = 0;
+ instr[i].volume = 0;
+ instr[i].loopStart = 0;
+ instr[i].loopLength = 0;
+
+ int i_new = convInstr[i];
+ if (i_new != 31) {
+ instr[i].length = ((instr2[i_new].end - (pos & 0x0F)) >> 1);
+ instr[i].loopStart = ((instr2[i_new].loopStart - (pos & 0x0F)) >> 1);
+ instr[i].loopLength = (( instr2[i_new].loopEnd - instr2[i_new].loopStart) >> 1);
+ instr[i].volume = instr2[i].volume;
+ pos += instr[i].length << 1;
+ }
+ }
+
+ // The Restart byte for song looping cannot be recovered from MSN file.
+ // Assume a value of 0.
+ char ciaaSpeed = 0;
+
+ // The mark cannot be recovered either.
+ // It can be one of ID='M.K.', ID='4CHN',ID='6CHN',ID='8CHN', ID='4FLT',ID='8FLT'
+ // Assume 'M.K.'
+ const char mark[4] = { 'M', '.', 'K', '.' };
+
+ // Write file
+ Common::File modFile(fileName, "wb");
+ if (!modFile.isOpen()) {
+ warning("Cannot create file '%s'", fileName.getFullName().c_str());
+ return false;
+ }
+
+ char songName[20];
+ memset(songName, 0, 20);
+ strncpy(songName, fileName.getFullName().c_str(), 19);
+ modFile.write(songName, 20);
+
+ for (int i = 0 ; i < 31 ; ++i) {
+ modFile.write(instr[i].iname, 22);
+ modFile.writeUint16BE(instr[i].length);
+ modFile.writeChar(instr[i].finetune);
+ modFile.writeChar(instr[i].volume);
+ modFile.writeUint16BE(instr[i].loopStart);
+ modFile.writeUint16BE(instr[i].loopLength);
+ }
+ modFile.writeChar((char)songLength);
+ modFile.writeChar(ciaaSpeed);
+ modFile.write(arrangement, 128);
+ modFile.write(mark, 4);
+
+ for (int p = 0 ; p < patternNumber ; ++p) {
+ for (int n = 0 ; n < 64 ; ++n) {
+ for (int k = 0 ; k < 4 ; ++k) {
+ modFile.writeUint32BE(*((uint32*)(note[p][n]+k))); // writeSInt32BE(note[p][n][k])
+ }
+ }
+ }
+
+ size_t nb;
+ char buffer[4096];
+ while ((nb = msnFile.read_noThrow(buffer, 4096)) > 0)
+ modFile.write(buffer, nb);
+
+ modFile.close();
+
+ return true;
+}
+
+
int main(int argc, char *argv[]) {
printf("What do you want to do?\n");
printf(" 1: convert HUMMEL1.MOD to MSN_DATA.052\n");
printf(" 2: convert SNTHEME2.MOD to MS2_DATA.056\n");
-// printf(" 3: convert MSN_DATA.052 to HUMMEL1.MOD\n");
-// printf(" 4: convert MS2_DATA.056 to SNTHEME2.MOD\n");
+ printf(" 3: convert MSN_DATA.052 to HUMMEL1.MOD\n");
+ printf(" 4: convert MS2_DATA.056 to SNTHEME2.MOD\n");
int mode = -1;
scanf("%d", &mode);
@@ -179,11 +364,13 @@ int main(int argc, char *argv[]) {
if (!reader.convertToMsn("MS2_DATA.056", 2))
return 1;
} else if (mode == 3) {
- // TODO
-
+ MsnReader reader("MSN_DATA.052", 1);
+ if (!reader.convertToMod("HUMMEL1.MOD"))
+ return 1;
} else if (mode == 4) {
- // TODO
-
+ MsnReader reader("MS2_DATA.056", 2);
+ if (!reader.convertToMod("SNTHEME2.MOD"))
+ return 1;
}
return 0;
diff --git a/engines/supernova/convert_mod.h b/engines/supernova/convert_mod.h
index 6d11e3e4f..e37d46c64 100644
--- a/engines/supernova/convert_mod.h
+++ b/engines/supernova/convert_mod.h
@@ -30,7 +30,32 @@ private:
int16 patternNumber; // patternzahl
int32 note[28][64][4];
-
- char convInstr[31]; // konv_instr
};
+class MsnReader {
+public:
+ MsnReader(const Common::Filename&, int version = 1);
+ ~MsnReader();
+
+ bool convertToMod(const Common::Filename& fileName);
+
+private:
+ Common::File msnFile;
+
+ struct {
+ uint16 seg;
+ uint16 start;
+ uint16 end;
+ uint16 loopStart;
+ uint16 loopEnd;
+ char volume;
+ char dummy[5];
+ } instr2[22];
+ int nbInstr2; // 22 for version1, 15 for version 2
+
+ int16 songLength;
+ char arrangement[128];
+
+ int16 patternNumber;
+ int32 note2[28][64][4];
+};
\ No newline at end of file
Commit: d8901863e4e7a3c901f447b3d42f79d45fae601b
https://github.com/scummvm/scummvm-tools/commit/d8901863e4e7a3c901f447b3d42f79d45fae601b
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2021-02-21T23:45:24Z
Commit Message:
SUPERNOVA: Cleanup, annotate and fix minor errors in conv_mod tool
Changed paths:
engines/supernova/convert_mod.cpp
engines/supernova/convert_mod.h
diff --git a/engines/supernova/convert_mod.cpp b/engines/supernova/convert_mod.cpp
index 18ebb9b95..126cf5ef1 100644
--- a/engines/supernova/convert_mod.cpp
+++ b/engines/supernova/convert_mod.cpp
@@ -101,8 +101,61 @@ bool ModReader::convertToMsn(const Common::Filename &fileName, int version) {
}
}
+ // Table to convert MOD effect
+ // Mod effect values:
+ // 0: Arpeggio
+ // 1: Slide up
+ // 2: Slide down
+ // 3: Slide to note
+ // 4: Vibrato
+ // 5: Continue 'Slide to note', but also do Volume slide
+ // 6: Continue 'Vibrato', but also do Volume slide
+ // 7: Tremolo
+ // 8: (Set panning position) - often unused
+ // 9: Set sample offset
+ // 10: Volume slide
+ // 11: Position Jump
+ // 12: Set volume
+ // 13: Pattern Break
+ // 14: Misc (depends on the next 4 bits)
+ // 15: Set speed
+ //
+ // MSN mapped values:
+ // 1: Slide up
+ // 2: Slide down
+ // 3: Slide to note
+ // 4: Volume slide
+ // 5: Set volume
+ // 6: Pattern Break
+ // 7: Set speed
+ // 0: Everything else
const char convEff[16] = {0,1,2,3, 0,0,0,0, 0,0,4,0, 5,6,0,7};
+ // For each note, the 4 bytes are:
+ // 31 30 29 28 27 26 25 24 - 23 22 21 20 19 18 17 16 - 15 14 13 12 11 10 09 08 - 07 06 05 04 03 02 01 00
+ // h h h h g g g g f f f f e e e e d d d d c c c c b b b b a a a a
+ //
+ // MOD:
+ // hhhh dddd (8 bits) Sample index
+ // cccc (4 bits) Effect type for this channel/division
+ // bbbb aaaa (8 bits) Effect value
+ // gggg ffff eeee (12 bits) Sample period
+ //
+ // MSN:
+ // hhhh (4 bits) Cleared to 0
+ // dddd c (5 bits) Sample index | after mapping through convInstr
+ // ccc (3 bits) Effect type | after mapping through convEff
+ // bbbb aaaa (8 bits) Effect value | unmodified
+ // gggg ffff eeee (12 bits) Sample period | unmodified
+ //
+ // MS2:
+ // hhhh (4 bits) Cleared to 0
+ // dddd (4 bits) Sample index | after mapping through convInstr
+ // cccc (4 bits) Effect type | unmodified
+ // bbbb aaaa (8 bits) Effect value | unmodified
+ // gggg ffff eeee (12 bits) Sample period | transformed (0xE000 / p) - 256
+
+
for (int p = 0; p < patternNumber; ++p) {
for (int n = 0; n < 64; ++n) {
for (int k = 0; k < 4; ++k) {
@@ -116,22 +169,21 @@ bool ModReader::convertToMsn(const Common::Filename &fileName, int version) {
int32 e1 = (*l >> 8) & 0x0F;
//int32 op = (*l & 255);
int32 e = convEff[e1];
- *l &= 0x0fff00ff;
+ *l &= 0x0FFF00FF;
*l |= (i_new << 11) | (e << 8);
} else {
if (i_new == 31)
i_new = 15;
int32 h = (*l >> 16) & 0x0fff;
if (h)
- h = (0xe000 / h) - 256;
- *l &= 0x00000fff;
+ h = (0xE000 / h) - 256;
+ *l &= 0x00000FFF;
*l |= (i_new << 12) | (h << 16);
}
}
}
}
-
// Write file
Common::File msnFile(fileName, "wb");
if (!msnFile.isOpen()) {
@@ -154,7 +206,7 @@ bool ModReader::convertToMsn(const Common::Filename &fileName, int version) {
for (int p = 0 ; p < patternNumber ; ++p) {
for (int n = 0 ; n < 64 ; ++n) {
for (int k = 0 ; k < 4 ; ++k) {
- msnFile.writeUint32LE(*((uint32*)(note2[p][n]+k))); // writeSInt32LE(note[p][n][k])
+ msnFile.writeUint32LE(*((uint32*)(note2[p][n]+k))); // writeSint32LE(note[p][n][k])
}
}
}
@@ -220,48 +272,66 @@ bool MsnReader::convertToMod(const Common::Filename &fileName) {
} instr[31];
int32 note[28][64][4];
- char convInstr[31];
- for (int i = 0 ; i < 31 ; ++i)
- convInstr[i] = 31;
-
- // We can't recover the position of 0 since it appears several times. So just assume it is the first one.
+ // We can't recover some MOD effects since several of them are mapped to 0.
+ // Assume the MSN effect of value 0 is Arpeggio (MOD effect of value 0).
const char invConvEff[8] = {0, 1, 2, 3, 10, 12, 13 ,15};
+ // Reminder from convertToMsn
+ // 31 30 29 28 27 26 25 24 - 23 22 21 20 19 18 17 16 - 15 14 13 12 11 10 09 08 - 07 06 05 04 03 02 01 00
+ // h h h h g g g g f f f f e e e e d d d d c c c c b b b b a a a a
+ //
+ // MSN:
+ // hhhh (4 bits) Cleared to 0
+ // dddd c (5 bits) Sample index | after mapping through convInstr
+ // ccc (3 bits) Effect type | after mapping through convEff
+ // bbbb aaaa (8 bits) Effect value | unmodified
+ // gggg ffff eeee (12 bits) Sample period | unmodified
+ //
+ // MS2:
+ // hhhh (4 bits) Cleared to 0
+ // dddd (4 bits) Sample index | after mapping through convInstr
+ // cccc (4 bits) Effect type | unmodified
+ // bbbb aaaa (8 bits) Effect value | unmodified
+ // gggg ffff eeee (12 bits) Sample period | transformed (0xE000 / p) - 256
+ //
+ // MOD:
+ // hhhh dddd (8 bits) Sample index
+ // cccc (4 bits) Effect type for this channel/division
+ // bbbb aaaa (8 bits) Effect value
+ // gggg ffff eeee (12 bits) Sample period
+
// Can we recover the instruments mapping? I don't think so as part of the original instrument index is cleared.
- // Assume the mapping is 1<-> 1.
+ // And it doesn't really matter as long as we are consistent.
+ // However we need to make sure 31 (or 15 in MS2) is mapped to 0 in MOD.
+ // We just add 1 to all other values, and this means a 1 <-> 1 mapping for the instruments
for (int p = 0; p < patternNumber; ++p) {
for (int n = 0; n < 64; ++n) {
for (int k = 0; k < 4; ++k) {
int32* l = &(note[p][n][k]);
*l = note2[p][n][k];
- int32 i_new = 0;
+ int32 i = 0;
if (nbInstr2 == 22) { // version 1
- i_new = ((*l & 0xFF00) >> 11);
+ i = ((*l & 0xF800) >> 11);
int32 e = ((*l & 0x0700) >> 8);
int32 e1 = invConvEff[e];
- *l &= 0x0fff00ff;
+ *l &= 0x0FFF00FF;
*l |= (e1 << 8);
} else { // version 2
int32 h = (*l >> 16);
- i_new = ((*l & 0xFFFF) >> 12);
- *l &= 0x00000fff;
+ i = ((*l & 0xF000) >> 12);
+ *l &= 0x00000FFF;
if (h)
- h = 0xe000 / (h + 256);
+ h = 0xE000 / (h + 256);
*l |= (h << 16);
- if (i_new == 15)
- i_new = 31;
+ if (i == 15)
+ i = 31;
}
- if (i_new != 31) {
- // Get the part of the instrument that is present in the MSN file.
- int32 i = ((*l >> 24) & 0xF0) - 1;
- if (i < i_new) {
- // Write the missing par of the MOD instrument index
- *l |= ((i_new - i) << 12);
- i = i_new;
- }
- notice("MOD instrument: %d, MSN instrument: %d", (int)i, (int)i_new);
- convInstr[i] = i_new;
+ // Add back index in note
+ if (i != 31) {
+ ++i;
+ *l |= ((i & 0x0F) << 12);
+ *l |= ((i & 0xF0) << 24);
}
}
}
@@ -279,22 +349,22 @@ bool MsnReader::convertToMod(const Common::Filename &fileName) {
instr[i].loopStart = 0;
instr[i].loopLength = 0;
- int i_new = convInstr[i];
- if (i_new != 31) {
- instr[i].length = ((instr2[i_new].end - (pos & 0x0F)) >> 1);
- instr[i].loopStart = ((instr2[i_new].loopStart - (pos & 0x0F)) >> 1);
- instr[i].loopLength = (( instr2[i_new].loopEnd - instr2[i_new].loopStart) >> 1);
+ if (i < nbInstr2) {
+ instr[i].length = ((instr2[i].end - (pos & 0x0F)) >> 1);
+ instr[i].loopStart = ((instr2[i].loopStart - (pos & 0x0F)) >> 1);
+ instr[i].loopLength = (( instr2[i].loopEnd - instr2[i].loopStart) >> 1);
instr[i].volume = instr2[i].volume;
pos += instr[i].length << 1;
}
}
- // The Restart byte for song looping cannot be recovered from MSN file.
- // Assume a value of 0.
- char ciaaSpeed = 0;
+ // The ciaaSpeed is kind of useless and not present in the MSN file.
+ // Traditionally 0x78 in SoundTracker. Was used in NoiseTracker as a restart point.
+ // ProTracker uses 0x7F. FastTracker uses it as a restart point, whereas ScreamTracker 3 uses 0x7F like ProTracker.
+ // You can use this to roughly detect which tracker made a MOD, and detection gets more accurate for more obscure MOD types.
+ char ciaaSpeed = 0x7F;
- // The mark cannot be recovered either.
- // It can be one of ID='M.K.', ID='4CHN',ID='6CHN',ID='8CHN', ID='4FLT',ID='8FLT'
+ // The mark cannot be recovered either. Since we have 4 channels and 31 instrument it can be either ID='M.K.' or ID='4CHN'.
// Assume 'M.K.'
const char mark[4] = { 'M', '.', 'K', '.' };
@@ -326,7 +396,7 @@ bool MsnReader::convertToMod(const Common::Filename &fileName) {
for (int p = 0 ; p < patternNumber ; ++p) {
for (int n = 0 ; n < 64 ; ++n) {
for (int k = 0 ; k < 4 ; ++k) {
- modFile.writeUint32BE(*((uint32*)(note[p][n]+k))); // writeSInt32BE(note[p][n][k])
+ modFile.writeUint32BE(*((uint32*)(note[p][n]+k))); // writeSint32BE(note[p][n][k])
}
}
}
diff --git a/engines/supernova/convert_mod.h b/engines/supernova/convert_mod.h
index e37d46c64..65d4acdc6 100644
--- a/engines/supernova/convert_mod.h
+++ b/engines/supernova/convert_mod.h
@@ -30,6 +30,7 @@ private:
int16 patternNumber; // patternzahl
int32 note[28][64][4];
+ // Each pattern is subdivided in 64 divisions, each one with 4 channels
};
class MsnReader {
Commit: 19b3fc796788ecd173ebce44b34a2952d7ac4c59
https://github.com/scummvm/scummvm-tools/commit/19b3fc796788ecd173ebce44b34a2952d7ac4c59
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2021-02-21T23:45:34Z
Commit Message:
SUPERNOVA: Fix getting instrument length from MSN data file
With this change I am now getting correct MOD files.
Also set to 0 unused instruments when converting MOD to MSN
files to avoid having garbage in the data file. This is not important
but helps check the result.
Changed paths:
engines/supernova/convert_mod.cpp
diff --git a/engines/supernova/convert_mod.cpp b/engines/supernova/convert_mod.cpp
index 126cf5ef1..01f65771d 100644
--- a/engines/supernova/convert_mod.cpp
+++ b/engines/supernova/convert_mod.cpp
@@ -38,7 +38,6 @@ ModReader::ModReader(const Common::Filename &fileName) {
++nb_instr;
}
- modFile.seek(1084, SEEK_SET);
patternNumber = l / 1024;
for (int p = 0 ; p < patternNumber ; ++p) {
for (int n = 0 ; n < 64 ; ++n) {
@@ -83,6 +82,17 @@ bool ModReader::convertToMsn(const Common::Filename &fileName, int version) {
convInstr[i] = 31;
}
+ // Initialize all instruments to 0. This is not really important but that way we get 0 instead
+ // of garbage for the last 5 bytes (dummy) of each instruments and for the unused instruments.
+ for (int i = 0 ; i < nbInstr2 ; ++i) {
+ instr2[i].seg = 0;
+ instr2[i].start = 0;
+ instr2[i].end = 0;
+ instr2[i].loopStart = 0;
+ instr2[i].loopEnd = 0;
+ instr2[i].volume = 0;
+ memset(instr2[i].dummy, 0, 5);
+ }
int pos = (version == 1 ? 484 : 372) + patternNumber * 1024;
for (int i = 0; i < 31; ++i) {
@@ -337,7 +347,6 @@ bool MsnReader::convertToMod(const Common::Filename &fileName) {
}
}
- int pos = (nbInstr2 == 22 ? 484 : 372) + patternNumber * 1024;
for (int i = 0; i < 31; ++i) {
// iname is not stored in the mod file. Just set it to 'instrument#'
// finetune is not stored either. Assume 0.
@@ -350,11 +359,10 @@ bool MsnReader::convertToMod(const Common::Filename &fileName) {
instr[i].loopLength = 0;
if (i < nbInstr2) {
- instr[i].length = ((instr2[i].end - (pos & 0x0F)) >> 1);
- instr[i].loopStart = ((instr2[i].loopStart - (pos & 0x0F)) >> 1);
+ instr[i].length = ((instr2[i].end - instr2[i].start) >> 1);
+ instr[i].loopStart = ((instr2[i].loopStart - instr2[i].start) >> 1);
instr[i].loopLength = (( instr2[i].loopEnd - instr2[i].loopStart) >> 1);
instr[i].volume = instr2[i].volume;
- pos += instr[i].length << 1;
}
}
Commit: c323e5054c7e47542878362ce1e8d0bc25db2f6e
https://github.com/scummvm/scummvm-tools/commit/c323e5054c7e47542878362ce1e8d0bc25db2f6e
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2021-02-21T23:45:45Z
Commit Message:
SUPERNOVA: Fix writing song length when converting mod to msn data
Now when doing MSN -> MOD -> MSN or MS2 -> MOD -> MS2 we
get back exactly what we started with.
Changed paths:
engines/supernova/convert_mod.cpp
diff --git a/engines/supernova/convert_mod.cpp b/engines/supernova/convert_mod.cpp
index 01f65771d..8dea8e0ae 100644
--- a/engines/supernova/convert_mod.cpp
+++ b/engines/supernova/convert_mod.cpp
@@ -28,7 +28,6 @@ ModReader::ModReader(const Common::Filename &fileName) {
char mark_str[0];
strncpy(mark_str, mark, 4);
mark_str[4] = 0;
- notice("Mark: '%s'", mark_str);
uint32 l = modFile.size() - 1084;
int nb_instr = 0;
@@ -47,7 +46,7 @@ ModReader::ModReader(const Common::Filename &fileName) {
}
}
- notice("\nName: %s\nInstruments: %d\nPatterns: %d\n", songName, nb_instr, (int)patternNumber);
+ notice("\nName: %s\nSong length: %d\nInstruments: %d\nPatterns: %d\n", songName, (int)songLength, nb_instr, (int)patternNumber);
}
ModReader::~ModReader() {
@@ -106,7 +105,6 @@ bool ModReader::convertToMsn(const Common::Filename &fileName, int version) {
instr2[i_new].loopStart = (pos & 0x0F) + (instr[i].loopStart << 1);
instr2[i_new].loopEnd = instr2[i_new].loopStart + (instr[i].loopLength << 1);
instr2[i_new].volume = instr[i].volume;
- notice("Nr: %d Start: %d", i_new, pos);
pos += instr[i].length << 1;
}
}
@@ -210,7 +208,8 @@ bool ModReader::convertToMsn(const Common::Filename &fileName, int version) {
msnFile.writeChar(instr2[i].volume);
msnFile.write(instr2[i].dummy, 5);
}
- msnFile.writeUint16LE(*((uint16*)&songLength)); // writeSint16LE(songLength)
+ int16 songLength2 = songLength;
+ msnFile.writeUint16LE(*((uint16*)&songLength2)); // writeSint16LE(songLength)
msnFile.write(arrangement, 128);
msnFile.writeUint16LE(*((uint16*)&patternNumber)); // writeSint16LE(patternNumber)
for (int p = 0 ; p < patternNumber ; ++p) {
Commit: 250ebc33a82afeff3f3b1fb1339b0c1a8b21c8cd
https://github.com/scummvm/scummvm-tools/commit/250ebc33a82afeff3f3b1fb1339b0c1a8b21c8cd
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2021-02-21T23:45:53Z
Commit Message:
SUPERNOVA: Remove some left over debug code
Changed paths:
engines/supernova/convert_mod.cpp
diff --git a/engines/supernova/convert_mod.cpp b/engines/supernova/convert_mod.cpp
index 8dea8e0ae..15c4802e2 100644
--- a/engines/supernova/convert_mod.cpp
+++ b/engines/supernova/convert_mod.cpp
@@ -25,10 +25,6 @@ ModReader::ModReader(const Common::Filename &fileName) {
modFile.read_throwsOnError(arrangement, 128);
modFile.read_throwsOnError(mark, 4);
- char mark_str[0];
- strncpy(mark_str, mark, 4);
- mark_str[4] = 0;
-
uint32 l = modFile.size() - 1084;
int nb_instr = 0;
for (int i = 0 ; i < 31 ; ++i) {
More information about the Scummvm-git-logs
mailing list