diff options
author | Martin Kiewitz | 2015-06-21 00:45:45 +0200 |
---|---|---|
committer | Martin Kiewitz | 2015-06-21 00:45:45 +0200 |
commit | d24c68c739dadd1404d937f9a21d93e8841a09ee (patch) | |
tree | be811b96fb4e31b732709d0741cde6be120c1688 /engines/agos | |
parent | 4a88c69b5dedc4acac8bcb9609c1cf44c497c4ff (diff) | |
download | scummvm-rg350-d24c68c739dadd1404d937f9a21d93e8841a09ee.tar.gz scummvm-rg350-d24c68c739dadd1404d937f9a21d93e8841a09ee.tar.bz2 scummvm-rg350-d24c68c739dadd1404d937f9a21d93e8841a09ee.zip |
AGOS: implement Accolade AdLib + MT32 music drivers
- both known variants are supported (INSTR.DAT + MUSIC.DRV)
- INSTR.DAT/MUSIC.DRV holds channel mapping, instrument mapping, etc.
- fixed bug inside S1D MidiParser, that ruined some instrument changes
0xFC header was seen as 2 byte header, but it's 4 bytes in Elvira 2
and 5 bytes in Waxworks / Simon 1 demo
- dynamic channel allocation for the MUSIC.DRV adlib driver is not
implemented atm, simply because at least the demos of Waxworks and
Simon 1 do not use this feature
- sound effects of Waxworks are not implemented atm
- note: the game "Altered Destiny" uses Accolade INSTR.DAT variant too
Diffstat (limited to 'engines/agos')
-rw-r--r-- | engines/agos/agos.cpp | 4 | ||||
-rw-r--r-- | engines/agos/drivers/accolade/adlib.cpp | 873 | ||||
-rw-r--r-- | engines/agos/drivers/accolade/mididriver.h | 45 | ||||
-rw-r--r-- | engines/agos/drivers/accolade/mt32.cpp | 267 | ||||
-rw-r--r-- | engines/agos/midi.cpp | 249 | ||||
-rw-r--r-- | engines/agos/midi.h | 4 | ||||
-rw-r--r-- | engines/agos/midiparser_s1d.cpp | 20 | ||||
-rw-r--r-- | engines/agos/module.mk | 2 |
8 files changed, 1456 insertions, 8 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/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp new file mode 100644 index 0000000000..2ef1264e23 --- /dev/null +++ b/engines/agos/drivers/accolade/adlib.cpp @@ -0,0 +1,873 @@ +/* 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 adlib_Operator1Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11 +}; + +const byte adlib_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 adlib_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 adlib_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 adlib_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 adlib_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 adlib_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_Emulated { +public: + MidiDriver_Accolade_AdLib(Audio::Mixer *mixer); + virtual ~MidiDriver_Accolade_AdLib(); + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + // AudioStream + bool isStereo() const { return false; } + int getRate() const { return _mixer->getOutputRate(); } + int getPolyphony() const { return AGOS_ADLIB_VOICES_COUNT; } + bool hasRhythmChannel() const { return false; } + + // MidiDriver_Emulated + void generateSamples(int16 *buf, int len); + + void setVolume(byte volume); + virtual uint32 property(int prop, uint32 param); + + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); + +private: + bool _musicDrvMode; + + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel + byte _MIDIchannelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments + byte _MIDIinstrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + // from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument + signed char _MIDIinstrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT]; + // simple mapping between MIDI key notes and MT32 key notes + byte _MIDIpercussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT]; + + // from INSTR.DAT/MUSIC.DRV - adlib instrument data + adlib_InstrumentEntry *_instrumentTable; + byte _instrumentCount; + + struct adlib_ChannelEntry { + const adlib_InstrumentEntry *currentInstrumentPtr; + byte currentNote; + byte currentA0hReg; + byte currentB0hReg; + int16 volumeAdjust; + + adlib_ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0), + currentA0hReg(0), currentB0hReg(0), volumeAdjust(0) { } + }; + + byte _percussionReg; + + OPL::OPL *_opl; + int _masterVolume; + + // points to a MIDI channel for each of the new voice channels + byte _voiceChannelMapping[AGOS_ADLIB_VOICES_COUNT]; + + // stores information about all FM voice channels + adlib_ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT]; + +protected: + void onTimer(); + +private: + void resetAdLib(); + void resetAdLib_OperatorRegisters(byte baseRegister, byte value); + void resetAdLib_FMVoiceChannelRegisters(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(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer), _masterVolume(15), _opl(0) { + memset(_MIDIchannelMapping, 0, sizeof(_MIDIchannelMapping)); + memset(_MIDIinstrumentMapping, 0, sizeof(_MIDIinstrumentMapping)); + memset(_MIDIinstrumentVolumeAdjust, 0, sizeof(_MIDIinstrumentVolumeAdjust)); + memset(_MIDIpercussionKeyNoteMapping, 0, sizeof(_MIDIpercussionKeyNoteMapping)); + + _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() { + int rate = _mixer->getOutputRate(); + +// debugC(kDebugLevelAdLibDriver, "AdLib: starting driver"); + + _opl = OPL::Config::create(OPL::Config::kOpl2); + + if (!_opl) + return -1; + + _opl->init(rate); + + MidiDriver_Emulated::open(); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); + + 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); + + warning("End of open()"); + return 0; +} + +void MidiDriver_Accolade_AdLib::close() { + _mixer->stopHandle(_mixerSoundHandle); + + delete _opl; +} + +void MidiDriver_Accolade_AdLib::setVolume(byte volume) { + _masterVolume = volume; + //renewNotes(-1, true); +} + +void MidiDriver_Accolade_AdLib::onTimer() { +} + +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 + resetAdLib_OperatorRegisters(0x20, 0); + resetAdLib_OperatorRegisters(0x60, 0); + resetAdLib_OperatorRegisters(0x80, 0); + resetAdLib_FMVoiceChannelRegisters(0xA0, 0); + resetAdLib_FMVoiceChannelRegisters(0xB0, 0); + resetAdLib_FMVoiceChannelRegisters(0xC0, 0); + resetAdLib_OperatorRegisters(0xE0, 0); + resetAdLib_OperatorRegisters(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::resetAdLib_OperatorRegisters(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::resetAdLib_FMVoiceChannelRegisters(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 = _MIDIchannelMapping[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 = _MIDIinstrumentMapping[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::generateSamples(int16 *data, int len) { + _opl->readBuffer(data, len); +} + +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 = adlib_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 = _MIDIpercussionKeyNoteMapping[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 |= adlib_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 = adlib_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 = adlib_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 adlib_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 (!_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)"); + } + } + } + } + + if (operatorNr == 1) { + operatorReg = adlib_Operator1Register[FMvoiceChannel]; + } else { + operatorReg = adlib_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 = adlib_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 &= ~adlib_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-existant 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 adlib_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-existant instrument"); + return; + } + instrumentPtr = &_instrumentTable[percussionInstrumentNr]; + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _MIDIinstrumentVolumeAdjust[percussionInstrumentNr]; + } +} + +void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + const adlib_InstrumentEntry *instrumentPtr; + byte op1Reg = 0; + byte op2Reg = 0; + + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existant instrument"); + return; // out of range + } + + // setup instrument + instrumentPtr = &_instrumentTable[mappedInstrumentNr]; + //warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + op1Reg = adlib_Operator1Register[FMvoiceChannel]; + op2Reg = adlib_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 = _MIDIinstrumentVolumeAdjust[MIDIinstrumentNr]; +} + +void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) { + _opl->write(0x220, reg); + _opl->write(0x221, 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(_MIDIchannelMapping)) + return false; + + memcpy(_MIDIchannelMapping, driverData + channelMappingOffset, sizeof(_MIDIchannelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_MIDIchannelMapping); channelNr++) { + _MIDIchannelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_MIDIinstrumentMapping)) + return false; + + memcpy(_MIDIinstrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_MIDIinstrumentMapping); instrumentNr++) { + _MIDIinstrumentMapping[instrumentNr] = instrumentNr; + } + + if (instrumentVolumeAdjustSize) { + if (instrumentVolumeAdjustSize != sizeof(_MIDIinstrumentVolumeAdjust)) + return false; + + memcpy(_MIDIinstrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize); + } + + // Get key note mapping, if available + if (keyNoteMappingSize) { + if (keyNoteMappingSize != sizeof(_MIDIpercussionKeyNoteMapping)) + return false; + + memcpy(_MIDIpercussionKeyNoteMapping, 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 adlib_InstrumentEntry[instrumentCount]; + _instrumentCount = instrumentCount; + + byte *instrDATReadPtr = driverData + instrumentDataOffset; + adlib_InstrumentEntry *instrumentWritePtr = _instrumentTable; + + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(adlib_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() { + return new MidiDriver_Accolade_AdLib(g_system->getMixer()); +} + +bool MidiDriver_Accolade_AdLib_setupInstruments(MidiDriver *driver, byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + return static_cast<MidiDriver_Accolade_AdLib *>(driver)->setupInstruments(driverData, driverDataSize, useMusicDrvFile); +} + +} // 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..81064ec652 --- /dev/null +++ b/engines/agos/drivers/accolade/mididriver.h @@ -0,0 +1,45 @@ +/* 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_SOFTSEQ_MIDIDRIVER_H +#define AGOS_SOFTSEQ_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 MidiDriver *MidiDriver_Accolade_AdLib_create(); +extern bool MidiDriver_Accolade_AdLib_setupInstruments(MidiDriver *driver, byte *instrDATData, uint16 instrDATDataSize, bool useMusicDrvFile); + +extern MidiDriver *MidiDriver_Accolade_MT32_create(); +extern bool MidiDriver_Accolade_MT32_setupInstruments(MidiDriver *driver, byte *instrDATData, uint16 instrDATDataSize, bool useMusicDrvFile); + +} // End of namespace AGOS + +#endif // AGOS_SOFTSEQ_MIDIDRIVER_H diff --git a/engines/agos/drivers/accolade/mt32.cpp b/engines/agos/drivers/accolade/mt32.cpp new file mode 100644 index 0000000000..6567d1d9ef --- /dev/null +++ b/engines/agos/drivers/accolade/mt32.cpp @@ -0,0 +1,267 @@ +/* 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/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 _MT32; + bool _nativeMT32; + + bool _isOpen; + int _baseFreq; + +private: + // simple mapping between MIDI channel and MT32 channel + byte _MIDIchannelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // simple mapping between MIDI instruments and MT32 instruments + byte _MIDIinstrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + +public: + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); + + void MT32SysEx(const byte *&dataPtr, int32 &bytesLeft); +}; + +MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() { + _driver = NULL; + _isOpen = false; + _MT32 = false; + _nativeMT32 = false; + _baseFreq = 250; + + memset(_MIDIchannelMapping, 0, sizeof(_MIDIchannelMapping)); + memset(_MIDIinstrumentMapping, 0, sizeof(_MIDIinstrumentMapping)); + //memset(_MIDIkeyNoteMapping, 0, sizeof(_MIDIkeyNoteMapping)); +} + +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); + + switch (musicType) { + case MT_MT32: + _MT32 = true; + _nativeMT32 = false; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _MT32 = true; + _nativeMT32 = true; + } + break; + default: + break; + } + + _driver = MidiDriver::createMidi(dev); + if (!_driver) + return 255; + + if (_nativeMT32) + _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + int ret = _driver->open(); + if (ret) + return ret; + + if (_MT32) + _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 = _MIDIchannelMapping[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 = _MIDIinstrumentMapping[midiInstrument]; + // 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(_MIDIchannelMapping)) + return false; + + memcpy(_MIDIchannelMapping, driverData + channelMappingOffset, sizeof(_MIDIchannelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_MIDIchannelMapping); channelNr++) { + _MIDIchannelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_MIDIinstrumentMapping)) + return false; + + memcpy(_MIDIinstrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_MIDIinstrumentMapping); instrumentNr++) { + _MIDIinstrumentMapping[instrumentNr] = instrumentNr; + } + return true; +} + +MidiDriver *MidiDriver_Accolade_MT32_create() { + return new MidiDriver_Accolade_MT32(); +} + +bool MidiDriver_Accolade_MT32_setupInstruments(MidiDriver *driver, byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile) { + return static_cast<MidiDriver_Accolade_MT32 *>(driver)->setupInstruments(instrumentData, instrumentDataSize, useMusicDrvFile); +} + +} // End of namespace AGOS diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp index e5875a8353..d75b7b8cb7 100644 --- a/engines/agos/midi.cpp +++ b/engines/agos/midi.cpp @@ -27,6 +27,8 @@ #include "agos/agos.h" #include "agos/midi.h" +#include "agos/drivers/accolade/mididriver.h" + namespace AGOS { @@ -57,6 +59,8 @@ MidiPlayer::MidiPlayer() { _loopTrackDefault = false; _queuedTrack = 255; _loopQueuedTrack = 0; + + _accolade_mode = false; } MidiPlayer::~MidiPlayer() { @@ -73,11 +77,244 @@ MidiPlayer::~MidiPlayer() { unloadAdlibPatches(); } -int MidiPlayer::open(int gameType) { +int MidiPlayer::open(int gameType, bool isDemo) { // Don't call open() twice! assert(!_driver); - // Setup midi driver + bool accolade_useMusicDrvFile = false; + MusicType accolade_musicType = MT_INVALID; + + switch (gameType) { + case GType_ELVIRA1: + case GType_ELVIRA2: + _accolade_mode = true; + break; + case GType_WW: + _accolade_mode = true; + accolade_useMusicDrvFile = true; + break; + case GType_SIMON1: + if (isDemo) { + _accolade_mode = true; + accolade_useMusicDrvFile = true; + } + break; + default: + break; + } + + if (_accolade_mode) { + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + accolade_musicType = MidiDriver::getMusicType(dev); + + switch (accolade_musicType) { + case MT_ADLIB: + case MT_MT32: + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + // Real MT32 + accolade_musicType = MT_MT32; + } else { + _accolade_mode = false; + } + break; + default: + _accolade_mode = false; + break; + } + } + + if (_accolade_mode) { + // Setup midi driver + switch (accolade_musicType) { + case MT_ADLIB: + _driver = MidiDriver_Accolade_AdLib_create(); + break; + case MT_MT32: + _driver = MidiDriver_Accolade_MT32_create(); + break; + default: + assert(0); + break; + } + if (!_driver) + return 255; + + byte *instrumentData = NULL; + uint16 instrumentDataSize = 0; + + if (!accolade_useMusicDrvFile) { + // Elvira 1 / Elvira 2: read INSTR.DAT + Common::File *instrDatStream = new Common::File(); + + if (!instrDatStream->open("INSTR.DAT")) { + error("INSTR.DAT: unable to open file"); + } + + uint32 streamSize = instrDatStream->size(); + uint32 streamLeft = streamSize; + uint16 skipChunks = 0; // 1 for MT32, 0 for AdLib + uint16 chunkSize = 0; + + switch (accolade_musicType) { + 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("INSTR.DAT: unexpected EOF"); + + chunkSize = instrDatStream->readUint16LE(); + streamLeft -= 2; + + if (streamLeft < chunkSize) + error("INSTR.DAT: unexpected EOF"); + + if (skipChunks) { + // Skip the chunk + instrDatStream->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("INSTR.DAT: no actual instrument data found"); + + curByte = instrDatStream->readByte(); + chunkSize--; + } while (curByte); + + instrumentDataSize = chunkSize; + + // Read the requested instrument data entry + instrumentData = new byte[instrumentDataSize]; + instrDatStream->read(instrumentData, instrumentDataSize); + + instrDatStream->close(); + delete instrDatStream; + + } else { + // Waxworks / Simon 1 demo: Read MUSIC.DRV + Common::File *musicDrvStream = new Common::File(); + + if (!musicDrvStream->open("MUSIC.DRV")) { + error("MUSIC.DRV: unable to open file"); + } + + uint32 streamSize = musicDrvStream->size(); + uint32 streamLeft = streamSize; + uint16 getChunk = 0; // 4 for MT32, 2 for AdLib + + switch (accolade_musicType) { + case MT_ADLIB: + getChunk = 2; + break; + case MT_MT32: + getChunk = 4; + break; + default: + assert(0); + break; + } + + if (streamLeft < 2) + error("MUSIC.DRV: unexpected EOF"); + + uint16 chunkCount = musicDrvStream->readUint16LE(); + streamLeft -= 2; + + if (getChunk >= chunkCount) + error("MUSIC.DRV: required chunk not available"); + + uint16 headerOffset = 2 + (28 * getChunk); + streamLeft -= (28 * getChunk); + + if (streamLeft < 28) + error("MUSIC.DRV: unexpected EOF"); + + // Seek to required chunk + musicDrvStream->seek(headerOffset); + musicDrvStream->skip(20); // skip over name + streamLeft -= 20; + + uint16 musicDrvSignature = musicDrvStream->readUint16LE(); + uint16 musicDrvType = musicDrvStream->readUint16LE(); + uint16 chunkOffset = musicDrvStream->readUint16LE(); + uint16 chunkSize = musicDrvStream->readUint16LE(); + + // Security checks + if (musicDrvSignature != 0xFEDC) + error("MUSIC.DRV: chunk signature mismatch"); + if (musicDrvType != 1) + error("MUSIC.DRV: not a music driver"); + if (chunkOffset >= streamSize) + error("MUSIC.DRV: driver chunk points outside of file"); + + streamLeft = streamSize - chunkOffset; + if (streamLeft < chunkSize) + error("MUSIC.DRV: driver chunk is larger than file"); + + instrumentDataSize = chunkSize; + + // Read the requested instrument data entry + instrumentData = new byte[instrumentDataSize]; + + musicDrvStream->seek(chunkOffset); + musicDrvStream->read(instrumentData, instrumentDataSize); + + musicDrvStream->close(); + delete musicDrvStream; + } + + // Pass the instrument data to the driver + bool instrumentSuccess = false; + + switch (accolade_musicType) { + case MT_ADLIB: + instrumentSuccess = MidiDriver_Accolade_AdLib_setupInstruments(_driver, instrumentData, instrumentDataSize, accolade_useMusicDrvFile); + break; + case MT_MT32: + instrumentSuccess = MidiDriver_Accolade_MT32_setupInstruments(_driver, instrumentData, instrumentDataSize, accolade_useMusicDrvFile); + break; + default: + assert(0); + break; + } + delete[] instrumentData; + + if (!instrumentSuccess) { + // driver did not like the contents + if (!accolade_useMusicDrvFile) + error("INSTR.DAT: contents not acceptable"); + else + error("MUSIC.DRV: contents not acceptable"); + } + + int ret = _driver->open(); + if (ret == 0) { + // Reset is done inside our MIDI driver + _driver->setTimerCallback(this, &onTimer); + } + + //setTimerRate(_driver->getBaseTempo()); + return 0; + } + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM)); _nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32")); @@ -113,6 +350,12 @@ void MidiPlayer::send(uint32 b) { if (!_current) return; + if (_accolade_mode) { + // Send directly to Accolade driver + _driver->send(b); + return; + } + byte channel = (byte)(b & 0x0F); if ((b & 0xFFF0) == 0x07B0) { // Adjust volume changes by master music and master sfx volume. @@ -146,8 +389,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) diff --git a/engines/agos/midi.h b/engines/agos/midi.h index 7e78bfef28..e7fcadc6ee 100644 --- a/engines/agos/midi.h +++ b/engines/agos/midi.h @@ -113,12 +113,14 @@ 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: + bool _accolade_mode; }; } // End of namespace AGOS diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp index c2c08bf451..f07ef5be41 100644 --- a/engines/agos/midiparser_s1d.cpp +++ b/engines/agos/midiparser_s1d.cpp @@ -181,10 +181,22 @@ bool MidiParser_S1D::loadMusic(byte *data, uint32 size) { // 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 currently ignore it + // the original Accolade code does see 0xFC as end of track, which means there must have been a change + if ((pos[1] == 0x29) && (pos[2] == 0x07) && (pos[3] == 0x01)) { + // Security check + // Last byte is either 0x00 or 0x01. Maybe some looping indicator? + pos += 5; // Waxworks / Simon 1 demo + } else { + if ((pos[1] == 0x04) && (pos[2] == 0x06) && (pos[3] == 06)) { + pos += 4; // Elvira 2 + } else { + warning("0xFC startup without proper signature"); + } + } + } // 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..ae9d5cb108 100644 --- a/engines/agos/module.mk +++ b/engines/agos/module.mk @@ -1,6 +1,8 @@ MODULE := engines/agos MODULE_OBJS := \ + drivers/accolade/adlib.o \ + drivers/accolade/mt32.o \ agos.o \ charset.o \ charset-fontdata.o \ |