aboutsummaryrefslogtreecommitdiff
path: root/engines/agos
diff options
context:
space:
mode:
authorMartin Kiewitz2015-06-21 00:45:45 +0200
committerMartin Kiewitz2015-06-21 00:45:45 +0200
commitd24c68c739dadd1404d937f9a21d93e8841a09ee (patch)
treebe811b96fb4e31b732709d0741cde6be120c1688 /engines/agos
parent4a88c69b5dedc4acac8bcb9609c1cf44c497c4ff (diff)
downloadscummvm-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.cpp4
-rw-r--r--engines/agos/drivers/accolade/adlib.cpp873
-rw-r--r--engines/agos/drivers/accolade/mididriver.h45
-rw-r--r--engines/agos/drivers/accolade/mt32.cpp267
-rw-r--r--engines/agos/midi.cpp249
-rw-r--r--engines/agos/midi.h4
-rw-r--r--engines/agos/midiparser_s1d.cpp20
-rw-r--r--engines/agos/module.mk2
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 \