aboutsummaryrefslogtreecommitdiff
path: root/audio/miles_mt32.cpp
diff options
context:
space:
mode:
authorMartin Kiewitz2015-06-27 22:43:32 +0200
committerMartin Kiewitz2015-06-27 22:44:39 +0200
commit0655839a4ff2a3805919c0b9bfa8f035d015e641 (patch)
tree2708550c323fac08ba6062b869389905ff21a0c8 /audio/miles_mt32.cpp
parent9cfe5c12968bdb53fb552615a354180b04778e29 (diff)
downloadscummvm-rg350-0655839a4ff2a3805919c0b9bfa8f035d015e641.tar.gz
scummvm-rg350-0655839a4ff2a3805919c0b9bfa8f035d015e641.tar.bz2
scummvm-rg350-0655839a4ff2a3805919c0b9bfa8f035d015e641.zip
COMMON: move Miles Audio AdLib+MT32 to common
- remove Miles Audio from Sherlock engine - put Miles Audio into common audio (namespace Audio) - Miles Audio is used at least by the engines TINSEL, GROOVIE, TOLTECS, SAGA and KYRA This way it can be used by the other engines
Diffstat (limited to 'audio/miles_mt32.cpp')
-rw-r--r--audio/miles_mt32.cpp441
1 files changed, 441 insertions, 0 deletions
diff --git a/audio/miles_mt32.cpp b/audio/miles_mt32.cpp
new file mode 100644
index 0000000000..cb039e6ea3
--- /dev/null
+++ b/audio/miles_mt32.cpp
@@ -0,0 +1,441 @@
+/* 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 "audio/miles.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/mutex.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+namespace Audio {
+
+// Miles Audio MT32 driver
+//
+// TODO: currently missing: timbre file support (used in 7th Guest)
+
+#define MILES_MT32_PATCH_COUNT 128
+
+const byte milesMT32SysExResetParameters[] = {
+ 0x01, 0xFF
+};
+
+const byte milesMT32SysExChansSetup[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF
+};
+
+const byte milesMT32SysExPartialReserveTable[] = {
+ 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, 0xFF
+};
+
+const byte milesMT32SysExInitReverb[] = {
+ 0x00, 0x03, 0x02, 0xFF // Reverb mode 0, reverb time 3, reverb level 2
+};
+
+class MidiDriver_Miles_MT32 : public MidiDriver {
+public:
+ MidiDriver_Miles_MT32();
+ virtual ~MidiDriver_Miles_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;
+
+public:
+
+private:
+ void resetMT32();
+
+ void MT32SysEx(const uint32 targetAddress, const byte *dataPtr);
+
+ void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId);
+ void writePatchByte(byte patchId, byte index, byte patchValue);
+ void writeToSystemArea(byte index, byte value);
+
+ void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue);
+ void programChange(byte midiChannel, byte patchId);
+
+ void setupPatch(byte patchId, byte patchBank);
+
+private:
+ struct MidiChannelEntry {
+ byte currentPatchBank;
+ byte currentPatchId;
+ bool patchIdSet;
+
+ MidiChannelEntry() : currentPatchBank(0),
+ currentPatchId(0),
+ patchIdSet(false) { }
+ };
+
+ // stores information about all MIDI channels
+ MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT];
+
+ byte _patchesBank[MILES_MT32_PATCH_COUNT];
+};
+
+MidiDriver_Miles_MT32::MidiDriver_Miles_MT32() {
+ _driver = NULL;
+ _isOpen = false;
+ _MT32 = false;
+ _nativeMT32 = false;
+ _baseFreq = 250;
+
+ memset(_patchesBank, 0, sizeof(_patchesBank));
+}
+
+MidiDriver_Miles_MT32::~MidiDriver_Miles_MT32() {
+ Common::StackLock lock(_mutex);
+ if (_driver) {
+ _driver->setTimerCallback(0, 0);
+ _driver->close();
+ delete _driver;
+ }
+ _driver = NULL;
+}
+
+int MidiDriver_Miles_MT32::open() {
+ assert(!_driver);
+
+ // Setup midi driver
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
+ MusicType musicType = MidiDriver::getMusicType(dev);
+
+ switch (musicType) {
+ case MT_MT32:
+ _nativeMT32 = true;
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _nativeMT32 = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!_nativeMT32) {
+ error("MILES-MT32: non-mt32 currently not supported!");
+ }
+
+ _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 (_nativeMT32) {
+ _driver->sendMT32Reset();
+
+ resetMT32();
+ }
+
+ return 0;
+}
+
+void MidiDriver_Miles_MT32::close() {
+ if (_driver) {
+ _driver->close();
+ }
+}
+
+void MidiDriver_Miles_MT32::resetMT32() {
+ // reset all internal parameters / patches
+ MT32SysEx(0x7F0000, milesMT32SysExResetParameters);
+
+ // init part/channel assignments
+ MT32SysEx(0x10000D, milesMT32SysExChansSetup);
+
+ // partial reserve table
+ MT32SysEx(0x100004, milesMT32SysExPartialReserveTable);
+
+ // init reverb
+ MT32SysEx(0x100001, milesMT32SysExInitReverb);
+}
+
+void MidiDriver_Miles_MT32::MT32SysEx(const uint32 targetAddress, const byte *dataPtr) {
+ byte sysExMessage[270];
+ uint16 sysExPos = 0;
+ byte sysExByte = 0;
+ uint16 sysExChecksum = 0;
+
+ memset(&sysExMessage, 0, sizeof(sysExMessage));
+
+ sysExMessage[0] = 0x41; // Roland
+ sysExMessage[1] = 0x10;
+ sysExMessage[2] = 0x16; // Model MT32
+ sysExMessage[3] = 0x12; // Command DT1
+
+ sysExChecksum = 0;
+
+ sysExMessage[4] = (targetAddress >> 16) & 0xFF;
+ sysExMessage[5] = (targetAddress >> 8) & 0xFF;
+ sysExMessage[6] = targetAddress & 0xFF;
+
+ sysExChecksum -= sysExMessage[4];
+ sysExChecksum -= sysExMessage[5];
+ sysExChecksum -= sysExMessage[6];
+
+ sysExPos = 7;
+ while (1) {
+ sysExByte = *dataPtr++;
+ if (sysExByte == 0xff)
+ break; // Message done
+
+ assert(sysExPos < sizeof(sysExMessage));
+ sysExMessage[sysExPos++] = sysExByte;
+ sysExChecksum -= sysExByte;
+ }
+
+ // Calculate checksum
+ assert(sysExPos < sizeof(sysExMessage));
+ sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
+
+ // Send SysEx
+ _driver->sysEx(sysExMessage, sysExPos);
+
+ // Wait the time it takes to send the SysEx data
+ uint32 delay = (sysExPos + 2) * 1000 / 3125;
+
+ // Plus an additional delay for the MT-32 rev00
+ if (_nativeMT32)
+ delay += 40;
+
+ g_system->delayMillis(delay);
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_Miles_MT32::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+
+ switch (command) {
+ case 0x80: // note off
+ case 0x90: // note on
+ case 0xe0: // pitch bend change
+ _driver->send(b);
+ break;
+ case 0xb0: // Control change
+ controlChange(channel, op1, op2);
+ break;
+ case 0xc0: // Program Change
+ programChange(channel, op1);
+ break;
+ case 0xa0: // Polyphonic key pressure (aftertouch)
+ case 0xd0: // Channel pressure (aftertouch)
+ // Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver
+ break;
+ case 0xf0: // SysEx
+ warning("MILES-MT32: SysEx: %x", b);
+ break;
+ default:
+ warning("MILES-MT32: Unknown event %02x", command);
+ }
+}
+
+void MidiDriver_Miles_MT32::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) {
+ byte channelPatchId = 0;
+
+ switch (controllerNumber) {
+ case MILES_CONTROLLER_SELECT_PATCH_BANK:
+ _midiChannels[midiChannel].currentPatchBank = controllerValue;
+ return;
+
+ case MILES_CONTROLLER_PATCH_REVERB:
+ channelPatchId = _midiChannels[midiChannel].currentPatchId;
+
+ writePatchByte(channelPatchId, 6, controllerValue);
+ _driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
+ return;
+
+ case MILES_CONTROLLER_PATCH_BENDER:
+ channelPatchId = _midiChannels[midiChannel].currentPatchId;
+
+ writePatchByte(channelPatchId, 4, controllerValue);
+ _driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change
+ return;
+
+ case MILES_CONTROLLER_REVERB_MODE:
+ writeToSystemArea(1, controllerValue);
+ return;
+
+ case MILES_CONTROLLER_REVERB_TIME:
+ writeToSystemArea(2, controllerValue);
+ return;
+
+ case MILES_CONTROLLER_REVERB_LEVEL:
+ writeToSystemArea(3, controllerValue);
+ return;
+
+ case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE:
+ // uses .MT data, cannot implement atm
+ return;
+
+ case MILES_CONTROLLER_PROTECT_TIMBRE:
+ // timbre .MT data, cannot implement atm
+ return;
+
+ default:
+ break;
+ }
+
+ if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) {
+ // send SysEx
+ warning("MILES-MT32: embedded SysEx controller %2x, value %2x", controllerNumber, controllerValue);
+ return;
+ }
+
+ if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) {
+ // XMIDI controllers? ignore those
+ return;
+ }
+
+ _driver->send(0xB0 | midiChannel | (controllerNumber << 8) | (controllerValue << 16));
+}
+
+void MidiDriver_Miles_MT32::programChange(byte midiChannel, byte patchId) {
+ byte channelPatchBank = _midiChannels[midiChannel].currentPatchBank;
+ byte activePatchBank = _patchesBank[patchId];
+
+ // remember patch id for the current MIDI-channel
+ _midiChannels[midiChannel].currentPatchId = patchId;
+
+ if (channelPatchBank != activePatchBank) {
+ // associate patch with timbre
+ setupPatch(patchId, channelPatchBank);
+ warning("setup patch");
+ }
+
+ // Search timbre and remember it (only used when timbre file is available)
+ // TODO
+
+ // Finally send to MT32
+ _driver->send(0xC0 | midiChannel | (patchId << 8));
+}
+
+void MidiDriver_Miles_MT32::setupPatch(byte patchId, byte patchBank) {
+ byte timbreId = 0;
+
+ _patchesBank[patchId] = patchBank;
+
+ if (patchBank) {
+ // non-built-in bank
+ // TODO: search timbre
+ }
+
+ // for built-in bank (or timbres, that are not available) use default MT32 timbres
+ timbreId = patchId & 0x3F;
+ if (!(patchId & 0x40)) {
+ writePatchTimbre(patchId, 0, timbreId); // Group A
+ } else {
+ writePatchTimbre(patchId, 1, timbreId); // Group B
+ }
+}
+
+void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) {
+ byte sysExData[3];
+ uint32 targetAddress = 0;
+
+ targetAddress = ((patchId << 3) << 16) | 0x000500;
+
+ sysExData[0] = timbreGroup;
+ sysExData[1] = timbreId;
+ sysExData[2] = 0xFF; // terminator
+
+ MT32SysEx(targetAddress, sysExData);
+}
+
+void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) {
+ byte sysExData[2];
+ uint32 targetAddress = 0;
+
+ targetAddress = (((patchId << 3) + index ) << 16) | 0x000500;
+
+ sysExData[0] = patchValue;
+ sysExData[1] = 0xFF; // terminator
+
+ MT32SysEx(targetAddress, sysExData);
+}
+
+void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) {
+ byte sysExData[2];
+ uint32 targetAddress = 0;
+
+ targetAddress = 0x100000 | index;
+
+ sysExData[0] = value;
+ sysExData[1] = 0xFF; // terminator
+
+ MT32SysEx(targetAddress, sysExData);
+}
+
+MidiDriver *MidiDriver_Miles_MT32_create(const Common::String instrumentDataFilename) {
+ // For some games there are timbre files called [something].MT
+ // Sherlock Holmes 2 doesn't have one of those
+ // so I can't implement them
+ return new MidiDriver_Miles_MT32();
+}
+
+} // End of namespace Audio