diff options
Diffstat (limited to 'engines/agos/midi.cpp')
-rw-r--r-- | engines/agos/midi.cpp | 359 |
1 files changed, 341 insertions, 18 deletions
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp index 045fd9dac5..85f2dd5977 100644 --- a/engines/agos/midi.cpp +++ b/engines/agos/midi.cpp @@ -23,10 +23,20 @@ #include "common/config-manager.h" #include "common/file.h" #include "common/textconsole.h" +#include "common/memstream.h" #include "agos/agos.h" #include "agos/midi.h" +#include "agos/drivers/accolade/mididriver.h" +// Miles Audio for Simon 2 +#include "audio/miles.h" + +// PKWARE data compression library decompressor required for Simon 2 +#include "common/dcl.h" + +#include "gui/message.h" + namespace AGOS { @@ -42,6 +52,9 @@ MidiPlayer::MidiPlayer() { _driver = 0; _map_mt32_to_gm = false; + _adlibPatches = NULL; + + _adLibMusic = false; _enable_sfx = true; _current = 0; @@ -52,9 +65,11 @@ MidiPlayer::MidiPlayer() { _paused = false; _currentTrack = 255; - _loopTrackDefault = false; + _loopTrack = 0; _queuedTrack = 255; _loopQueuedTrack = 0; + + _musicMode = kMusicModeDisabled; } MidiPlayer::~MidiPlayer() { @@ -68,14 +83,160 @@ MidiPlayer::~MidiPlayer() { } _driver = NULL; clearConstructs(); + unloadAdlibPatches(); } -int MidiPlayer::open(int gameType) { +int MidiPlayer::open(int gameType, bool isDemo) { // Don't call open() twice! assert(!_driver); - // Setup midi driver - MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM)); + Common::String accoladeDriverFilename; + MusicType musicType = MT_INVALID; + + switch (gameType) { + case GType_ELVIRA1: + _musicMode = kMusicModeAccolade; + accoladeDriverFilename = "INSTR.DAT"; + break; + case GType_ELVIRA2: + case GType_WW: + // Attention: Elvira 2 shipped with INSTR.DAT and MUSIC.DRV + // MUSIC.DRV is the correct one. INSTR.DAT seems to be a left-over + _musicMode = kMusicModeAccolade; + accoladeDriverFilename = "MUSIC.DRV"; + break; + case GType_SIMON1: + if (isDemo) { + _musicMode = kMusicModeAccolade; + accoladeDriverFilename = "MUSIC.DRV"; + } + break; + case GType_SIMON2: + //_musicMode = kMusicModeMilesAudio; + // currently disabled, because there are a few issues + // MT32 seems to work fine now, AdLib seems to use bad instruments and is also outputting music on + // the right speaker only. The original driver did initialize the panning to 0 and the Simon2 XMIDI + // tracks don't set panning at all. We can reset panning to be centered, which would solve this + // issue, but we still don't know who's setting it in the original interpreter. + break; + default: + break; + } + + MidiDriver::DeviceHandle dev; + int ret = 0; + + if (_musicMode != kMusicModeDisabled) { + dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + musicType = MidiDriver::getMusicType(dev); + + switch (musicType) { + case MT_ADLIB: + case MT_MT32: + break; + case MT_GM: + if (!ConfMan.getBool("native_mt32")) { + // Not a real MT32 / no MUNT + ::GUI::MessageDialog dialog(("You appear to be using a General MIDI device,\n" + "but your game only supports Roland MT32 MIDI.\n" + "We try to map the Roland MT32 instruments to\n" + "General MIDI ones. It is still possible that\n" + "some tracks sound incorrect.")); + dialog.runModal(); + } + // Switch to MT32 driver in any case + musicType = MT_MT32; + break; + default: + _musicMode = kMusicModeDisabled; + break; + } + } + + switch (_musicMode) { + case kMusicModeAccolade: { + // Setup midi driver + switch (musicType) { + case MT_ADLIB: + _driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename); + break; + case MT_MT32: + _driver = MidiDriver_Accolade_MT32_create(accoladeDriverFilename); + break; + default: + assert(0); + break; + } + if (!_driver) + return 255; + + ret = _driver->open(); + if (ret == 0) { + // Reset is done inside our MIDI driver + _driver->setTimerCallback(this, &onTimer); + } + + //setTimerRate(_driver->getBaseTempo()); + return 0; + } + + case kMusicModeMilesAudio: { + switch (musicType) { + case MT_ADLIB: { + Common::File instrumentDataFile; + if (instrumentDataFile.exists("MIDPAK.AD")) { + // if there is a file called MIDPAK.AD, use it directly + warning("SIMON 2: using MIDPAK.AD"); + _driver = Audio::MidiDriver_Miles_AdLib_create("MIDPAK.AD", "MIDPAK.AD"); + } else { + // if there is no file called MIDPAK.AD, try to extract it from the file SETUP.SHR + // if we didn't do this, the user would be forced to "install" the game instead of simply + // copying all files from CD-ROM. + Common::SeekableReadStream *midpakAdLibStream; + midpakAdLibStream = simon2SetupExtractFile("MIDPAK.AD"); + if (!midpakAdLibStream) + error("MidiPlayer: could not extract MIDPAK.AD from SETUP.SHR"); + + // Pass this extracted data to the driver + warning("SIMON 2: using MIDPAK.AD extracted from SETUP.SHR"); + _driver = Audio::MidiDriver_Miles_AdLib_create("", "", midpakAdLibStream); + delete midpakAdLibStream; + } + // TODO: not sure what's going wrong with AdLib + // it doesn't seem to matter if we use the regular XMIDI tracks or the 2nd set meant for MT32 + break; + } + case MT_MT32: + _driver = Audio::MidiDriver_Miles_MT32_create(""); + _nativeMT32 = true; // use 2nd set of XMIDI tracks + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _driver = Audio::MidiDriver_Miles_MT32_create(""); + _nativeMT32 = true; // use 2nd set of XMIDI tracks + } + break; + + default: + break; + } + if (!_driver) + return 255; + + ret = _driver->open(); + if (ret == 0) { + // Reset is done inside our MIDI driver + _driver->setTimerCallback(this, &onTimer); + } + return 0; + } + + default: + break; + } + + dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM)); + _adLibMusic = (MidiDriver::getMusicType(dev) == MT_ADLIB); _nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32")); _driver = MidiDriver::createMidi(dev); @@ -85,9 +246,15 @@ int MidiPlayer::open(int gameType) { if (_nativeMT32) _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + /* Disabled due to not sounding right, and low volume level + if (gameType == GType_SIMON1 && MidiDriver::getMusicType(dev) == MT_ADLIB) { + loadAdlibPatches(); + } + */ + _map_mt32_to_gm = (gameType != GType_SIMON2 && !_nativeMT32); - int ret = _driver->open(); + ret = _driver->open(); if (ret) return ret; _driver->setTimerCallback(this, &onTimer); @@ -104,6 +271,12 @@ void MidiPlayer::send(uint32 b) { if (!_current) return; + if (_musicMode != kMusicModeDisabled) { + // Send directly to Accolade/Miles Audio driver + _driver->send(b); + return; + } + byte channel = (byte)(b & 0x0F); if ((b & 0xFFF0) == 0x07B0) { // Adjust volume changes by master music and master sfx volume. @@ -114,8 +287,10 @@ void MidiPlayer::send(uint32 b) { else if (_current == &_music) volume = volume * _musicVolume / 255; b = (b & 0xFF00FFFF) | (volume << 16); - } else if ((b & 0xF0) == 0xC0 && _map_mt32_to_gm) { - b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); + } else if ((b & 0xF0) == 0xC0) { + if (_map_mt32_to_gm && !_adlibPatches) { + b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); + } } else if ((b & 0xFFF0) == 0x007BB0) { // Only respond to an All Notes Off if this channel // has already been allocated. @@ -135,8 +310,10 @@ void MidiPlayer::send(uint32 b) { _current->volume[channel] = 127; } + // Allocate channels if needed if (!_current->channel[channel]) _current->channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + if (_current->channel[channel]) { if (channel == 9) { if (_current == &_sfx) @@ -144,7 +321,16 @@ void MidiPlayer::send(uint32 b) { else if (_current == &_music) _current->channel[9]->volume(_current->volume[9] * _musicVolume / 255); } - _current->channel[channel]->send(b); + + if ((b & 0xF0) == 0xC0 && _adlibPatches) { + // NOTE: In the percussion channel, this function is a + // no-op. Any percussion instruments you hear may + // be the stock ones from adlib.cpp. + _driver->sysEx_customInstrument(_current->channel[channel]->getNumber(), 'ADL ', _adlibPatches + 30 * ((b >> 8) & 0xFF)); + } else { + _current->channel[channel]->send(b); + } + if ((b & 0xFFF0) == 0x79B0) { // We have received a "Reset All Controllers" message // and passed it on to the MIDI driver. This may or may @@ -166,13 +352,13 @@ void MidiPlayer::metaEvent(byte type, byte *data, uint16 length) { return; } else if (_current == &_sfx) { clearConstructs(_sfx); - } else if (_current->loopTrack) { + } else if (_loopTrack) { _current->parser->jumpToTick(0); } else if (_queuedTrack != 255) { _currentTrack = 255; byte destination = _queuedTrack; _queuedTrack = 255; - _current->loopTrack = _loopQueuedTrack; + _loopTrack = _loopQueuedTrack; _loopQueuedTrack = false; // Remember, we're still inside the locked mutex. @@ -300,7 +486,7 @@ void MidiPlayer::setVolume(int musicVol, int sfxVol) { void MidiPlayer::setLoop(bool loop) { Common::StackLock lock(_mutex); - _loopTrackDefault = loop; + _loopTrack = loop; } void MidiPlayer::queueTrack(int track, bool loop) { @@ -355,6 +541,47 @@ void MidiPlayer::resetVolumeTable() { } } +void MidiPlayer::loadAdlibPatches() { + Common::File ibk; + + if (!ibk.open("mt_fm.ibk")) + return; + + if (ibk.readUint32BE() == 0x49424b1a) { + _adlibPatches = new byte[128 * 30]; + byte *ptr = _adlibPatches; + + memset(_adlibPatches, 0, 128 * 30); + + for (int i = 0; i < 128; i++) { + byte instr[16]; + + ibk.read(instr, 16); + + ptr[0] = instr[0]; // Modulator Sound Characteristics + ptr[1] = instr[2]; // Modulator Scaling/Output Level + ptr[2] = ~instr[4]; // Modulator Attack/Decay + ptr[3] = ~instr[6]; // Modulator Sustain/Release + ptr[4] = instr[8]; // Modulator Wave Select + ptr[5] = instr[1]; // Carrier Sound Characteristics + ptr[6] = instr[3]; // Carrier Scaling/Output Level + ptr[7] = ~instr[5]; // Carrier Attack/Delay + ptr[8] = ~instr[7]; // Carrier Sustain/Release + ptr[9] = instr[9]; // Carrier Wave Select + ptr[10] = instr[10]; // Feedback/Connection + + // The remaining six bytes are reserved for future use + + ptr += 30; + } + } +} + +void MidiPlayer::unloadAdlibPatches() { + delete[] _adlibPatches; + _adlibPatches = NULL; +} + static const int simon1_gmf_size[] = { 8900, 12166, 2848, 3442, 4034, 4508, 7064, 9730, 6014, 4742, 3138, 6570, 5384, 8909, 6457, 16321, 2742, 8968, 4804, 8442, 7717, @@ -426,9 +653,11 @@ void MidiPlayer::loadSMF(Common::File *in, int song, bool sfx) { // It seems that 4 corresponds to our base tempo, so // this should be the right way to calculate it. timerRate = (4 * _driver->getBaseTempo()) / p->data[5]; - p->loopTrack = (p->data[6] != 0); - } else { - p->loopTrack = _loopTrackDefault; + + // According to bug #1004919 calling setLoop() from + // within a lock causes a lockup, though I have no + // idea when this actually happens. + _loopTrack = (p->data[6] != 0); } MidiParser *parser = MidiParser::createParser_SMF(); @@ -498,8 +727,6 @@ void MidiPlayer::loadMultipleSMF(Common::File *in, bool sfx) { p->song_sizes[i] = size; } - p->loopTrack = _loopTrackDefault; - if (!sfx) { _currentTrack = 255; resetVolumeTable(); @@ -531,7 +758,6 @@ void MidiPlayer::loadXMIDI(Common::File *in, bool sfx) { in->seek(pos, 0); p->data = (byte *)calloc(size, 1); in->read(p->data, size); - p->loopTrack = _loopTrackDefault; } else { error("Expected 'FORM' tag but found '%c%c%c%c' instead", buf[0], buf[1], buf[2], buf[3]); } @@ -576,8 +802,105 @@ void MidiPlayer::loadS1D(Common::File *in, bool sfx) { _currentTrack = 255; resetVolumeTable(); } - p->loopTrack = _loopTrackDefault; p->parser = parser; // That plugs the power cord into the wall } +#define MIDI_SETUP_BUNDLE_HEADER_SIZE 56 +#define MIDI_SETUP_BUNDLE_FILEHEADER_SIZE 48 +#define MIDI_SETUP_BUNDLE_FILENAME_MAX_SIZE 12 + +// PKWARE data compression library (called "DCL" in ScummVM) was used for storing files within SETUP.SHR +// we need it to be able to get the file MIDPAK.AD, otherwise we would have to require the user +// to "install" the game before being able to actually play it, when using AdLib. +// +// SETUP.SHR file format: +// [bundle file header] +// [compressed file header] [compressed file data] +// * compressed file count +Common::SeekableReadStream *MidiPlayer::simon2SetupExtractFile(const Common::String &requestedFileName) { + Common::File *setupBundleStream = new Common::File(); + uint32 bundleSize = 0; + uint32 bundleBytesLeft = 0; + byte bundleHeader[MIDI_SETUP_BUNDLE_HEADER_SIZE]; + byte bundleFileHeader[MIDI_SETUP_BUNDLE_FILEHEADER_SIZE]; + uint16 bundleFileCount = 0; + uint16 bundleFileNr = 0; + + Common::String fileName; + uint32 fileCompressedSize = 0; + byte *fileCompressedDataPtr = nullptr; + + Common::SeekableReadStream *extractedStream = nullptr; + + if (!setupBundleStream->open("setup.shr")) + error("MidiPlayer: could not open setup.shr"); + + bundleSize = setupBundleStream->size(); + bundleBytesLeft = bundleSize; + + if (bundleSize < MIDI_SETUP_BUNDLE_HEADER_SIZE) + error("MidiPlayer: unexpected EOF in setup.shr"); + + if (setupBundleStream->read(bundleHeader, MIDI_SETUP_BUNDLE_HEADER_SIZE) != MIDI_SETUP_BUNDLE_HEADER_SIZE) + error("MidiPlayer: setup.shr read error"); + bundleBytesLeft -= MIDI_SETUP_BUNDLE_HEADER_SIZE; + + // Verify header byte + if (bundleHeader[13] != 't') + error("MidiPlayer: setup.shr bundle header data mismatch"); + + bundleFileCount = READ_LE_UINT16(&bundleHeader[14]); + + // Search for requested file + while (bundleFileNr < bundleFileCount) { + if (bundleBytesLeft < sizeof(bundleFileHeader)) + error("MidiPlayer: unexpected EOF in setup.shr"); + + if (setupBundleStream->read(bundleFileHeader, sizeof(bundleFileHeader)) != sizeof(bundleFileHeader)) + error("MidiPlayer: setup.shr read error"); + bundleBytesLeft -= MIDI_SETUP_BUNDLE_FILEHEADER_SIZE; + + // Extract filename from file-header + fileName.clear(); + for (byte curPos = 0; curPos < MIDI_SETUP_BUNDLE_FILENAME_MAX_SIZE; curPos++) { + if (!bundleFileHeader[curPos]) // terminating NUL + break; + fileName.insertChar(bundleFileHeader[curPos], curPos); + } + + // Get compressed + fileCompressedSize = READ_LE_UINT32(&bundleFileHeader[20]); + if (!fileCompressedSize) + error("MidiPlayer: compressed file is 0 bytes, data corruption?"); + if (bundleBytesLeft < fileCompressedSize) + error("MidiPlayer: unexpected EOF in setup.shr"); + + if (fileName == requestedFileName) { + // requested file found + fileCompressedDataPtr = new byte[fileCompressedSize]; + + if (setupBundleStream->read(fileCompressedDataPtr, fileCompressedSize) != fileCompressedSize) + error("MidiPlayer: setup.shr read error"); + + Common::MemoryReadStream *compressedStream = nullptr; + + compressedStream = new Common::MemoryReadStream(fileCompressedDataPtr, fileCompressedSize); + // we don't know the unpacked size, let decompressor figure it out + extractedStream = Common::decompressDCL(compressedStream); + delete compressedStream; + break; + } + + // skip compressed size + setupBundleStream->skip(fileCompressedSize); + bundleBytesLeft -= fileCompressedSize; + + bundleFileNr++; + } + setupBundleStream->close(); + delete setupBundleStream; + + return extractedStream; +} + } // End of namespace AGOS |