diff options
Diffstat (limited to 'engines/agos')
-rw-r--r-- | engines/agos/agos.cpp | 4 | ||||
-rw-r--r-- | engines/agos/detection_tables.h | 8 | ||||
-rw-r--r-- | engines/agos/drivers/accolade/adlib.cpp | 883 | ||||
-rw-r--r-- | engines/agos/drivers/accolade/driverfile.cpp | 166 | ||||
-rw-r--r-- | engines/agos/drivers/accolade/mididriver.h | 44 | ||||
-rw-r--r-- | engines/agos/drivers/accolade/mt32.cpp | 278 | ||||
-rw-r--r-- | engines/agos/gfx.cpp | 7 | ||||
-rw-r--r-- | engines/agos/input.cpp | 1 | ||||
-rw-r--r-- | engines/agos/midi.cpp | 359 | ||||
-rw-r--r-- | engines/agos/midi.h | 22 | ||||
-rw-r--r-- | engines/agos/midiparser_s1d.cpp | 39 | ||||
-rw-r--r-- | engines/agos/module.mk | 3 | ||||
-rw-r--r-- | engines/agos/res_snd.cpp | 5 | ||||
-rw-r--r-- | engines/agos/rooms.cpp | 3 | ||||
-rw-r--r-- | engines/agos/saveload.cpp | 7 | ||||
-rw-r--r-- | engines/agos/sound.cpp | 4 | ||||
-rw-r--r-- | engines/agos/zones.cpp | 3 |
17 files changed, 1796 insertions, 40 deletions
diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp index 6eda2eb9aa..8952e649fd 100644 --- a/engines/agos/agos.cpp +++ b/engines/agos/agos.cpp @@ -585,7 +585,9 @@ Common::Error AGOSEngine::init() { ((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) || (getPlatform() == Common::kPlatformDOS)) { - int ret = _midi->open(getGameType()); + bool isDemo = (getFeatures() & GF_DEMO) ? true : false; + + int ret = _midi->open(getGameType(), isDemo); if (ret) warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret)); diff --git a/engines/agos/detection_tables.h b/engines/agos/detection_tables.h index 2f4709c49e..793d4081cf 100644 --- a/engines/agos/detection_tables.h +++ b/engines/agos/detection_tables.h @@ -1385,7 +1385,7 @@ static const AGOSGameDescription gameDescriptions[] = { { { "simon1", - "Floppy", + "Infocom Floppy", { { "gamepc", GAME_BASEFILE, "9f93d27432ce44a787eef10adb640870", 37070}, @@ -1409,7 +1409,7 @@ static const AGOSGameDescription gameDescriptions[] = { { { "simon1", - "Floppy", + "Infocom Floppy", { { "gamepc", GAME_BASEFILE, "62de24fc579b94fac7d3d23201b65b14", -1}, @@ -1599,11 +1599,11 @@ static const AGOSGameDescription gameDescriptions[] = { GF_TALKIE }, - // Simon the Sorcerer 1 - English DOS CD alternate? + // Simon the Sorcerer 1 - English DOS CD (Infocom) { { "simon1", - "CD", + "Infocom CD", { { "gamepc", GAME_BASEFILE, "c0b948b6821d2140f8b977144f21027a", -1}, diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp new file mode 100644 index 0000000000..294be2b8a7 --- /dev/null +++ b/engines/agos/drivers/accolade/adlib.cpp @@ -0,0 +1,883 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "agos/agos.h" +#include "agos/drivers/accolade/mididriver.h" + +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" + +namespace AGOS { + +#define AGOS_ADLIB_VOICES_COUNT 11 +#define AGOS_ADLIB_VOICES_MELODIC_COUNT 6 +#define AGOS_ADLIB_VOICES_PERCUSSION_START 6 +#define AGOS_ADLIB_VOICES_PERCUSSION_COUNT 5 +#define AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL 9 + +// 5 instruments on top of the regular MIDI ones +// used by the MUSIC.DRV variant for percussion instruments +#define AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT 5 + +const byte operator1Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11 +}; + +const byte operator2Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0xFF, 0xFF, 0xFF, 0xFF +}; + +// percussion: +// voice 6 - base drum - also uses operator 13h +// voice 7 - snare drum +// voice 8 - tom tom +// voice 9 - cymbal +// voice 10 - hi hat +const byte percussionBits[AGOS_ADLIB_VOICES_PERCUSSION_COUNT] = { + 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +// hardcoded, dumped from Accolade music system +// same for INSTR.DAT + MUSIC.DRV, except that MUSIC.DRV does the lookup differently +const byte percussionKeyNoteChannelTable[] = { + 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x0A, 0x08, 0x0A, 0x08, + 0x0A, 0x08, 0x08, 0x09, 0x08, 0x09, 0x0F, 0x0F, 0x0A, 0x0F, + 0x0A, 0x0F, 0x0F, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x0A, 0x0F, 0x0F, 0x08, 0x0F, 0x08 +}; + +struct InstrumentEntry { + byte reg20op1; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg40op1; // Level Key Scaling / Total Level + byte reg60op1; // Attack Rate / Decay Rate + byte reg80op1; // Sustain Level / Release Rate + byte reg20op2; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg40op2; // Level Key Scaling / Total Level + byte reg60op2; // Attack Rate / Decay Rate + byte reg80op2; // Sustain Level / Release Rate + byte regC0; // Feedback / Algorithm, bit 0 - set -> both operators in use +}; + +// hardcoded, dumped from Accolade music system (INSTR.DAT variant) +const uint16 frequencyLookUpTable[12] = { + 0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF, + 0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B +}; + +// hardcoded, dumped from Accolade music system (MUSIC.DRV variant) +const uint16 frequencyLookUpTableMusicDrv[12] = { + 0x0205, 0x0223, 0x0244, 0x0267, 0x028B, 0x02B2, 0x02DB, + 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF +}; + +// +// Accolade adlib music driver +// +// Remarks: +// +// There are at least 2 variants of this sound system. +// One for the games Elvira 1 + Elvira 2 +// It seems it was also used for the game "Altered Destiny" +// Another one for the games Waxworks + Simon, the Sorcerer 1 Demo +// +// First one uses the file INSTR.DAT for instrument data, channel mapping etc. +// Second one uses the file MUSIC.DRV, which actually contains driver code + instrument data + channel mapping, etc. +// +// The second variant supported dynamic channel allocation for the FM voice channels, but this +// feature was at least definitely disabled for Simon, the Sorcerer 1 demo and for the Waxworks demo too. +// +// I have currently not implemented dynamic channel allocation. + +class MidiDriver_Accolade_AdLib : public MidiDriver { +public: + MidiDriver_Accolade_AdLib(); + virtual ~MidiDriver_Accolade_AdLib(); + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } + + void setVolume(byte volume); + virtual uint32 property(int prop, uint32 param); + + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); + + void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); + +private: + bool _musicDrvMode; + + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel + byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments + byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + // from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument + signed char _instrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT]; + // simple mapping between MIDI key notes and MT32 key notes + byte _percussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT]; + + // from INSTR.DAT/MUSIC.DRV - adlib instrument data + InstrumentEntry *_instrumentTable; + byte _instrumentCount; + + struct ChannelEntry { + const InstrumentEntry *currentInstrumentPtr; + byte currentNote; + byte currentA0hReg; + byte currentB0hReg; + int16 volumeAdjust; + + ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0), + currentA0hReg(0), currentB0hReg(0), volumeAdjust(0) { } + }; + + byte _percussionReg; + + OPL::OPL *_opl; + int _masterVolume; + + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + + bool _isOpen; + + // stores information about all FM voice channels + ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT]; + + void onTimer(); + + void resetAdLib(); + void resetAdLibOperatorRegisters(byte baseRegister, byte value); + void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value); + + void programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr); + void programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr); + void setRegister(int reg, int value); + void noteOn(byte FMvoiceChannel, byte note, byte velocity); + void noteOnSetVolume(byte FMvoiceChannel, byte operatorReg, byte adjustedVelocity); + void noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote); +}; + +MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib() + : _masterVolume(15), _opl(0), + _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) { + memset(_channelMapping, 0, sizeof(_channelMapping)); + memset(_instrumentMapping, 0, sizeof(_instrumentMapping)); + memset(_instrumentVolumeAdjust, 0, sizeof(_instrumentVolumeAdjust)); + memset(_percussionKeyNoteMapping, 0, sizeof(_percussionKeyNoteMapping)); + + _instrumentTable = NULL; + _instrumentCount = 0; + _musicDrvMode = false; + _percussionReg = 0x20; +} + +MidiDriver_Accolade_AdLib::~MidiDriver_Accolade_AdLib() { + if (_instrumentTable) { + delete[] _instrumentTable; + _instrumentCount = 0; + } +} + +int MidiDriver_Accolade_AdLib::open() { +// debugC(kDebugLevelAdLibDriver, "AdLib: starting driver"); + + _opl = OPL::Config::create(OPL::Config::kOpl2); + + if (!_opl) + return -1; + + _opl->init(); + + _isOpen = true; + + _opl->start(new Common::Functor0Mem<void, MidiDriver_Accolade_AdLib>(this, &MidiDriver_Accolade_AdLib::onTimer)); + + resetAdLib(); + + // Finally set up default instruments + for (byte FMvoiceNr = 0; FMvoiceNr < AGOS_ADLIB_VOICES_COUNT; FMvoiceNr++) { + if (FMvoiceNr < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Regular FM voices with instrument 0 + programChangeSetInstrument(FMvoiceNr, 0, 0); + } else { + byte percussionInstrument; + if (!_musicDrvMode) { + // INSTR.DAT: percussion voices with instrument 1, 2, 3, 4 and 5 + percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 1; + } else { + // MUSIC.DRV: percussion voices with instrument 0x80, 0x81, 0x82, 0x83 and 0x84 + percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80; + } + programChangeSetInstrument(FMvoiceNr, percussionInstrument, percussionInstrument); + } + } + + // driver initialization does this here: + // INSTR.DAT + // noteOn(9, 0x29, 0); + // noteOff(9, 0x26, false); + // MUSIC.DRV + // noteOn(9, 0x26, 0); + // noteOff(9, 0x26, false); + + return 0; +} + +void MidiDriver_Accolade_AdLib::close() { + delete _opl; + _isOpen = false; +} + +void MidiDriver_Accolade_AdLib::setVolume(byte volume) { + _masterVolume = volume; + //renewNotes(-1, true); +} + +void MidiDriver_Accolade_AdLib::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); +} + +void MidiDriver_Accolade_AdLib::resetAdLib() { + // The original driver sent 0x00 to register 0x00 up to 0xF5 + setRegister(0xBD, 0x00); // Disable rhythm + + // reset FM voice instrument data + resetAdLibOperatorRegisters(0x20, 0); + resetAdLibOperatorRegisters(0x60, 0); + resetAdLibOperatorRegisters(0x80, 0); + resetAdLibFMVoiceChannelRegisters(0xA0, 0); + resetAdLibFMVoiceChannelRegisters(0xB0, 0); + resetAdLibFMVoiceChannelRegisters(0xC0, 0); + resetAdLibOperatorRegisters(0xE0, 0); + resetAdLibOperatorRegisters(0x40, 0x3F); // original driver sent 0x00 + + setRegister(0x01, 0x20); // enable waveform control on both operators + setRegister(0x04, 0x60); // Timer control + + setRegister(0x08, 0); // select FM music mode + setRegister(0xBD, 0x20); // Enable rhythm + + // reset our percussion register + _percussionReg = 0x20; +} + +void MidiDriver_Accolade_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) { + byte operatorIndex; + + for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) { + switch (operatorIndex) { + case 0x06: + case 0x07: + case 0x0E: + case 0x0F: + break; + default: + setRegister(baseRegister + operatorIndex, value); + } + } +} + +void MidiDriver_Accolade_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) { + byte FMvoiceChannel; + + for (FMvoiceChannel = 0; FMvoiceChannel < AGOS_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + setRegister(baseRegister + FMvoiceChannel, value); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Accolade_AdLib::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + byte mappedChannel = _channelMapping[channel]; + byte mappedInstrument = 0; + + // Ignore everything that is outside of our channel range + if (mappedChannel >= AGOS_ADLIB_VOICES_COUNT) + return; + + switch (command) { + case 0x80: + noteOff(mappedChannel, op1, false); + break; + case 0x90: + // Convert noteOn with velocity 0 to a noteOff + if (op2 == 0) + return noteOff(mappedChannel, op1, false); + + noteOn(mappedChannel, op1, op2); + break; + case 0xb0: // Control change + // Doesn't seem to be implemented + break; + case 0xc0: // Program Change + mappedInstrument = _instrumentMapping[op1]; + programChange(mappedChannel, mappedInstrument, op1); + break; + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + // Aftertouch doesn't seem to be implemented + break; + case 0xe0: + // No pitch bend change + break; + case 0xf0: // SysEx + warning("ADLIB: SysEx: %x", b); + break; + default: + warning("ADLIB: Unknown event %02x", command); + } +} + +void MidiDriver_Accolade_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; +} + +void MidiDriver_Accolade_AdLib::noteOn(byte FMvoiceChannel, byte note, byte velocity) { + byte adjustedNote = note; + byte adjustedVelocity = velocity; + byte regValueA0h = 0; + byte regValueB0h = 0; + + // adjust velocity + int16 channelVolumeAdjust = _channels[FMvoiceChannel].volumeAdjust; + channelVolumeAdjust += adjustedVelocity; + channelVolumeAdjust = CLIP<int16>(channelVolumeAdjust, 0, 0x7F); + + // TODO: adjust to global volume + // original drivers had a global volume variable, which was 0 for full volume, -64 for half volume + // and -128 for mute + + adjustedVelocity = channelVolumeAdjust; + + if (!_musicDrvMode) { + // INSTR.DAT + // force note-off + noteOff(FMvoiceChannel, note, true); + + } else { + // MUSIC.DRV + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // force note-off, but only for actual FM voice channels + noteOff(FMvoiceChannel, note, true); + } + } + + if (FMvoiceChannel != 9) { + // regular FM voice + + if (!_musicDrvMode) { + // INSTR.DAT: adjust key note + while (adjustedNote < 24) + adjustedNote += 12; + adjustedNote -= 12; + } + + } else { + // percussion channel + // MUSIC.DRV variant didn't do this adjustment, it directly used a pointer + adjustedNote -= 36; + if (adjustedNote > 40) { // Security check + warning("ADLIB: bad percussion channel note"); + return; + } + + byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote]; + if (percussionChannel >= AGOS_ADLIB_VOICES_COUNT) + return; // INSTR.DAT variant checked for ">" instead of ">=", which seems to have been a bug + + // Map the keynote accordingly + adjustedNote = _percussionKeyNoteMapping[adjustedNote]; + // Now overwrite the FM voice channel + FMvoiceChannel = percussionChannel; + } + + if (!_musicDrvMode) { + // INSTR.DAT + + // Save this key note + _channels[FMvoiceChannel].currentNote = adjustedNote; + + adjustedVelocity += 24; + if (adjustedVelocity > 120) + adjustedVelocity = 120; + adjustedVelocity = adjustedVelocity >> 1; // divide by 2 + + } else { + // MUSIC.DRV + adjustedVelocity = adjustedVelocity >> 1; // divide by 2 + } + + // Set volume of voice channel + noteOnSetVolume(FMvoiceChannel, 1, adjustedVelocity); + if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Set second operator for FM voices + first percussion + noteOnSetVolume(FMvoiceChannel, 2, adjustedVelocity); + } + + if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Percussion + byte percussionIdx = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START; + + // Enable bit of the requested percussion type + assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT); + _percussionReg |= percussionBits[percussionIdx]; + setRegister(0xBD, _percussionReg); + } + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL) { + // FM voice, Base Drum, Snare Drum + Tom Tom + byte adlibNote = adjustedNote; + byte adlibOctave = 0; + byte adlibFrequencyIdx = 0; + uint16 adlibFrequency = 0; + + if (!_musicDrvMode) { + // INSTR.DAT + if (adlibNote >= 0x60) + adlibNote = 0x5F; + + adlibOctave = (adlibNote / 12) - 1; + adlibFrequencyIdx = adlibNote % 12; + adlibFrequency = frequencyLookUpTable[adlibFrequencyIdx]; + + if (adlibFrequency & 0x8000) + adlibOctave++; + if (adlibOctave & 0x80) { + adlibOctave++; + adlibFrequency = adlibFrequency >> 1; + } + + } else { + // MUSIC.DRV variant + if (adlibNote >= 19) + adlibNote -= 19; + + adlibOctave = (adlibNote / 12); + adlibFrequencyIdx = adlibNote % 12; + // additional code, that will lookup octave and do a multiplication with it + // noteOn however calls the frequency calculation in a way that it multiplies with 0 + adlibFrequency = frequencyLookUpTableMusicDrv[adlibFrequencyIdx]; + } + + regValueA0h = adlibFrequency & 0xFF; + regValueB0h = ((adlibFrequency & 0x300) >> 8) | (adlibOctave << 2); + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set Key-On flag for regular FM voices, but not for percussion + regValueB0h |= 0x20; + } + + setRegister(0xA0 + FMvoiceChannel, regValueA0h); + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + _channels[FMvoiceChannel].currentA0hReg = regValueA0h; + _channels[FMvoiceChannel].currentB0hReg = regValueB0h; + + if (_musicDrvMode) { + // MUSIC.DRV + if (FMvoiceChannel < AGOS_ADLIB_VOICES_MELODIC_COUNT) { + _channels[FMvoiceChannel].currentNote = adjustedNote; + } + } + } +} + +// 100% the same for INSTR.DAT and MUSIC.DRV variants +// except for a bug, that was introduced for MUSIC.DRV +void MidiDriver_Accolade_AdLib::noteOnSetVolume(byte FMvoiceChannel, byte operatorNr, byte adjustedVelocity) { + byte operatorReg = 0; + byte regValue40h = 0; + const InstrumentEntry *curInstrument = NULL; + + regValue40h = (63 - adjustedVelocity) & 0x3F; + + if ((operatorNr == 1) && (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START)) { + // first operator of FM voice channels or first percussion channel + curInstrument = _channels[FMvoiceChannel].currentInstrumentPtr; + if (!(curInstrument->regC0 & 0x01)) { // check, if both operators produce sound + // only one does, instrument wants fixed volume + if (operatorNr == 1) { + regValue40h = curInstrument->reg40op1; + } else { + regValue40h = curInstrument->reg40op2; + } + + // not sure, if we are supposed to implement these bugs, or not +#if 0 + if (!_musicDrvMode) { + // Table is 16 bytes instead of 18 bytes + if ((FMvoiceChannel == 7) || (FMvoiceChannel == 9)) { + regValue40h = 0; + warning("volume set bug (original)"); + } + } + if (_musicDrvMode) { + // MUSIC.DRV variant has a bug, which will overwrite these registers + // for all operators above 11 / 0Bh, which means percussion will always + // get a value of 0 (the table holding those bytes was 12 bytes instead of 18 + if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) { + regValue40h = 0; + warning("volume set bug (original)"); + } + } +#endif + } + } + + if (operatorNr == 1) { + operatorReg = operator1Register[FMvoiceChannel]; + } else { + operatorReg = operator2Register[FMvoiceChannel]; + } + assert(operatorReg != 0xFF); // Security check + setRegister(0x40 + operatorReg, regValue40h); +} + +void MidiDriver_Accolade_AdLib::noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote) { + byte adjustedNote = note; + byte regValueB0h = 0; + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // regular FM voice + + if (!_musicDrvMode) { + // INSTR.DAT: adjust key note + while (adjustedNote < 24) + adjustedNote += 12; + adjustedNote -= 12; + } + + if (!dontCheckNote) { + // check, if current note is also the current actually playing channel note + if (_channels[FMvoiceChannel].currentNote != adjustedNote) + return; // not the same -> ignore this note off command + } + + regValueB0h = _channels[FMvoiceChannel].currentB0hReg & 0xDF; // Remove "key on" bit + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + + } else { + // percussion + adjustedNote -= 36; + if (adjustedNote > 40) { // Security check + warning("ADLIB: bad percussion channel note"); + return; + } + + byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote]; + if (percussionChannel > AGOS_ADLIB_VOICES_COUNT) + return; + + byte percussionIdx = percussionChannel - AGOS_ADLIB_VOICES_PERCUSSION_START; + + // Disable bit of the requested percussion type + assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT); + _percussionReg &= ~percussionBits[percussionIdx]; + setRegister(0xBD, _percussionReg); + } +} + +void MidiDriver_Accolade_AdLib::programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; // out of range + } + + // setup instrument + //warning("ADLIB: program change for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Regular FM voice + programChangeSetInstrument(FMvoiceChannel, mappedInstrumentNr, MIDIinstrumentNr); + + } else { + // Percussion + // set default instrument (again) + byte percussionInstrumentNr = 0; + const InstrumentEntry *instrumentPtr; + + if (!_musicDrvMode) { + // INSTR.DAT: percussion default instruments start at instrument 1 + percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 1; + } else { + // MUSIC.DRV: percussion default instruments start at instrument 0x80 + percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80; + } + if (percussionInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; + } + instrumentPtr = &_instrumentTable[percussionInstrumentNr]; + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[percussionInstrumentNr]; + } +} + +void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + const InstrumentEntry *instrumentPtr; + byte op1Reg = 0; + byte op2Reg = 0; + + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; // out of range + } + + // setup instrument + instrumentPtr = &_instrumentTable[mappedInstrumentNr]; + //warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + op1Reg = operator1Register[FMvoiceChannel]; + op2Reg = operator2Register[FMvoiceChannel]; + + setRegister(0x20 + op1Reg, instrumentPtr->reg20op1); + setRegister(0x40 + op1Reg, instrumentPtr->reg40op1); + setRegister(0x60 + op1Reg, instrumentPtr->reg60op1); + setRegister(0x80 + op1Reg, instrumentPtr->reg80op1); + + if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set 2nd operator as well for FM voices and first percussion voice + setRegister(0x20 + op2Reg, instrumentPtr->reg20op2); + setRegister(0x40 + op2Reg, instrumentPtr->reg40op2); + setRegister(0x60 + op2Reg, instrumentPtr->reg60op2); + setRegister(0x80 + op2Reg, instrumentPtr->reg80op2); + + if (!_musicDrvMode) { + // set Feedback / Algorithm as well + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + } else { + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set Feedback / Algorithm as well for regular FM voices only + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + } + } + } + + // Remember instrument + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[MIDIinstrumentNr]; +} + +void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) { + _opl->writeReg(reg, value); + //warning("OPL %x %x (%d)", reg, value, value); +} + +uint32 MidiDriver_Accolade_AdLib::property(int prop, uint32 param) { + return 0; +} + +// Called right at the start, we get an INSTR.DAT entry +bool MidiDriver_Accolade_AdLib::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + uint16 channelMappingOffset = 0; + uint16 channelMappingSize = 0; + uint16 instrumentMappingOffset = 0; + uint16 instrumentMappingSize = 0; + uint16 instrumentVolumeAdjustOffset = 0; + uint16 instrumentVolumeAdjustSize = 0; + uint16 keyNoteMappingOffset = 0; + uint16 keyNoteMappingSize = 0; + uint16 instrumentCount = 0; + uint16 instrumentDataOffset = 0; + uint16 instrumentDataSize = 0; + uint16 instrumentEntrySize = 0; + + if (!useMusicDrvFile) { + // INSTR.DAT: we expect at least 354 bytes + if (driverDataSize < 354) + return false; + + // Data is like this: + // 128 bytes instrument mapping + // 128 bytes instrument volume adjust (signed!) + // 16 bytes unknown + // 16 bytes channel mapping + // 64 bytes key note mapping (not used for MT32) + // 1 byte instrument count + // 1 byte bytes per instrument + // x bytes no instruments used for MT32 + + channelMappingOffset = 256 + 16; + channelMappingSize = 16; + instrumentMappingOffset = 0; + instrumentMappingSize = 128; + instrumentVolumeAdjustOffset = 128; + instrumentVolumeAdjustSize = 128; + keyNoteMappingOffset = 256 + 16 + 16; + keyNoteMappingSize = 64; + + byte instrDatInstrumentCount = driverData[256 + 16 + 16 + 64]; + byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1]; + + // We expect 9 bytes per instrument + if (instrDatBytesPerInstrument != 9) + return false; + // And we also expect at least one adlib instrument + if (!instrDatInstrumentCount) + return false; + + instrumentCount = instrDatInstrumentCount; + instrumentDataOffset = 256 + 16 + 16 + 64 + 2; + instrumentDataSize = instrDatBytesPerInstrument * instrDatInstrumentCount; + instrumentEntrySize = instrDatBytesPerInstrument; + + } else { + // MUSIC.DRV: we expect at least 468 bytes + if (driverDataSize < 468) + return false; + + // music.drv is basically a driver, but with a few fixed locations for certain data + + channelMappingOffset = 396; + channelMappingSize = 16; + instrumentMappingOffset = 140; + instrumentMappingSize = 128; + instrumentVolumeAdjustOffset = 140 + 128; + instrumentVolumeAdjustSize = 128; + keyNoteMappingOffset = 376 + 36; // adjust by 36, because we adjust keyNote before mapping (see noteOn) + keyNoteMappingSize = 64; + + // seems to have used 128 + 5 instruments + // 128 regular ones and an additional 5 for percussion + instrumentCount = 128 + AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT; + instrumentDataOffset = 722; + instrumentEntrySize = 9; + instrumentDataSize = instrumentCount * instrumentEntrySize; + } + + // Channel mapping + if (channelMappingSize) { + // Get these 16 bytes for MIDI channel mapping + if (channelMappingSize != sizeof(_channelMapping)) + return false; + + memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) { + _channelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_instrumentMapping)) + return false; + + memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) { + _instrumentMapping[instrumentNr] = instrumentNr; + } + + if (instrumentVolumeAdjustSize) { + if (instrumentVolumeAdjustSize != sizeof(_instrumentVolumeAdjust)) + return false; + + memcpy(_instrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize); + } + + // Get key note mapping, if available + if (keyNoteMappingSize) { + if (keyNoteMappingSize != sizeof(_percussionKeyNoteMapping)) + return false; + + memcpy(_percussionKeyNoteMapping, driverData + keyNoteMappingOffset, keyNoteMappingSize); + } + + // Check, if there are enough bytes left to hold all instrument data + if (driverDataSize < (instrumentDataOffset + instrumentDataSize)) + return false; + + // We release previous instrument data, just in case + if (_instrumentTable) + delete[] _instrumentTable; + + _instrumentTable = new InstrumentEntry[instrumentCount]; + _instrumentCount = instrumentCount; + + byte *instrDATReadPtr = driverData + instrumentDataOffset; + InstrumentEntry *instrumentWritePtr = _instrumentTable; + + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(InstrumentEntry)); + instrDATReadPtr += instrumentEntrySize; + instrumentWritePtr++; + } + + // Enable MUSIC.DRV-Mode (slightly different behaviour) + if (useMusicDrvFile) + _musicDrvMode = true; + + if (_musicDrvMode) { + // Extra code for MUSIC.DRV + + // This was done during "programChange" in the original driver + instrumentWritePtr = _instrumentTable; + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + instrumentWritePtr->reg80op1 |= 0x03; // set release rate + instrumentWritePtr->reg80op2 |= 0x03; + instrumentWritePtr++; + } + } + return true; +} + +MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename) { + byte *driverData = NULL; + uint16 driverDataSize = 0; + bool isMusicDrvFile = false; + + MidiDriver_Accolade_readDriver(driverFilename, MT_ADLIB, driverData, driverDataSize, isMusicDrvFile); + if (!driverData) + error("ACCOLADE-ADLIB: error during readDriver()"); + + MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib(); + if (driver) { + if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) { + delete driver; + driver = nullptr; + } + } + + delete[] driverData; + return driver; +} + +} // End of namespace AGOS diff --git a/engines/agos/drivers/accolade/driverfile.cpp b/engines/agos/drivers/accolade/driverfile.cpp new file mode 100644 index 0000000000..4ff2fd550f --- /dev/null +++ b/engines/agos/drivers/accolade/driverfile.cpp @@ -0,0 +1,166 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "agos/agos.h" +#include "audio/mididrv.h" +#include "common/error.h" +#include "common/file.h" + +namespace AGOS { + +// this reads and gets Accolade driver data +// we need it for channel mapping, instrument mapping and other things +// this driver data chunk gets passed to the actual music driver (MT32 / AdLib) +void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile) { + Common::File *driverStream = new Common::File(); + + isMusicDrvFile = false; + + if (!driverStream->open(filename)) { + error("%s: unable to open file", filename.c_str()); + } + + if (filename == "INSTR.DAT") { + // INSTR.DAT: used by Elvira 1 + uint32 streamSize = driverStream->size(); + uint32 streamLeft = streamSize; + uint16 skipChunks = 0; // 1 for MT32, 0 for AdLib + uint16 chunkSize = 0; + + switch (requestedDriverType) { + case MT_ADLIB: + skipChunks = 0; + break; + case MT_MT32: + skipChunks = 1; // Skip one entry for MT32 + break; + default: + assert(0); + break; + } + + do { + if (streamLeft < 2) + error("%s: unexpected EOF", filename.c_str()); + + chunkSize = driverStream->readUint16LE(); + streamLeft -= 2; + + if (streamLeft < chunkSize) + error("%s: unexpected EOF", filename.c_str()); + + if (skipChunks) { + // Skip the chunk + driverStream->skip(chunkSize); + streamLeft -= chunkSize; + + skipChunks--; + } + } while (skipChunks); + + // Seek over the ASCII string until there is a NUL terminator + byte curByte = 0; + + do { + if (chunkSize == 0) + error("%s: no actual instrument data found", filename.c_str()); + + curByte = driverStream->readByte(); + chunkSize--; + } while (curByte); + + driverDataSize = chunkSize; + + // Read the requested instrument data entry + driverData = new byte[driverDataSize]; + driverStream->read(driverData, driverDataSize); + + } else if (filename == "MUSIC.DRV") { + // MUSIC.DRV / used by Elvira 2 / Waxworks / Simon 1 demo + uint32 streamSize = driverStream->size(); + uint32 streamLeft = streamSize; + uint16 getChunk = 0; // 4 for MT32, 2 for AdLib + + switch (requestedDriverType) { + case MT_ADLIB: + getChunk = 2; + break; + case MT_MT32: + getChunk = 4; + break; + default: + assert(0); + break; + } + + if (streamLeft < 2) + error("%s: unexpected EOF", filename.c_str()); + + uint16 chunkCount = driverStream->readUint16LE(); + streamLeft -= 2; + + if (getChunk >= chunkCount) + error("%s: required chunk not available", filename.c_str()); + + uint16 headerOffset = 2 + (28 * getChunk); + streamLeft -= (28 * getChunk); + + if (streamLeft < 28) + error("%s: unexpected EOF", filename.c_str()); + + // Seek to required chunk + driverStream->seek(headerOffset); + driverStream->skip(20); // skip over name + streamLeft -= 20; + + uint16 musicDrvSignature = driverStream->readUint16LE(); + uint16 musicDrvType = driverStream->readUint16LE(); + uint16 chunkOffset = driverStream->readUint16LE(); + uint16 chunkSize = driverStream->readUint16LE(); + + // Security checks + if (musicDrvSignature != 0xFEDC) + error("%s: chunk signature mismatch", filename.c_str()); + if (musicDrvType != 1) + error("%s: not a music driver", filename.c_str()); + if (chunkOffset >= streamSize) + error("%s: driver chunk points outside of file", filename.c_str()); + + streamLeft = streamSize - chunkOffset; + if (streamLeft < chunkSize) + error("%s: driver chunk is larger than file", filename.c_str()); + + driverDataSize = chunkSize; + + // Read the requested instrument data entry + driverData = new byte[driverDataSize]; + + driverStream->seek(chunkOffset); + driverStream->read(driverData, driverDataSize); + isMusicDrvFile = true; + } + + driverStream->close(); + delete driverStream; +} + +} // End of namespace AGOS diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h new file mode 100644 index 0000000000..253fb6b736 --- /dev/null +++ b/engines/agos/drivers/accolade/mididriver.h @@ -0,0 +1,44 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H +#define AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H + +#include "agos/agos.h" +#include "audio/mididrv.h" +#include "common/error.h" + +namespace AGOS { + +#define AGOS_MIDI_CHANNEL_COUNT 16 +#define AGOS_MIDI_INSTRUMENT_COUNT 128 + +#define AGOS_MIDI_KEYNOTE_COUNT 64 + +extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile); + +extern MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename); +extern MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename); + +} // End of namespace AGOS + +#endif // AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H diff --git a/engines/agos/drivers/accolade/mt32.cpp b/engines/agos/drivers/accolade/mt32.cpp new file mode 100644 index 0000000000..319e0ebf56 --- /dev/null +++ b/engines/agos/drivers/accolade/mt32.cpp @@ -0,0 +1,278 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "agos/agos.h" +#include "agos/drivers/accolade/mididriver.h" + +#include "audio/mididrv.h" + +#include "common/config-manager.h" +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +namespace AGOS { + +class MidiDriver_Accolade_MT32 : public MidiDriver { +public: + MidiDriver_Accolade_MT32(); + virtual ~MidiDriver_Accolade_MT32(); + + // MidiDriver + int open(); + void close(); + bool isOpen() const { return _isOpen; } + + void send(uint32 b); + + MidiChannel *allocateChannel() { + if (_driver) + return _driver->allocateChannel(); + return NULL; + } + MidiChannel *getPercussionChannel() { + if (_driver) + return _driver->getPercussionChannel(); + return NULL; + } + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + if (_driver) + _driver->setTimerCallback(timer_param, timer_proc); + } + + uint32 getBaseTempo() { + if (_driver) { + return _driver->getBaseTempo(); + } + return 1000000 / _baseFreq; + } + +protected: + Common::Mutex _mutex; + MidiDriver *_driver; + bool _nativeMT32; // native MT32, may also be our MUNT, or MUNT over MIDI + + bool _isOpen; + int _baseFreq; + +private: + // simple mapping between MIDI channel and MT32 channel + byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // simple mapping between MIDI instruments and MT32 instruments + byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + +public: + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); +}; + +MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() { + _driver = NULL; + _isOpen = false; + _nativeMT32 = false; + _baseFreq = 250; + + memset(_channelMapping, 0, sizeof(_channelMapping)); + memset(_instrumentMapping, 0, sizeof(_instrumentMapping)); +} + +MidiDriver_Accolade_MT32::~MidiDriver_Accolade_MT32() { + Common::StackLock lock(_mutex); + if (_driver) { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + } + _driver = NULL; +} + +int MidiDriver_Accolade_MT32::open() { + assert(!_driver); + +// debugC(kDebugLevelMT32Driver, "MT32: starting driver"); + + // Setup midi driver + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32); + MusicType musicType = MidiDriver::getMusicType(dev); + + // check, if we got a real MT32 (or MUNT, or MUNT over MIDI) + switch (musicType) { + case MT_MT32: + _nativeMT32 = true; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _nativeMT32 = true; + } + break; + default: + break; + } + + _driver = MidiDriver::createMidi(dev); + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + if (_nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + + return 0; +} + +void MidiDriver_Accolade_MT32::close() { + if (_driver) { + _driver->close(); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Accolade_MT32::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + + if (command == 0xF0) { + if (_driver) { + _driver->send(b); + } + return; + } + + byte mappedChannel = _channelMapping[channel]; + + if (mappedChannel < AGOS_MIDI_CHANNEL_COUNT) { + // channel mapped to an actual MIDI channel, so use that one + b = (b & 0xFFFFFFF0) | mappedChannel; + if (command == 0xC0) { + // Program change + // Figure out the requested instrument + byte midiInstrument = (b >> 8) & 0xFF; + byte mappedInstrument = _instrumentMapping[midiInstrument]; + + // If there is no actual MT32 (or MUNT), we make a second mapping to General MIDI instruments + if (!_nativeMT32) { + mappedInstrument = (MidiDriver::_mt32ToGm[mappedInstrument]); + } + // And replace it + b = (b & 0xFFFF00FF) | (mappedInstrument << 8); + } + + if (_driver) { + _driver->send(b); + } + } +} + +// Called right at the start, we get an INSTR.DAT entry +bool MidiDriver_Accolade_MT32::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + uint16 channelMappingOffset = 0; + uint16 channelMappingSize = 0; + uint16 instrumentMappingOffset = 0; + uint16 instrumentMappingSize = 0; + + if (!useMusicDrvFile) { + // INSTR.DAT: we expect at least 354 bytes + if (driverDataSize < 354) + return false; + + // Data is like this: + // 128 bytes instrument mapping + // 128 bytes instrument volume adjust (signed!) (not used for MT32) + // 16 bytes unknown + // 16 bytes channel mapping + // 64 bytes key note mapping (not really used for MT32) + // 1 byte instrument count + // 1 byte bytes per instrument + // x bytes no instruments used for MT32 + + channelMappingOffset = 256 + 16; + channelMappingSize = 16; + instrumentMappingOffset = 0; + instrumentMappingSize = 128; + + } else { + // MUSIC.DRV: we expect at least 468 bytes + if (driverDataSize < 468) + return false; + + channelMappingOffset = 396; + channelMappingSize = 16; + instrumentMappingOffset = 140; + instrumentMappingSize = 128; + } + + // Channel mapping + if (channelMappingSize) { + // Get these 16 bytes for MIDI channel mapping + if (channelMappingSize != sizeof(_channelMapping)) + return false; + + memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) { + _channelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_instrumentMapping)) + return false; + + memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) { + _instrumentMapping[instrumentNr] = instrumentNr; + } + return true; +} + +MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename) { + byte *driverData = NULL; + uint16 driverDataSize = 0; + bool isMusicDrvFile = false; + + MidiDriver_Accolade_readDriver(driverFilename, MT_MT32, driverData, driverDataSize, isMusicDrvFile); + if (!driverData) + error("ACCOLADE-ADLIB: error during readDriver()"); + + MidiDriver_Accolade_MT32 *driver = new MidiDriver_Accolade_MT32(); + if (driver) { + if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) { + delete driver; + driver = nullptr; + } + } + + delete[] driverData; + return driver; +} + +} // End of namespace AGOS diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp index 5a1f9f1917..867842276a 100644 --- a/engines/agos/gfx.cpp +++ b/engines/agos/gfx.cpp @@ -1303,6 +1303,13 @@ void AGOSEngine::setWindowImageEx(uint16 mode, uint16 vgaSpriteId) { } else { setWindowImage(mode, vgaSpriteId); } + + // Amiga versions wait for verb area to be displayed. + if (getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformAmiga && vgaSpriteId == 1) { + _copyScnFlag = 5; + while (_copyScnFlag && !shouldQuit()) + delay(1); + } } void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCase) { diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp index 687a8ef1cf..686b1c35b2 100644 --- a/engines/agos/input.cpp +++ b/engines/agos/input.cpp @@ -707,6 +707,7 @@ bool AGOSEngine::processSpecialKeys() { if (_midiEnabled) { _midi->pause(_musicPaused); } + _mixer->pauseHandle(_modHandle, _musicPaused); syncSoundSettings(); break; case 's': 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 diff --git a/engines/agos/midi.h b/engines/agos/midi.h index 398e445535..edb3402735 100644 --- a/engines/agos/midi.h +++ b/engines/agos/midi.h @@ -33,10 +33,15 @@ class File; namespace AGOS { +enum kMusicMode { + kMusicModeDisabled = 0, + kMusicModeAccolade = 1, + kMusicModeMilesAudio +}; + struct MusicInfo { MidiParser *parser; byte *data; - bool loopTrack; byte num_songs; // For Type 1 SMF resources byte *songs[16]; // For Type 1 SMF resources uint32 song_sizes[16]; // For Type 1 SMF resources @@ -47,7 +52,6 @@ struct MusicInfo { MusicInfo() { clear(); } void clear() { parser = 0; data = 0; num_songs = 0; - loopTrack = false; memset(songs, 0, sizeof(songs)); memset(song_sizes, 0, sizeof(song_sizes)); memset(channel, 0, sizeof(channel)); @@ -73,17 +77,22 @@ protected: // These are only used for music. byte _currentTrack; - bool _loopTrackDefault; + bool _loopTrack; byte _queuedTrack; bool _loopQueuedTrack; + byte *_adlibPatches; + protected: static void onTimer(void *data); void clearConstructs(); void clearConstructs(MusicInfo &info); void resetVolumeTable(); + void loadAdlibPatches(); + void unloadAdlibPatches(); public: + bool _adLibMusic; bool _enable_sfx; public: @@ -109,12 +118,17 @@ public: void setVolume(int musicVol, int sfxVol); public: - int open(int gameType); + int open(int gameType, bool isDemo); // MidiDriver_BASE interface implementation virtual void send(uint32 b); virtual void metaEvent(byte type, byte *data, uint16 length); +private: + kMusicMode _musicMode; + +private: + Common::SeekableReadStream *simon2SetupExtractFile(const Common::String &requestedFileName); }; } // End of namespace AGOS diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp index c2c08bf451..7b9a058efc 100644 --- a/engines/agos/midiparser_s1d.cpp +++ b/engines/agos/midiparser_s1d.cpp @@ -179,12 +179,43 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) { bool MidiParser_S1D::loadMusic(byte *data, uint32 size) { unloadMusic(); + if (!size) + return false; + // The original actually just ignores the first two bytes. byte *pos = data; - if (*(pos++) != 0xFC) - debug(1, "Expected 0xFC header but found 0x%02X instead", (int) *pos); - - pos += 1; + if (*pos == 0xFC) { + // SysEx found right at the start + // this seems to happen since Elvira 2, we ignore it + // 3rd byte after the SysEx seems to be saved into a global + + // We expect at least 4 bytes in total + if (size < 4) + return false; + + byte skipOffset = pos[2]; // get second byte after the SysEx + // pos[1] seems to have been ignored + // pos[3] is saved into a global inside the original interpreters + + // Waxworks + Simon 1 demo typical header is: + // 0xFC 0x29 0x07 0x01 [0x00/0x01] + // Elvira 2 typical header is: + // 0xFC 0x04 0x06 0x06 + + if (skipOffset >= 6) { + // should be at least 6, so that we skip over the 2 size bytes and the + // smallest SysEx possible + skipOffset -= 2; // 2 size bytes were already read by previous code outside of this method + + if (size <= skipOffset) // Skip to the end of file? -> something is not correct + return false; + + // Do skip over the bytes + pos += skipOffset; + } else { + warning("MidiParser_S1D: unexpected skip offset in music file"); + } + } // And now we're at the actual data. Only one track. _numTracks = 1; diff --git a/engines/agos/module.mk b/engines/agos/module.mk index 7069d8005b..6d4e72e433 100644 --- a/engines/agos/module.mk +++ b/engines/agos/module.mk @@ -1,6 +1,9 @@ MODULE := engines/agos MODULE_OBJS := \ + drivers/accolade/adlib.o \ + drivers/accolade/driverfile.o \ + drivers/accolade/mt32.o \ agos.o \ charset.o \ charset-fontdata.o \ diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp index 5d6ab60c8b..d04f1735d6 100644 --- a/engines/agos/res_snd.cpp +++ b/engines/agos/res_snd.cpp @@ -220,6 +220,7 @@ void AGOSEngine::playModule(uint16 music) { } _mixer->playStream(Audio::Mixer::kMusicSoundType, &_modHandle, audioStream); + _mixer->pauseHandle(_modHandle, _musicPaused); } void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) { @@ -309,7 +310,9 @@ void AGOSEngine::stopMusic() { } void AGOSEngine::playSting(uint16 soundId) { - if (!_midi->_enable_sfx) + // The sound effects in floppy disk version of + // Simon the Sorcerer 1 are only meant for AdLib + if (!_midi->_adLibMusic || !_midi->_enable_sfx) return; char filename[15]; diff --git a/engines/agos/rooms.cpp b/engines/agos/rooms.cpp index 6185653d42..d1d6f2b99d 100644 --- a/engines/agos/rooms.cpp +++ b/engines/agos/rooms.cpp @@ -383,7 +383,7 @@ bool AGOSEngine::loadRoomItems(uint16 room) { for (uint16 z = minNum; z <= maxNum; z++) { uint16 itemNum = z + 2; item = derefItem(itemNum); - item->parent = 0; + _itemArrayPtr[itemNum] = 0; uint16 num = (itemNum - _itemArrayInited); _roomStates[num].state = item->state; @@ -453,6 +453,7 @@ bool AGOSEngine::loadRoomItems(uint16 room) { item->classFlags = _roomStates[num].classFlags; SubRoom *subRoom = (SubRoom *)findChildOfType(item, kRoomType); subRoom->roomExitStates = _roomStates[num].roomExitStates; + } in.close(); diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp index 5d5e2d7b03..b968ae645c 100644 --- a/engines/agos/saveload.cpp +++ b/engines/agos/saveload.cpp @@ -1261,7 +1261,6 @@ bool AGOSEngine_Elvira2::loadGame(const Common::String &filename, bool restartMo uint16 room = _currentRoom; _currentRoom = f->readUint16BE(); - if (_roomsListPtr) { byte *p = _roomsListPtr; if (room == _currentRoom) { @@ -1293,8 +1292,7 @@ bool AGOSEngine_Elvira2::loadGame(const Common::String &filename, bool restartMo for (uint16 z = minNum; z <= maxNum; z++) { uint16 itemNum = z + 2; - Item *item = derefItem(itemNum); - item->parent = 0; + _itemArrayPtr[itemNum] = 0; } } } @@ -1318,6 +1316,9 @@ bool AGOSEngine_Elvira2::loadGame(const Common::String &filename, bool restartMo uint parent = f->readUint16BE(); uint next = f->readUint16BE(); + if (getGameType() == GType_WW && getPlatform() == Common::kPlatformDOS && derefItem(item->parent) == NULL) + item->parent = 0; + parent_item = derefItem(parent); setItemParent(item, parent_item); diff --git a/engines/agos/sound.cpp b/engines/agos/sound.cpp index 812f46504f..762f60bd91 100644 --- a/engines/agos/sound.cpp +++ b/engines/agos/sound.cpp @@ -515,7 +515,7 @@ void Sound::readSfxFile(const Common::String &filename) { // This method is only used by Simon2 void Sound::loadSfxTable(const char *gameFilename, uint32 base) { - stopAll(); + stopAllSfx(); delete _effects; const bool dataIsUnsigned = true; @@ -684,7 +684,7 @@ void Sound::playRawData(byte *soundData, uint sound, uint size, uint freq) { memcpy(buffer, soundData, size); byte flags = 0; - if (_vm->getPlatform() == Common::kPlatformDOS) + if (_vm->getPlatform() == Common::kPlatformDOS && _vm->getGameId() != GID_ELVIRA2) flags = Audio::FLAG_UNSIGNED; Audio::AudioStream *stream = Audio::makeRawStream(buffer, size, freq, flags); diff --git a/engines/agos/zones.cpp b/engines/agos/zones.cpp index 1644213579..5a753d9b4b 100644 --- a/engines/agos/zones.cpp +++ b/engines/agos/zones.cpp @@ -94,8 +94,7 @@ void AGOSEngine::loadZone(uint16 zoneNum, bool useError) { vpe->sfxFile = NULL; - if ((getPlatform() == Common::kPlatformAmiga || getPlatform() == Common::kPlatformAtariST) && - getGameType() == GType_ELVIRA2) { + if (getGameType() == GType_ELVIRA2) { // A singe sound file is used for Amiga and AtariST versions if (loadVGASoundFile(1, 3)) { vpe->sfxFile = _block; |