aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/sound/drivers/midi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/sound/drivers/midi.cpp')
-rw-r--r--engines/sci/sound/drivers/midi.cpp859
1 files changed, 859 insertions, 0 deletions
diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp
new file mode 100644
index 0000000000..07d2a3a222
--- /dev/null
+++ b/engines/sci/sound/drivers/midi.cpp
@@ -0,0 +1,859 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/sci.h"
+
+#include "common/config-manager.h"
+#include "sound/fmopl.h"
+#include "sound/softsynth/emumidi.h"
+
+#include "sci/resource.h"
+#include "sci/sound/drivers/mididriver.h"
+#include "sci/sound/drivers/map-mt32-to-gm.h"
+
+namespace Sci {
+
+class MidiPlayer_Midi : public MidiPlayer {
+public:
+ enum {
+ kVoices = 32,
+ kReverbConfigNr = 11,
+ kMaxSysExSize = 264
+ };
+
+ MidiPlayer_Midi();
+ virtual ~MidiPlayer_Midi();
+
+ int open(ResourceManager *resMan);
+ void close();
+ void send(uint32 b);
+ void sysEx(const byte *msg, uint16 length);
+ bool hasRhythmChannel() const { return true; }
+ byte getPlayId(SciVersion soundVersion);
+ int getPolyphony() const { return kVoices; }
+ void setVolume(byte volume);
+ int getVolume();
+ void setReverb(byte reverb);
+ void playSwitch(bool play);
+
+private:
+ bool isMt32GmPatch(const byte *data, int size);
+ void readMt32GmPatch(const byte *data, int size);
+ void readMt32Patch(const byte *data, int size);
+ void readMt32DrvData();
+
+ void mapMt32ToGm(byte *data, size_t size);
+ uint8 lookupGmInstrument(const char *iname);
+ uint8 lookupGmRhythmKey(const char *iname);
+ uint8 getGmInstrument(const Mt32ToGmMap &Mt32Ins);
+
+ void sendMt32SysEx(const uint32 addr, Common::SeekableReadStream *str, int len, bool noDelay);
+ void sendMt32SysEx(const uint32 addr, const byte *buf, int len, bool noDelay);
+ void setMt32Volume(byte volume);
+ void resetMt32();
+
+ void noteOn(int channel, int note, int velocity);
+ void setPatch(int channel, int patch);
+ void controlChange(int channel, int control, int value);
+
+ struct Channel {
+ byte mappedPatch;
+ byte patch;
+ int velocityMapIdx;
+ bool playing;
+ int8 keyShift;
+ int8 volAdjust;
+ uint8 pan;
+ uint8 hold;
+ uint8 volume;
+
+ Channel() : mappedPatch(MIDI_UNMAPPED), patch(MIDI_UNMAPPED), velocityMapIdx(0), playing(false),
+ keyShift(0), volAdjust(0), pan(0x80), hold(0), volume(0x7f) { }
+ };
+
+ bool _isMt32;
+ bool _isOldPatchFormat;
+ bool _hasReverb;
+ bool _playSwitch;
+ int _masterVolume;
+
+ byte _reverbConfig[kReverbConfigNr][3];
+ Channel _channels[16];
+ uint8 _percussionMap[128];
+ int8 _keyShift[128];
+ int8 _volAdjust[128];
+ uint8 _patchMap[128];
+ uint8 _velocityMapIdx[128];
+ uint8 _velocityMap[4][128];
+
+ // These are extensions used for our own MT-32 to GM mapping
+ uint8 _pitchBendRange[128];
+ uint8 _percussionVelocityScale[128];
+
+ byte _goodbyeMsg[20];
+ byte _sysExBuf[kMaxSysExSize];
+};
+
+MidiPlayer_Midi::MidiPlayer_Midi() : _playSwitch(true), _masterVolume(15), _isMt32(false), _hasReverb(false), _isOldPatchFormat(true) {
+ MidiDriverType midiType = MidiDriver::detectMusicDriver(MDT_MIDI);
+ _driver = createMidi(midiType);
+
+ if (midiType == MD_MT32 || ConfMan.getBool("native_mt32"))
+ _isMt32 = true;
+
+ _sysExBuf[0] = 0x41;
+ _sysExBuf[1] = 0x10;
+ _sysExBuf[2] = 0x16;
+ _sysExBuf[3] = 0x12;
+}
+
+MidiPlayer_Midi::~MidiPlayer_Midi() {
+ delete _driver;
+}
+
+void MidiPlayer_Midi::noteOn(int channel, int note, int velocity) {
+ uint8 patch = _channels[channel].mappedPatch;
+
+ if (channel == MIDI_RHYTHM_CHANNEL) {
+ if (_percussionMap[note] == MIDI_UNMAPPED) {
+ debugC(kDebugLevelSound, "[Midi] Percussion instrument %i is unmapped", note);
+ return;
+ }
+
+ note = _percussionMap[note];
+ // Scale velocity;
+ velocity = velocity * _percussionVelocityScale[note] / 127;
+ } else if (patch >= 128) {
+ if (patch == MIDI_UNMAPPED)
+ return;
+
+ // Map to rhythm
+ channel = MIDI_RHYTHM_CHANNEL;
+ note = patch - 128;
+
+ // Scale velocity;
+ velocity = velocity * _percussionVelocityScale[note] / 127;
+ } else {
+ int8 keyshift = _keyShift[channel];
+
+ int shiftNote = note + keyshift;
+
+ if (keyshift > 0) {
+ while (shiftNote > 127)
+ shiftNote -= 12;
+ } else {
+ while (shiftNote < 0)
+ shiftNote += 12;
+ }
+
+ note = shiftNote;
+
+ // We assume that velocity 0 maps to 0 (for note off)
+ int mapIndex = _channels[channel].velocityMapIdx;
+ velocity = _velocityMap[mapIndex][velocity];
+ }
+
+ _channels[channel].playing = true;
+ _driver->send(0x90 | channel, note, velocity);
+}
+
+void MidiPlayer_Midi::controlChange(int channel, int control, int value) {
+ switch (control) {
+ case 0x07:
+ _channels[channel].volume = value;
+
+ if (!_playSwitch)
+ return;
+
+ value += _channels[channel].volAdjust;
+
+ if (value > 0x7f)
+ value = 0x7f;
+
+ if (value < 0)
+ value = 1;
+
+ value *= _masterVolume;
+
+ if (value != 0) {
+ value /= 15;
+
+ if (value == 0)
+ value = 1;
+ }
+ break;
+ case 0x0a:
+ if (_channels[channel].pan == value)
+ return;
+
+ _channels[channel].pan = value;
+ break;
+ case 0x40:
+ if (_channels[channel].hold == value)
+ return;
+
+ _channels[channel].hold = value;
+ break;
+ case 0x7b:
+ if (!_channels[channel].playing)
+ return;
+
+ _channels[channel].playing = false;
+ }
+
+ _driver->send(0xb0 | channel, control, value);
+}
+
+void MidiPlayer_Midi::setPatch(int channel, int patch) {
+ bool resetVol = false;
+
+ if ((channel == MIDI_RHYTHM_CHANNEL) || (_channels[channel].patch == patch))
+ return;
+
+ _channels[channel].patch = patch;
+ _channels[channel].velocityMapIdx = _velocityMapIdx[patch];
+
+ if (_channels[channel].mappedPatch == MIDI_UNMAPPED)
+ resetVol = true;
+
+ _channels[channel].mappedPatch = _patchMap[patch];
+
+ if (_patchMap[patch] == MIDI_UNMAPPED) {
+ debugC(kDebugLevelSound, "[Midi] Channel %i set to unmapped patch %i", channel, patch);
+ _driver->send(0xb0 | channel, 0x7b, 0);
+ _driver->send(0xb0 | channel, 0x40, 0);
+ return;
+ }
+
+ if (_channels[channel].keyShift != _keyShift[patch]) {
+ _channels[channel].keyShift = _keyShift[patch];
+ _driver->send(0xb0 | channel, 0x7b, 0);
+ _driver->send(0xb0 | channel, 0x40, 0);
+ resetVol = true;
+ }
+
+ if (resetVol || (_channels[channel].volAdjust != _volAdjust[patch])) {
+ _channels[channel].volAdjust = _volAdjust[patch];
+ controlChange(channel, 0x07, _channels[channel].volume);
+ }
+
+ uint8 bendRange = _pitchBendRange[patch];
+ if (bendRange != MIDI_UNMAPPED)
+ _driver->setPitchBendRange(channel, bendRange);
+
+ _driver->send(0xc0 | channel, _patchMap[patch], 0);
+}
+
+void MidiPlayer_Midi::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+ byte op1 = (b >> 8) & 0x7f;
+ byte op2 = (b >> 16) & 0x7f;
+
+ // In early SCI0, we may also get events for AdLib rhythm channels.
+ // While an MT-32 would ignore those with the default channel mapping,
+ // we filter these out for the benefit of other MIDI devices.
+ if (channel < 1 || channel > 9)
+ return;
+
+ switch (command) {
+ case 0x80:
+ noteOn(channel, op1, 0);
+ break;
+ case 0x90:
+ noteOn(channel, op1, op2);
+ break;
+ case 0xb0:
+ controlChange(channel, op1, op2);
+ break;
+ case 0xc0:
+ setPatch(channel, op1);
+ break;
+ case 0xe0:
+ _driver->send(b);
+ break;
+ default:
+ warning("Ignoring MIDI event %02x", command);
+ }
+}
+
+void MidiPlayer_Midi::setVolume(byte volume) {
+ _masterVolume = volume;
+
+ if (!_playSwitch)
+ return;
+
+ for (uint i = 1; i < 10; i++) {
+ if (_channels[i].volume != 0xff)
+ controlChange(i, 0x07, _channels[i].volume & 0x7f);
+ }
+}
+
+int MidiPlayer_Midi::getVolume() {
+ return _masterVolume;
+}
+
+void MidiPlayer_Midi::setReverb(byte reverb) {
+ _reverb = CLIP<byte>(reverb, 0, kReverbConfigNr - 1);
+ if (_hasReverb)
+ sendMt32SysEx(0x100001, _reverbConfig[_reverb], 3, true);
+}
+
+void MidiPlayer_Midi::playSwitch(bool play) {
+ _playSwitch = play;
+ if (play)
+ setVolume(_masterVolume);
+ else {
+ for (uint i = 1; i < 10; i++)
+ _driver->send(0xb0 | i, 7, 0);
+ }
+}
+
+bool MidiPlayer_Midi::isMt32GmPatch(const byte *data, int size)
+{
+ if (size < 1155)
+ return false;
+ if (size > 16889)
+ return true;
+
+ bool isMt32 = false;
+ bool isMt32Gm = false;
+
+ if (READ_LE_UINT16(data + 1153) + 1155 == size)
+ isMt32Gm = true;
+
+ int pos = 492 + 246 * data[491];
+
+ if ((size >= (pos + 386)) && (READ_BE_UINT16(data + pos) == 0xabcd))
+ pos += 386;
+
+ if ((size >= (pos + 267)) && (READ_BE_UINT16(data + pos) == 0xdcba))
+ pos += 267;
+
+ if (size == pos)
+ isMt32 = true;
+
+ if (isMt32 == isMt32Gm)
+ error("Failed to detect MT-32 patch format");
+
+ return isMt32Gm;
+}
+
+void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, Common::SeekableReadStream *str, int len, bool noDelay = false) {
+ if (len + 8 > kMaxSysExSize) {
+ warning("SysEx message exceed maximum size; ignoring");
+ return;
+ }
+
+ uint16 chk = 0;
+
+ _sysExBuf[4] = (addr >> 16) & 0xff;
+ _sysExBuf[5] = (addr >> 8) & 0xff;
+ _sysExBuf[6] = addr & 0xff;
+
+ for (int i = 0; i < len; i++)
+ _sysExBuf[7 + i] = str->readByte();
+
+ for (int i = 4; i < 7 + len; i++)
+ chk += _sysExBuf[i];
+
+ _sysExBuf[7 + len] = 128 - chk % 128;
+
+ if (noDelay)
+ _driver->sysEx(_sysExBuf, len + 8);
+ else
+ sysEx(_sysExBuf, len + 8);
+}
+
+void MidiPlayer_Midi::sendMt32SysEx(const uint32 addr, const byte *buf, int len, bool noDelay = false) {
+ Common::MemoryReadStream *str = new Common::MemoryReadStream(buf, len);
+ sendMt32SysEx(addr, str, len, noDelay);
+ delete str;
+}
+
+void MidiPlayer_Midi::readMt32Patch(const byte *data, int size) {
+ Common::MemoryReadStream *str = new Common::MemoryReadStream(data, size);
+
+ // Send before-SysEx text
+ str->seek(0x14);
+ sendMt32SysEx(0x200000, str, 20);
+
+ // Save goodbye message
+ str->read(_goodbyeMsg, 20);
+
+ byte volume = CLIP<uint16>(str->readUint16LE(), 0, 100);
+ setMt32Volume(volume);
+
+ // Reverb default only used in (roughly) SCI0/SCI01
+ _reverb = str->readByte();
+ _hasReverb = true;
+
+ // Skip reverb SysEx message
+ str->seek(11, SEEK_CUR);
+
+ // Read reverb data
+ for (int i = 0; i < kReverbConfigNr; i++) {
+ _reverbConfig[i][0] = str->readByte();
+ _reverbConfig[i][1] = str->readByte();
+ _reverbConfig[i][2] = str->readByte();
+ }
+
+ // Patches 1-48
+ sendMt32SysEx(0x50000, str, 256);
+ sendMt32SysEx(0x50200, str, 128);
+
+ // Timbres
+ byte timbresNr = str->readByte();
+ for (int i = 0; i < timbresNr; i++)
+ sendMt32SysEx(0x80000 + (i << 9), str, 246);
+
+ uint16 flag = str->readUint16BE();
+
+ if (!str->eos() && (flag == 0xabcd)) {
+ // Patches 49-96
+ sendMt32SysEx(0x50300, str, 256);
+ sendMt32SysEx(0x50500, str, 128);
+ flag = str->readUint16BE();
+ }
+
+ if (!str->eos() && (flag == 0xdcba)) {
+ // Rhythm key map
+ sendMt32SysEx(0x30110, str, 256);
+ // Partial reserve
+ sendMt32SysEx(0x100004, str, 9);
+ }
+
+ // Send after-SysEx text
+ str->seek(0);
+ sendMt32SysEx(0x200000, str, 20);
+
+ // Send the mystery SysEx
+ sendMt32SysEx(0x52000a, (const byte *)"\x16\x16\x16\x16\x16\x16", 6);
+
+ delete str;
+}
+
+void MidiPlayer_Midi::readMt32GmPatch(const byte *data, int size) {
+ memcpy(_patchMap, data, 0x80);
+ memcpy(_keyShift, data + 0x80, 0x80);
+ memcpy(_volAdjust, data + 0x100, 0x80);
+ memcpy(_percussionMap, data + 0x180, 0x80);
+ _channels[MIDI_RHYTHM_CHANNEL].volAdjust = data[0x200];
+ memcpy(_velocityMapIdx, data + 0x201, 0x80);
+ memcpy(_velocityMap, data + 0x281, 0x200);
+
+ uint16 midiSize = READ_LE_UINT16(data + 0x481);
+
+ if (midiSize > 0) {
+ if (size < midiSize + 1155)
+ error("Failed to read MIDI data");
+
+ const byte *midi = data + 1155;
+ byte command = 0;
+ uint i = 0;
+
+ while (i < midiSize) {
+ byte op1, op2;
+
+ if (midi[i] & 0x80)
+ command = midi[i++];
+
+ switch (command & 0xf0) {
+ case 0xf0: {
+ byte *sysExEnd = (byte *)memchr(midi + i, 0xf7, midiSize - i);
+
+ if (!sysExEnd)
+ error("Failed to find end of sysEx");
+
+ int len = sysExEnd - (midi + i);
+ sysEx(midi + i, len);
+
+ i += len + 1; // One more for the 0x7f
+ break;
+ }
+ case 0x80:
+ case 0x90:
+ case 0xa0:
+ case 0xb0:
+ case 0xe0:
+ if (i + 1 >= midiSize)
+ error("MIDI command exceeds data size");
+
+ op1 = midi[i++];
+ op2 = midi[i++];
+ _driver->send(command, op1, op2);
+ break;
+ case 0xc0:
+ case 0xd0:
+ if (i >= midiSize)
+ error("MIDI command exceeds data size");
+
+ op1 = midi[i++];
+ _driver->send(command, op1, 0);
+ break;
+ default:
+ error("Failed to find MIDI command byte");
+ }
+ }
+ }
+}
+
+void MidiPlayer_Midi::readMt32DrvData() {
+ Common::File f;
+
+ if (f.open("MT32.DRV")) {
+ int size = f.size();
+
+ assert(size >= 166);
+
+ // Send before-SysEx text
+ f.seek(0x59);
+
+ // Skip 2 extra 0 bytes in some drivers
+ if (f.readUint16LE() != 0)
+ f.seek(-2, SEEK_CUR);
+
+ sendMt32SysEx(0x200000, static_cast<Common::SeekableReadStream *>(&f), 20);
+
+ // Send after-SysEx text (SSCI sends this before every song)
+ sendMt32SysEx(0x200000, static_cast<Common::SeekableReadStream *>(&f), 20);
+
+ // Save goodbye message
+ f.read(_goodbyeMsg, 20);
+
+ // Set volume
+ byte volume = CLIP<uint16>(f.readUint16LE(), 0, 100);
+ setMt32Volume(volume);
+
+ byte reverbSysEx[13];
+ // This old driver should have a full reverb SysEx
+ if ((f.read(reverbSysEx, 13) != 13) || (reverbSysEx[0] != 0xf0) || (reverbSysEx[12] != 0xf7))
+ error("Error reading MT32.DRV");
+
+ // Send reverb SysEx
+ sysEx(reverbSysEx + 1, 11);
+ _hasReverb = false;
+
+ f.seek(0x29);
+
+ // Read AdLib->MT-32 patch map
+ for (int i = 0; i < 48; i++) {
+ _patchMap[i] = f.readByte();
+ }
+
+ f.close();
+ } else {
+ error("Failed to open MT32.DRV");
+ }
+}
+
+byte MidiPlayer_Midi::lookupGmInstrument(const char *iname) {
+ int i = 0;
+
+ while (Mt32MemoryTimbreMaps[i].name) {
+ if (scumm_strnicmp(iname, Mt32MemoryTimbreMaps[i].name, 10) == 0)
+ return getGmInstrument(Mt32MemoryTimbreMaps[i]);
+ i++;
+ }
+ return MIDI_UNMAPPED;
+}
+
+byte MidiPlayer_Midi::lookupGmRhythmKey(const char *iname) {
+ int i = 0;
+
+ while (Mt32MemoryTimbreMaps[i].name) {
+ if (scumm_strnicmp(iname, Mt32MemoryTimbreMaps[i].name, 10) == 0)
+ return Mt32MemoryTimbreMaps[i].gmRhythmKey;
+ i++;
+ }
+ return MIDI_UNMAPPED;
+}
+
+uint8 MidiPlayer_Midi::getGmInstrument(const Mt32ToGmMap &Mt32Ins) {
+ if (Mt32Ins.gmInstr == MIDI_MAPPED_TO_RHYTHM)
+ return Mt32Ins.gmRhythmKey + 0x80;
+ else
+ return Mt32Ins.gmInstr;
+}
+
+void MidiPlayer_Midi::mapMt32ToGm(byte *data, size_t size) {
+ // FIXME: Clean this up
+ int memtimbres, patches;
+ uint8 group, number, keyshift, finetune, bender_range;
+ uint8 *patchpointer;
+ uint32 pos;
+ int i;
+
+ for (i = 0; i < 128; i++) {
+ _patchMap[i] = getGmInstrument(Mt32PresetTimbreMaps[i]);
+ _pitchBendRange[i] = 12;
+ }
+
+ for (i = 0; i < 128; i++)
+ _percussionMap[i] = Mt32PresetRhythmKeymap[i];
+
+ memtimbres = *(data + 0x1eb);
+ pos = 0x1ec + memtimbres * 0xf6;
+
+ if (size > pos && ((0x100 * *(data + pos) + *(data + pos + 1)) == 0xabcd)) {
+ patches = 96;
+ pos += 2 + 8 * 48;
+ } else
+ patches = 48;
+
+ debugC(kDebugLevelSound, "[MT32-to-GM] %d MT-32 Patches detected", patches);
+ debugC(kDebugLevelSound, "[MT32-to-GM] %d MT-32 Memory Timbres", memtimbres);
+
+ debugC(kDebugLevelSound, "\n[MT32-to-GM] Mapping patches..");
+
+ for (i = 0; i < patches; i++) {
+ char name[11];
+
+ if (i < 48)
+ patchpointer = data + 0x6b + 8 * i;
+ else
+ patchpointer = data + 0x1ec + 8 * (i - 48) + memtimbres * 0xf6 + 2;
+
+ group = *patchpointer;
+ number = *(patchpointer + 1);
+ keyshift = *(patchpointer + 2);
+ finetune = *(patchpointer + 3);
+ bender_range = *(patchpointer + 4);
+
+ debugCN(kDebugLevelSound, " [%03d] ", i);
+
+ switch (group) {
+ case 1:
+ number += 64;
+ // Fall through
+ case 0:
+ _patchMap[i] = getGmInstrument(Mt32PresetTimbreMaps[number]);
+ debugCN(kDebugLevelSound, "%s -> ", Mt32PresetTimbreMaps[number].name);
+ break;
+ case 2:
+ strncpy(name, (const char *)data + 0x1ec + number * 0xf6, 10);
+ name[10] = 0;
+ _patchMap[i] = lookupGmInstrument(name);
+ debugCN(kDebugLevelSound, "%s -> ", name);
+ break;
+ case 3:
+ _patchMap[i] = getGmInstrument(Mt32RhythmTimbreMaps[number]);
+ debugCN(kDebugLevelSound, "%s -> ", Mt32RhythmTimbreMaps[number].name);
+ break;
+ default:
+ break;
+ }
+
+ if (_patchMap[i] == MIDI_UNMAPPED) {
+ debugC(kDebugLevelSound, "[Unmapped]");
+ } else {
+ if (_patchMap[i] >= 128) {
+ debugC(kDebugLevelSound, "%s [Rhythm]", GmPercussionNames[_patchMap[i] - 128]);
+ } else {
+ debugC(kDebugLevelSound, "%s", GmInstrumentNames[_patchMap[i]]);
+ }
+ }
+
+ _keyShift[i] = CLIP<uint8>(keyshift, 0, 48) - 24;
+ _pitchBendRange[i] = CLIP<uint8>(bender_range, 0, 24);
+ }
+
+ if (size > pos && ((0x100 * *(data + pos) + *(data + pos + 1)) == 0xdcba)) {
+ debugC(kDebugLevelSound, "\n[MT32-to-GM] Mapping percussion..");
+
+ for (i = 0; i < 64 ; i++) {
+ number = *(data + pos + 4 * i + 2);
+
+ debugCN(kDebugLevelSound, " [%03d] ", i + 23);
+
+ if (number < 64) {
+ char name[11];
+ strncpy(name, (const char *)data + 0x1ec + number * 0xf6, 10);
+ name[10] = 0;
+ debugCN(kDebugLevelSound, "%s -> ", name);
+ _percussionMap[i + 23] = lookupGmRhythmKey(name);
+ } else {
+ if (number < 94) {
+ debugCN(kDebugLevelSound, "%s -> ", Mt32RhythmTimbreMaps[number - 64].name);
+ _percussionMap[i + 23] = Mt32RhythmTimbreMaps[number - 64].gmRhythmKey;
+ } else {
+ debugCN(kDebugLevelSound, "[Key %03i] -> ", number);
+ _percussionMap[i + 23] = MIDI_UNMAPPED;
+ }
+ }
+
+ if (_percussionMap[i + 23] == MIDI_UNMAPPED)
+ debugC(kDebugLevelSound, "[Unmapped]");
+ else
+ debugC(kDebugLevelSound, "%s", GmPercussionNames[_percussionMap[i + 23]]);
+
+ _percussionVelocityScale[i + 23] = *(data + pos + 4 * i + 3) * 127 / 100;
+ }
+ }
+}
+
+void MidiPlayer_Midi::setMt32Volume(byte volume) {
+ sendMt32SysEx(0x100016, &volume, 1);
+}
+
+void MidiPlayer_Midi::resetMt32() {
+ sendMt32SysEx(0x7f0000, (const byte *)"\x01\x00", 2, true);
+
+ // This seems to require a longer delay than usual
+ g_system->delayMillis(150);
+}
+
+int MidiPlayer_Midi::open(ResourceManager *resMan) {
+ assert(resMan != NULL);
+
+ int retval = _driver->open();
+ if (retval != 0) {
+ warning("Failed to open MIDI driver");
+ return retval;
+ }
+
+ // By default use no mapping
+ for (uint i = 0; i < 128; i++) {
+ _percussionMap[i] = i;
+ _patchMap[i] = i;
+ _velocityMap[0][i] = i;
+ _keyShift[i] = 0;
+ _volAdjust[i] = 0;
+ _velocityMapIdx[i] = 0;
+ _pitchBendRange[i] = MIDI_UNMAPPED;
+ _percussionVelocityScale[i] = 127;
+ }
+
+ Resource *res = NULL;
+
+ if (_isMt32) {
+ // MT-32
+ resetMt32();
+
+ res = resMan->findResource(ResourceId(kResourceTypePatch, 1), 0);
+
+ if (res) {
+ if (isMt32GmPatch(res->data, res->size)) {
+ readMt32GmPatch(res->data, res->size);
+ strncpy((char *)_goodbyeMsg, " ScummVM ", 20);
+ } else {
+ readMt32Patch(res->data, res->size);
+ }
+ } else {
+ readMt32DrvData();
+ }
+ } else {
+ // General MIDI
+ res = resMan->findResource(ResourceId(kResourceTypePatch, 4), 0);
+
+ if (res && isMt32GmPatch(res->data, res->size)) {
+ // There is a GM patch
+ readMt32GmPatch(res->data, res->size);
+
+ // Detect the format of patch 1, so that we know what play mask to use
+ res = resMan->findResource(ResourceId(kResourceTypePatch, 1), 0);
+ if (!res)
+ _isOldPatchFormat = false;
+ else
+ _isOldPatchFormat = !isMt32GmPatch(res->data, res->size);
+ } else {
+ // No GM patch found, map instruments using MT-32 patch
+
+ warning("Game has no native support for General MIDI, applying auto-mapping");
+
+ // Modify velocity map to make low velocity notes a little louder
+ for (uint i = 1; i < 0x40; i++)
+ _velocityMap[0][i] = 0x20 + (i - 1) / 2;
+
+ res = resMan->findResource(ResourceId(kResourceTypePatch, 1), 0);
+
+ if (res) {
+ if (!isMt32GmPatch(res->data, res->size))
+ mapMt32ToGm(res->data, res->size);
+ else
+ error("MT-32 patch has wrong type");
+ } else {
+ // No MT-32 patch present, try to read from MT32.DRV
+ Common::File f;
+
+ if (f.open("MT32.DRV")) {
+ int size = f.size();
+
+ assert(size >= 70);
+
+ f.seek(0x29);
+
+ // Read AdLib->MT-32 patch map
+ for (int i = 0; i < 48; i++)
+ _patchMap[i] = getGmInstrument(Mt32PresetTimbreMaps[f.readByte() & 0x7f]);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+void MidiPlayer_Midi::close() {
+ if (_isMt32) {
+ // Send goodbye message
+ sendMt32SysEx(0x200000, _goodbyeMsg, 20);
+ }
+
+ _driver->close();
+}
+
+void MidiPlayer_Midi::sysEx(const byte *msg, uint16 length) {
+ _driver->sysEx(msg, length);
+
+ // Wait the time it takes to send the SysEx data
+ uint32 delay = (length + 2) * 1000 / 3125;
+
+ // Plus an additional delay for the MT-32 rev00
+ if (_isMt32)
+ delay += 40;
+
+ g_system->delayMillis(delay);
+ g_system->updateScreen();
+}
+
+byte MidiPlayer_Midi::getPlayId(SciVersion soundVersion) {
+ switch (soundVersion) {
+ case SCI_VERSION_0_EARLY:
+ case SCI_VERSION_0_LATE:
+ return 0x01;
+ default:
+ if (_isMt32)
+ return 0x0c;
+ else
+ return _isOldPatchFormat ? 0x0c : 0x07;
+ }
+}
+
+MidiPlayer *MidiPlayer_Midi_create() {
+ return new MidiPlayer_Midi();
+}
+
+} // End of namespace Sci