diff options
| author | Joseph-Eugene Winzer | 2017-06-29 11:42:47 +0200 | 
|---|---|---|
| committer | Thierry Crozat | 2018-01-22 23:42:08 +0000 | 
| commit | 1b220e75e53c5dbd09842ba918b8cb05b542604f (patch) | |
| tree | bb387e022d07f7273ef8ca275b2d138bac46e7df | |
| parent | ef1bbce68eb2c45d36de0147df14c27dd8b622b6 (diff) | |
| download | scummvm-rg350-1b220e75e53c5dbd09842ba918b8cb05b542604f.tar.gz scummvm-rg350-1b220e75e53c5dbd09842ba918b8cb05b542604f.tar.bz2 scummvm-rg350-1b220e75e53c5dbd09842ba918b8cb05b542604f.zip  | |
SUPERNOVA: Implements playSoundMod()
| -rw-r--r-- | engines/supernova/console.cpp | 10 | ||||
| -rw-r--r-- | engines/supernova/console.h | 1 | ||||
| -rw-r--r-- | engines/supernova/supernova.cpp | 204 | ||||
| -rw-r--r-- | engines/supernova/supernova.h | 10 | 
4 files changed, 216 insertions, 9 deletions
diff --git a/engines/supernova/console.cpp b/engines/supernova/console.cpp index 96f97b71ce..560520c362 100644 --- a/engines/supernova/console.cpp +++ b/engines/supernova/console.cpp @@ -31,6 +31,7 @@ Console::Console(SupernovaEngine *vm, GameManager *gm)  {  	registerCmd("render", WRAP_METHOD(Console, cmdRenderImage));  	registerCmd("play", WRAP_METHOD(Console, cmdPlaySound)); +	registerCmd("music", WRAP_METHOD(Console, cmdMusic));  	registerCmd("list", WRAP_METHOD(Console, cmdList));  	registerCmd("inventory", WRAP_METHOD(Console, cmdInventory)); @@ -64,6 +65,15 @@ bool Console::cmdPlaySound(int argc, const char **argv) {  	return true;  } +bool Console::cmdMusic(int argc, const char **argv) { +	if (argc != 2) { +		debugPrintf("Usage: music [49/52]\n"); +		return true; +	} + +	_vm->playSoundMod(atoi(argv[1])); +} +  bool Console::cmdList(int argc, const char **argv) {  	// Objects in room and sections diff --git a/engines/supernova/console.h b/engines/supernova/console.h index 19efe47705..e78c3b556b 100644 --- a/engines/supernova/console.h +++ b/engines/supernova/console.h @@ -41,6 +41,7 @@ public:  	bool cmdRenderImage(int argc, const char **argv);  	bool cmdPlaySound(int argc, const char **argv); +	bool cmdMusic(int argc, const char **argv);  	bool cmdList(int argc, const char **argv);  	bool cmdInventory(int argc, const char **argv);  private: diff --git a/engines/supernova/supernova.cpp b/engines/supernova/supernova.cpp index 5fc88f6d06..0929f6c19d 100644 --- a/engines/supernova/supernova.cpp +++ b/engines/supernova/supernova.cpp @@ -34,6 +34,9 @@  #include "graphics/surface.h"  #include "graphics/screen.h"  #include "graphics/palette.h" +#include "audio/mods/protracker.h" +#include "common/memstream.h" +#include "common/endian.h"  #include "supernova/supernova.h" @@ -254,16 +257,18 @@ void SupernovaEngine::stopSound() {  void SupernovaEngine::playSoundMod(int filenumber)  { -	if (filenumber != 49 || filenumber != 52) { -		error("File not supposed to be played!"); +	if (filenumber != 49 && filenumber != 52) { +		return;  	} -	Common::File *file = new Common::File; -	if (!file->open(Common::String::format("msn_data.%03d", filenumber))) { -		error("File %s could not be read!", file->getName()); -	} +	Common::MemoryReadStream *modBuffer; +	modBuffer = convertToMod(Common::String::format("msn_data.%03d", filenumber).c_str()); -	// play Supernova MOD file +	if (modBuffer) { +		Audio::AudioStream *audioStream = Audio::makeProtrackerStream(modBuffer); +		stopSound(); +		_mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, audioStream); +	}  }  void SupernovaEngine::renderImage(MSNImageDecoder &image, int section, bool fullscreen) { @@ -1221,5 +1226,190 @@ void GameManager::executeRoom() {  	}  } +Common::MemoryReadStream *SupernovaEngine::convertToMod(const char *filename, int version) { +	// MSN format +	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]; + +	nbInstr2 = ((version == 1) ? 22 : 15); + +	Common::File msnFile; +	msnFile.open(filename); +	if (!msnFile.isOpen()) { +		warning("Data file '%s' not found", msnFile.getName()); +		return NULL; +	} + +	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.readByte(); +		msnFile.read(instr2[i].dummy, 5); +	} +	songLength = msnFile.readSint16LE(); +	msnFile.read(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(); +			} +		} +	} + +	/* MOD format */ +	struct { +		char iname[22]; +		uint16 length; +		char finetune; +		char volume; +		uint16 loopStart; +		uint16 loopLength; +	} instr[31]; +	int32 note[28][64][4]; + +	// 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. +	// 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 = 0; +				if (nbInstr2 == 22) { // version 1 +					i = ((*l & 0xF800) >> 11); +					int32 e = ((*l & 0x0700) >> 8); +					int32 e1 = invConvEff[e]; +					*l &= 0x0FFF00FF; +					*l |= (e1 << 8); +				} else { // version 2 +					int32 h = (*l >> 16); +					i = ((*l & 0xF000) >> 12); +					*l &= 0x00000FFF; +					if (h) +						h = 0xE000 / (h + 256); +					*l |= (h << 16); +					if (i == 15) +						i = 31; +				} + +				// Add back index in note +				if (i != 31) { +					++i; +					*l |= ((i & 0x0F) << 12); +					*l |= ((i & 0xF0) << 24); +				} +			} +		} +	} + +	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; + +		if (i < nbInstr2) { +			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; +		} +	} + +	// 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. 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', '.' }; + +	Common::MemoryWriteStreamDynamic buffer(DisposeAfterUse::NO); + +	buffer.write(msnFile.getName(), 19); +	buffer.writeByte(0); + +	for (int i = 0 ; i < 31 ; ++i) { +		buffer.write(instr[i].iname, 22); +		buffer.writeUint16BE(instr[i].length); +		buffer.writeByte(instr[i].finetune); +		buffer.writeByte(instr[i].volume); +		buffer.writeUint16BE(instr[i].loopStart); +		buffer.writeUint16BE(instr[i].loopLength); +	} +	buffer.writeByte((char)songLength); +	buffer.writeByte(ciaaSpeed); +	buffer.write(arrangement, 128); +	buffer.write(mark, 4); + +	for (int p = 0 ; p < patternNumber ; ++p) { +		for (int n = 0 ; n < 64 ; ++n) { +			for (int k = 0 ; k < 4 ; ++k) { +//				buffer.writeUint32BE(*((uint32*)(note[p][n]+k))); +				buffer.writeSint32BE(note[p][n][k]); +			} +		} +	} + +	uint nb; +	char buf[4096]; +	while ((nb = msnFile.read(buf, 4096)) > 0) +		buffer.write(buf, nb); + +	return new Common::MemoryReadStream(buffer.getData(), buffer.size()); +}  } diff --git a/engines/supernova/supernova.h b/engines/supernova/supernova.h index 8ff849f09f..53fd796bd7 100644 --- a/engines/supernova/supernova.h +++ b/engines/supernova/supernova.h @@ -27,15 +27,17 @@  #include "audio/mixer.h"  #include "audio/decoders/raw.h"  #include "common/array.h" +#include "common/events.h"  #include "common/random.h"  #include "common/scummsys.h" -#include "common/events.h"  #include "engines/engine.h" +#include "common/file.h" +#include "common/memstream.h"  #include "supernova/console.h"  #include "supernova/graphics.h" -#include "supernova/rooms.h"  #include "supernova/msn_def.h" +#include "supernova/rooms.h"  namespace Supernova { @@ -124,6 +126,8 @@ public:  	void renderText(const char *text);  	void renderBox(int x, int y, int width, int height, byte color);  	void setColor63(byte value); + +	Common::MemoryReadStream *convertToMod(const char *filename, int version = 1);  }; @@ -196,8 +200,10 @@ public:  	void closeLocker(const Room *room, Object *obj, Object *lock, int section);  	void edit(char *text, int x, int y, int length);  	int invertSection(int section); +	void command_print();  }; +  }  #endif  | 
