scummvm/audio/miles_mt32.cpp
Martin Kiewitz 0655839a4f 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
2015-06-27 22:44:39 +02:00

442 lines
11 KiB
C++

/* 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