diff options
author | Walter van Niftrik | 2010-01-21 16:27:29 +0000 |
---|---|---|
committer | Walter van Niftrik | 2010-01-21 16:27:29 +0000 |
commit | 208dbf1d9ad1bcb6e365d5ce9092de962d1d5cd9 (patch) | |
tree | e589aad09debd6c8a35e8c0d01e43604d5e0b2d4 /engines/sci/sound/drivers/midi.cpp | |
parent | 9f5e50c1304a8097a70761cc3e3c5eee7a4e3044 (diff) | |
download | scummvm-rg350-208dbf1d9ad1bcb6e365d5ce9092de962d1d5cd9.tar.gz scummvm-rg350-208dbf1d9ad1bcb6e365d5ce9092de962d1d5cd9.tar.bz2 scummvm-rg350-208dbf1d9ad1bcb6e365d5ce9092de962d1d5cd9.zip |
SCI: Renamed softseq/ to drivers/
svn-id: r47418
Diffstat (limited to 'engines/sci/sound/drivers/midi.cpp')
-rw-r--r-- | engines/sci/sound/drivers/midi.cpp | 859 |
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 |