[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