From 1b220e75e53c5dbd09842ba918b8cb05b542604f Mon Sep 17 00:00:00 2001 From: Joseph-Eugene Winzer Date: Thu, 29 Jun 2017 11:42:47 +0200 Subject: SUPERNOVA: Implements playSoundMod() --- engines/supernova/console.cpp | 10 ++ engines/supernova/console.h | 1 + engines/supernova/supernova.cpp | 204 ++++++++++++++++++++++++++++++++++++++-- engines/supernova/supernova.h | 10 +- 4 files changed, 216 insertions(+), 9 deletions(-) (limited to 'engines') 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 -- cgit v1.2.3