aboutsummaryrefslogtreecommitdiff
path: root/engines/agos/midi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/agos/midi.cpp')
-rw-r--r--engines/agos/midi.cpp369
1 files changed, 339 insertions, 30 deletions
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 045fd9dac5..d11ff201ea 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -23,10 +23,21 @@
#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"
+#include "agos/drivers/simon1/adlib.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 +53,7 @@ MidiPlayer::MidiPlayer() {
_driver = 0;
_map_mt32_to_gm = false;
+ _adLibMusic = false;
_enable_sfx = true;
_current = 0;
@@ -52,9 +64,11 @@ MidiPlayer::MidiPlayer() {
_paused = false;
_currentTrack = 255;
- _loopTrackDefault = false;
+ _loopTrack = 0;
_queuedTrack = 255;
_loopQueuedTrack = 0;
+
+ _musicMode = kMusicModeDisabled;
}
MidiPlayer::~MidiPlayer() {
@@ -70,12 +84,181 @@ MidiPlayer::~MidiPlayer() {
clearConstructs();
}
-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";
+ } else if (Common::File::exists("MT_FM.IBK")) {
+ _musicMode = kMusicModeSimon1;
+ }
+ 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;
+ }
+
+ case kMusicModeSimon1: {
+ // This only handles the original AdLib driver of Simon1.
+ if (musicType == MT_ADLIB) {
+ _adLibMusic = true;
+ _map_mt32_to_gm = false;
+ _nativeMT32 = false;
+
+ _driver = createMidiDriverSimon1AdLib("MT_FM.IBK");
+ if (_driver && _driver->open() == 0) {
+ _driver->setTimerCallback(this, &onTimer);
+ // Like the original, we enable the rhythm support by default.
+ _driver->send(0xB0, 0x67, 0x01);
+ return 0;
+ }
+
+ delete _driver;
+ _driver = nullptr;
+ }
+
+ _musicMode = kMusicModeDisabled;
+ }
+
+ 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);
@@ -87,7 +270,7 @@ int MidiPlayer::open(int gameType) {
_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 +287,33 @@ void MidiPlayer::send(uint32 b) {
if (!_current)
return;
+ if (_musicMode != kMusicModeDisabled) {
+ // Handle volume control for Simon1 output.
+ if (_musicMode == kMusicModeSimon1) {
+ // The driver does not support any volume control, thus we simply
+ // scale the velocities on note on for now.
+ // TODO: We should probably handle this at output level at some
+ // point. Then we can allow volume changes to affect already
+ // playing notes too. For now this simple change allows us to
+ // have some simple volume control though.
+ if ((b & 0xF0) == 0x90) {
+ byte volume = (b >> 16) & 0x7F;
+
+ if (_current == &_sfx) {
+ volume = volume * _sfxVolume / 255;
+ } else if (_current == &_music) {
+ volume = volume * _musicVolume / 255;
+ }
+
+ b = (b & 0xFF00FFFF) | (volume << 16);
+ }
+ }
+
+ // Send directly to Accolade/Miles/Simon1 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.
@@ -135,8 +345,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)
@@ -166,13 +378,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 +512,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) {
@@ -411,24 +623,27 @@ void MidiPlayer::loadSMF(Common::File *in, int song, bool sfx) {
// 1 BYTE : Major version
// 1 BYTE : Minor version
// 1 BYTE : Ticks (Ranges from 2 - 8, always 2 for SFX)
- // 1 BYTE : Loop control. 0 = no loop, 1 = loop
-
- // In the original, the ticks value indicated how many
- // times the music timer was called before it actually
- // did something. The larger the value the slower the
- // music.
- //
- // We, on the other hand, have a timer rate which is
- // used to control by how much the music advances on
- // each onTimer() call. The larger the value, the
- // faster the music.
- //
- // 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;
+ // 1 BYTE : Loop control. 0 = no loop, 1 = loop (Music only)
+ if (!sfx) {
+ // In the original, the ticks value indicated how many
+ // times the music timer was called before it actually
+ // did something. The larger the value the slower the
+ // music.
+ //
+ // We, on the other hand, have a timer rate which is
+ // used to control by how much the music advances on
+ // each onTimer() call. The larger the value, the
+ // faster the music.
+ //
+ // 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];
+
+ // 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 +713,6 @@ void MidiPlayer::loadMultipleSMF(Common::File *in, bool sfx) {
p->song_sizes[i] = size;
}
- p->loopTrack = _loopTrackDefault;
-
if (!sfx) {
_currentTrack = 255;
resetVolumeTable();
@@ -531,7 +744,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 +788,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