SCUMM: (IMS) - refactor MT32/GM drivers

The MT32 driver could be based on the GM driver for the
most parts. Also minor fixes and cleanup.
This commit is contained in:
athrxx 2022-10-06 13:53:46 +02:00
parent ead51cb45e
commit caa96a50f0
7 changed files with 1012 additions and 1446 deletions

View File

@ -1,574 +0,0 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "scumm/imuse/drivers/gmidi.h"
// This makes older titles play new system style, with 16 virtual channels and
// dynamic allocation (instead of playing on fixed channels).
//#define FORCE_NEWSTYLE_CHANNEL_ALLOCATION
namespace Scumm {
class IMuseChannel_GMidi : public MidiChannel {
public:
IMuseChannel_GMidi(IMuseDriver_GMidi *drv, int number);
~IMuseChannel_GMidi() override {}
MidiDriver *device() override { return _drv; }
byte getNumber() override { return _number; }
bool allocate();
void release() override { _allocated = false; }
void send(uint32 b) override { if (_drv) _drv->send((b & ~0x0F) | _number); }
// Regular messages
void noteOff(byte note) override;
void noteOn(byte note, byte velocity) override;
void controlChange(byte control, byte value) override;
void programChange(byte program) override;
void pitchBend(int16 bend) override;
// Control Change and SCUMM specific functions
void pitchBendFactor(byte value) override { _pitchBendSensitivity = value; }
void transpose(int8 value) override { _transpose = value; }
void detune(byte value) override { _detune = value; }
void priority(byte value) override { _prio = value; }
void sustain(bool value) override;
void allNotesOff() override;
void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override {}
private:
void noteOffIntern(byte note);
void noteOnIntern(byte note, byte velocity);
void setNotePlaying(byte note) { _drv->setNoteFlag(_number, note); }
void clearNotePlaying(byte note) { _drv->clearNoteFlag(_number, note); }
bool isNotePlaying(byte note) const { return _drv->queryNoteFlag(_number, note); }
void setNoteSustained(byte note) { _drv->setSustainFlag(_number, note); }
void clearNoteSustained(byte note) { _drv->clearSustainFlag(_number, note); }
bool isNoteSustained(byte note) const { return _drv->querySustainFlag(_number, note); }
IMuseDriver_GMidi *_drv;
const byte _number;
const bool _newSystem;
bool _allocated;
byte _polyphony;
byte _channelUsage;
bool _exhaust;
byte _prio;
int8 _detune;
int8 _transpose;
byte _pitchBendSensitivity;
int16 _pitchBend;
bool _sustain;
GMidiControlChan *&_idleChain;
GMidiControlChan *&_activeChain;
};
class GMidiControlChan {
public:
GMidiControlChan() : _prev(nullptr), _next(nullptr), _in(nullptr), _number(0), _note(0) {}
GMidiControlChan *_prev;
GMidiControlChan *_next;
IMuseChannel_GMidi *_in;
byte _number;
byte _note;
};
void connect(GMidiControlChan *&chain, GMidiControlChan *node) {
if (!node || node->_prev || node->_next)
return;
if ((node->_next = chain))
chain->_prev = node;
chain = node;
}
void disconnect(GMidiControlChan *&chain, GMidiControlChan *node) {
if (!node || !chain)
return;
const GMidiControlChan *ch = chain;
while (ch && ch != node)
ch = ch->_next;
if (!ch)
return;
if (node->_next)
node->_next->_prev = node->_prev;
if (node->_prev)
node->_prev->_next = node->_next;
else
chain = node->_next;
node->_next = node->_prev = nullptr;
}
#define sendMidi(stat, par1, par2) _drv->send(((par2) << 16) | ((par1) << 8) | (stat))
IMuseChannel_GMidi::IMuseChannel_GMidi(IMuseDriver_GMidi *drv, int number) :MidiChannel(), _drv(drv), _number(number), _allocated(false), _sustain(false),
_pitchBend(0x2000), _polyphony(1), _channelUsage(0), _exhaust(false), _prio(0), _detune(0), _transpose(0),
_pitchBendSensitivity(2), _idleChain(_drv->_idleChain), _activeChain(_drv->_activeChain), _newSystem(_drv->_newSystem) {
assert(_drv);
}
bool IMuseChannel_GMidi::allocate() {
if (_allocated)
return false;
if (!_newSystem) {
_prio = 0x80;
}
return (_allocated = true);
}
void IMuseChannel_GMidi::noteOff(byte note) {
if (_newSystem) {
if (!isNotePlaying(note))
return;
clearNotePlaying(note);
if (_sustain) {
setNoteSustained(note);
return;
}
}
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
noteOffIntern(note);
#else
if (_newSystem)
noteOffIntern(note);
else
sendMidi(0x80 | _number, note, 0x40);
#endif
}
void IMuseChannel_GMidi::noteOn(byte note, byte velocity) {
if (_newSystem) {
if (isNotePlaying(note)) {
noteOffIntern(note);
} else if (isNoteSustained(note)) {
setNotePlaying(note);
clearNoteSustained(note);
noteOffIntern(note);
} else {
setNotePlaying(note);
}
}
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
noteOnIntern(note, velocity);
#else
if (_newSystem)
noteOnIntern(note, velocity);
else
sendMidi(0x90 | _number, note, velocity);
#endif
}
void IMuseChannel_GMidi::controlChange(byte control, byte value) {
switch (control) {
case 1:
case 7:
case 10:
case 91:
case 93:
sendMidi(0xB0 | _number, control, value);
break;
case 17:
if (_newSystem)
_polyphony = value;
else
detune(value);
break;
case 18:
priority(value);
break;
case 123:
allNotesOff();
break;
default:
// The original driver does not pass through "blindly". The
// only controls that get sent are 1, 7, 10, 91 and 93.
warning("Unhandled Control: %d", control);
break;
}
}
void IMuseChannel_GMidi::programChange(byte program) {
sendMidi(0xC0 | _number, program, 0);
}
void IMuseChannel_GMidi::pitchBend(int16 bend) {
bend = bend + 0x2000;
sendMidi(0xE0 | _number, _pitchBend & 0x7F, (_pitchBend >> 7) & 0x7F);
}
void IMuseChannel_GMidi::sustain(bool value) {
_sustain = value;
if (_newSystem) {
// For SAMNMAX, this is fully software controlled. No control change message gets sent.
if (_sustain)
return;
for (int i = 0; i < 128; ++i) {
if (isNoteSustained(i))
noteOffIntern(i);
}
} else {
sendMidi(0xB0 | _number, 0x40, value);
}
}
void IMuseChannel_GMidi::allNotesOff() {
if (_newSystem) {
// For SAMNMAX, this is fully software controlled. No control change message gets sent.
if (_sustain)
return;
for (int i = 0; i < 128; ++i) {
if (isNotePlaying(i)) {
noteOffIntern(i);
clearNotePlaying(i);
} else if (isNoteSustained(i)) {
noteOffIntern(i);
clearNoteSustained(i);
}
}
} else {
sendMidi(0xB0 | _number, 0x7B, 0);
}
}
void IMuseChannel_GMidi::noteOffIntern(byte note) {
if (_activeChain == nullptr)
return;
GMidiControlChan *node = nullptr;
for (GMidiControlChan *i = _activeChain; i; i = i->_next) {
if (i->_number == _number && i->_note == note) {
node = i;
break;
}
}
if (!node)
return;
sendMidi(0x80 | _number, note, 0x40);
if (_newSystem)
_exhaust = (--_channelUsage > _polyphony);
disconnect(_activeChain, node);
connect(_idleChain, node);
}
void IMuseChannel_GMidi::noteOnIntern(byte note, byte velocity) {
GMidiControlChan *node = nullptr;
if (_idleChain) {
node = _idleChain;
disconnect(_idleChain, node);
} else {
IMuseChannel_GMidi *best = this;
for (GMidiControlChan *i = _activeChain; i; i = i->_next) {
assert (i->_in);
if ((best->_exhaust == i->_in->_exhaust && best->_prio >= i->_in->_prio) || (!best->_exhaust && i->_in->_exhaust)) {
best = i->_in;
node = i;
}
}
if (!node)
return;
IMuseChannel_GMidi *prt = _drv->_imsParts[node->_number];
if (prt) {
sendMidi(0x80 | prt->_number, node->_note, 0x40);
if (_newSystem)
prt->_exhaust = (--prt->_channelUsage > prt->_polyphony);
}
disconnect(_activeChain, node);
}
assert(node);
node->_in = this;
node->_number = _number;
node->_note = note;
connect(_activeChain, node);
if (_newSystem)
_exhaust = (++_channelUsage > _polyphony);
sendMidi(0x90 | _number, note, velocity);
}
#undef sendMidi
IMuseDriver_GMidi::IMuseDriver_GMidi(MidiDriver::DeviceHandle dev, bool rolandGSMode, bool newSystem) : MidiDriver(), _drv(nullptr), _gsMode(rolandGSMode), _imsParts(nullptr),
_newSystem(newSystem), _numChannels(16), _notesPlaying(nullptr), _notesSustained(nullptr), _controlChan(nullptr), _idleChain(nullptr), _activeChain(nullptr) {
_drv = MidiDriver::createMidi(dev);
assert(_drv);
}
IMuseDriver_GMidi::~IMuseDriver_GMidi() {
close();
delete _drv;
}
int IMuseDriver_GMidi::open() {
if (!_drv)
return MERR_CANNOT_CONNECT;
int res = _drv->open();
if (res)
return res;
createChannels();
if (_gsMode)
initDeviceAsRolandGS();
else
initDevice();
return res;
}
void IMuseDriver_GMidi::close() {
if (isOpen() && _drv) {
for (int i = 0; i < 16; ++i) {
send(0x0040B0 | i);
send(0x007BB0 | i);
}
_drv->close();
}
releaseChannels();
}
MidiChannel *IMuseDriver_GMidi::allocateChannel() {
if (!isOpen())
return nullptr;
for (int i = 0; i < _numChannels; ++i) {
IMuseChannel_GMidi *ch = _imsParts[i];
if (ch && i != 9 && ch->allocate())
return ch;
}
return nullptr;
}
MidiChannel *IMuseDriver_GMidi::getPercussionChannel() {
if (!isOpen())
return nullptr;
return _imsParts[9];
}
void IMuseDriver_GMidi::initDevice() {
// These are the init messages from the DOTT General Midi driver. This is the major part of the bug fix for bug
// no. 13460 ("DOTT: Incorrect MIDI pitch bending"). SAMNMAX has some less of the default settings (since
// the driver works a bit different), but it uses the same values for the pitch bend range.
for (int i = 0; i < 16; ++i) {
send(0x0064B0 | i);
send(0x0065B0 | i);
send(0x1006B0 | i);
send(0x7F07B0 | i);
send(0x3F0AB0 | i);
send(0x0000C0 | i);
send(0x4000E0 | i);
send(0x0001B0 | i);
send(0x0040B0 | i);
send(0x405BB0 | i);
send(0x005DB0 | i);
send(0x0000B0 | i);
send(0x007BB0 | i);
}
}
void IMuseDriver_GMidi::initDeviceAsRolandGS() {
byte buffer[12];
int i;
// General MIDI System On message
// Resets all GM devices to default settings
memcpy(&buffer[0], "\x7E\x7F\x09\x01", 4);
sysEx(buffer, 4);
debug(2, "GM SysEx: GM System On");
g_system->delayMillis(200);
// All GS devices recognize the GS Reset command,
// even using Roland's ID. It is impractical to
// support other manufacturers' devices for
// further GS settings, as there are limitless
// numbers of them out there that would each
// require individual SysEx commands with unique IDs.
// Roland GS SysEx ID
memcpy(&buffer[0], "\x41\x10\x42\x12", 4);
// GS Reset
memcpy(&buffer[4], "\x40\x00\x7F\x00\x41", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: GS Reset");
g_system->delayMillis(200);
// Set global Master Tune to 442.0kHz, as on the MT-32
memcpy(&buffer[4], "\x40\x00\x00\x00\x04\x04\x0F\x29", 8);
sysEx(buffer, 12);
debug(2, "GS SysEx: Master Tune set to 442.0kHz");
// Note: All Roland GS devices support CM-64/32L maps
// Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation
for (i = 0; i < 16; ++i) {
_drv->send((127 << 16) | (0 << 8) | (0xB0 | i));
_drv->send((1 << 16) | (32 << 8) | (0xB0 | i));
_drv->send((0 << 16) | (0 << 8) | (0xC0 | i));
}
debug(2, "GS Program Change: CM-64/32L Map Selected");
// Set Percussion Channel to SC-55 Map (CC#32, 01H), then
// Switch Drum Map to CM-64/32L (MT-32 Compatible Drums)
getPercussionChannel()->controlChange(0, 0);
getPercussionChannel()->controlChange(32, 1);
send(127 << 8 | 0xC0 | 9);
debug(2, "GS Program Change: Drum Map is CM-64/32L");
// Set Master Chorus to 0. The MT-32 has no chorus capability.
memcpy(&buffer[4], "\x40\x01\x3A\x00\x05", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Master Chorus Level is 0");
// Set Channels 1-16 Reverb to 64, which is the
// equivalent of MT-32 default Reverb Level 5
for (i = 0; i < 16; ++i)
send((64 << 16) | (91 << 8) | (0xB0 | i));
debug(2, "GM Controller 91 Change: Channels 1-16 Reverb Level is 64");
// Set Channels 1-16 Pitch Bend Sensitivity to
// 12 semitones; then lock the RPN by setting null.
for (i = 0; i < 16; ++i)
setPitchBendRange(i, 12);
debug(2, "GM Controller 6 Change: Channels 1-16 Pitch Bend Sensitivity is 12 semitones");
// Set channels 1-16 Mod. LFO1 Pitch Depth to 4
memcpy(&buffer[4], "\x40\x20\x04\x04\x18", 5);
for (i = 0; i < 16; ++i) {
buffer[5] = 0x20 + i;
buffer[8] = 0x18 - i;
sysEx(buffer, 9);
}
debug(2, "GS SysEx: Channels 1-16 Mod. LFO1 Pitch Depth Level is 4");
// Set Percussion Channel Expression to 80
getPercussionChannel()->controlChange(11, 80);
debug(2, "GM Controller 11 Change: Percussion Channel Expression Level is 80");
// Turn off Percussion Channel Rx. Expression so that
// Expression cannot be modified. I don't know why, but
// Roland does it this way.
memcpy(&buffer[4], "\x40\x10\x0E\x00\x22", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Percussion Channel Rx. Expression is OFF");
// Change Reverb Character to 0. I don't think this
// sounds most like MT-32, but apparently Roland does.
memcpy(&buffer[4], "\x40\x01\x31\x00\x0E", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Reverb Character is 0");
// Change Reverb Pre-LF to 4, which is similar to
// what MT-32 reverb does.
memcpy(&buffer[4], "\x40\x01\x32\x04\x09", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Reverb Pre-LF is 4");
// Change Reverb Time to 106; the decay on Hall 2
// Reverb is too fast compared to the MT-32's
memcpy(&buffer[4], "\x40\x01\x34\x6A\x21", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Reverb Time is 106");
}
void IMuseDriver_GMidi::createChannels() {
releaseChannels();
_imsParts = new IMuseChannel_GMidi*[_numChannels];
_controlChan = new GMidiControlChan*[12];
assert(_imsParts);
assert(_controlChan);
for (int i = 0; i < _numChannels; ++i)
_imsParts[i] = new IMuseChannel_GMidi(this, i);
for (int i = 0; i < 12; ++i) {
_controlChan[i] = new GMidiControlChan();
connect(_idleChain, _controlChan[i]);
}
if (_newSystem) {
_notesPlaying = new uint16[128]();
_notesSustained = new uint16[128]();
}
}
void IMuseDriver_GMidi::releaseChannels() {
if (_imsParts) {
for (int i = 0; i < _numChannels; ++i)
delete _imsParts[i];
delete[] _imsParts;
_imsParts = nullptr;
}
if (_controlChan) {
for (int i = 0; i < 12; ++i)
delete _controlChan[i];
delete[] _controlChan;
_controlChan = nullptr;
}
delete[] _notesPlaying;
_notesPlaying = nullptr;
delete[] _notesSustained;
_notesSustained = nullptr;
}
#undef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
} // End of namespace Scumm

View File

@ -1,84 +0,0 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SCUMM_IMUSE_DRV_GMIDI_H
#define SCUMM_IMUSE_DRV_GMIDI_H
#include "audio/mididrv.h"
namespace Scumm {
class IMuseChannel_GMidi;
class GMidiControlChan;
class IMuseDriver_GMidi : public MidiDriver {
friend class IMuseChannel_GMidi;
public:
IMuseDriver_GMidi(MidiDriver::DeviceHandle dev, bool rolandGSMode, bool newSystem);
~IMuseDriver_GMidi() override;
int open() override;
void close() override;
// Just pass these through...
bool isOpen() const override { return _drv ? _drv->isOpen() : false; }
uint32 property(int prop, uint32 param) override { return _drv ? _drv->property(prop, param) : 0; }
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override { if (_drv) _drv->setTimerCallback(timerParam, timerProc); }
uint32 getBaseTempo() override { return _drv ? _drv->getBaseTempo() : 0; }
void send(uint32 b) override { if (_drv) _drv->send(b); };
void sysEx(const byte *msg, uint16 length) override { if (_drv) _drv->sysEx(msg, length); }
void setPitchBendRange(byte channel, uint range) override { if (_drv) _drv->setPitchBendRange(channel, range); }
// Channel allocation functions
MidiChannel *allocateChannel() override;
MidiChannel *getPercussionChannel() override;
private:
void initDevice();
void initDeviceAsRolandGS();
void createChannels();
void releaseChannels();
void setNoteFlag(byte chan, byte note) { if (_notesPlaying && chan < 16 && note < 128) _notesPlaying[note] |= (1 << chan); }
void clearNoteFlag(byte chan, byte note) { if (_notesPlaying && chan < 16 && note < 128) _notesPlaying[note] &= ~(1 << chan); }
bool queryNoteFlag(byte chan, byte note) const { return (_notesPlaying && chan < 16 && note < 128) ? _notesPlaying[note] & (1 << chan) : false; }
void setSustainFlag(byte chan, byte note) { if (_notesSustained && chan < 16 && note < 128) _notesSustained[note] |= (1 << chan); }
void clearSustainFlag(byte chan, byte note) { if (_notesSustained && chan < 16 && note < 128) _notesSustained[note] &= ~(1 << chan); }
bool querySustainFlag(byte chan, byte note) const { return (_notesSustained && chan < 16 && note < 128) ? _notesSustained[note] & (1 << chan) : false; }
MidiDriver *_drv;
const bool _newSystem;
const bool _gsMode;
const byte _numChannels;
IMuseChannel_GMidi **_imsParts;
GMidiControlChan **_controlChan;
GMidiControlChan *_idleChain;
GMidiControlChan *_activeChain;
uint16 *_notesPlaying;
uint16 *_notesSustained;
};
} // End of namespace Scumm
#endif

View File

@ -0,0 +1,963 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "base/version.h"
#include "common/debug.h"
#include "common/system.h"
#include "scumm/imuse/drivers/midi.h"
// This makes older titles play new system style, with 16 virtual channels and
// dynamic allocation (instead of playing on fixed channels).
//#define FORCE_NEWSTYLE_CHANNEL_ALLOCATION
namespace Scumm {
/*******************************
* General Midi driver
********************************/
struct ChannelNode;
class IMuseChannel_Midi : public MidiChannel {
public:
IMuseChannel_Midi(IMuseDriver_GMidi *drv, int number);
~IMuseChannel_Midi() override {}
MidiDriver *device() override { return _drv; }
byte getNumber() override { return _number; }
virtual bool allocate();
void release() override { _allocated = false; }
void send(uint32 b) override { if (_drv) _drv->send((b & ~0x0F) | _number); }
// Regular messages
void noteOff(byte note) override;
void noteOn(byte note, byte velocity) override;
void controlChange(byte control, byte value) override;
virtual void programChange(byte program) override;
void pitchBend(int16 bend) override;
// Control Change and SCUMM specific functions
void pitchBendFactor(byte value) override { _pitchBendSensitivity = value; }
void transpose(int8 value) override { _transpose = value; }
void detune(byte value) override { _detune = value; }
void priority(byte value) override { _prio = value; }
void sustain(bool value) override;
void allNotesOff() override;
virtual void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override {}
virtual void setOutput(ChannelNode*) {}
protected:
virtual void sendMidi(byte evt, byte par1, byte par2) {
if (_drv)
_drv->send(((par2) << 16) | ((par1) << 8) | ((evt & 0xF0) | _number));
}
IMuseDriver_GMidi *_drv;
const byte _number;
const bool _newSystem;
int16 _pitchBend;
ChannelNode *_dummyNode;
private:
void noteOffIntern(byte note);
void noteOnIntern(byte note, byte velocity);
virtual bool validateTransmission(byte note) const { return true; }
void setNotePlaying(byte note) { _drv->setNoteFlag(_number, note); }
void clearNotePlaying(byte note) { _drv->clearNoteFlag(_number, note); }
bool isNotePlaying(byte note) const { return _drv->queryNoteFlag(_number, note); }
void setNoteSustained(byte note) { _drv->setSustainFlag(_number, note); }
void clearNoteSustained(byte note) { _drv->clearSustainFlag(_number, note); }
bool isNoteSustained(byte note) const { return _drv->querySustainFlag(_number, note); }
virtual void sendNoteOff(byte note);
virtual void sendNoteOn(byte note, byte velocity);
bool _allocated;
byte _polyphony;
byte _channelUsage;
bool _exhaust;
byte _prio;
int8 _detune;
int8 _transpose;
int16 _pitchBendTemp;
byte _pitchBendSensitivity;
bool _sustain;
ChannelNode *&_idleChain;
ChannelNode *&_activeChain;
};
struct ChannelNode {
ChannelNode() : _prev(nullptr), _next(nullptr), _in(nullptr), _number(0), _note(0), _addr(0) {}
ChannelNode *_prev;
ChannelNode *_next;
IMuseChannel_Midi *_in;
byte _number;
byte _note;
// MT-32 only
uint32 _addr;
};
void connect(ChannelNode *&chain, ChannelNode *node) {
if (!node || node->_prev || node->_next)
return;
if ((node->_next = chain))
chain->_prev = node;
chain = node;
}
void disconnect(ChannelNode *&chain, ChannelNode *node) {
if (!node || !chain)
return;
const ChannelNode *ch = chain;
while (ch && ch != node)
ch = ch->_next;
if (!ch)
return;
if (node->_next)
node->_next->_prev = node->_prev;
if (node->_prev)
node->_prev->_next = node->_next;
else
chain = node->_next;
node->_next = node->_prev = nullptr;
}
IMuseChannel_Midi::IMuseChannel_Midi(IMuseDriver_GMidi *drv, int number) :MidiChannel(), _drv(drv), _number(number), _allocated(false), _sustain(false),
_pitchBend(0x2000), _polyphony(1), _channelUsage(0), _exhaust(false), _prio(0x80), _detune(0), _transpose(0), _pitchBendTemp(0), _pitchBendSensitivity(0),
_activeChain(drv ? _drv->_activeChain : _dummyNode), _idleChain(drv ? _drv->_idleChain : _dummyNode), _newSystem(drv ? drv->_newSystem : false) {
assert(_drv);
if (!_newSystem)
_pitchBendSensitivity = 2;
}
bool IMuseChannel_Midi::allocate() {
if (_allocated)
return false;
_channelUsage = 0;
_exhaust = false;
return (_allocated = true);
}
void IMuseChannel_Midi::noteOff(byte note) {
if (_newSystem) {
if (!isNotePlaying(note))
return;
clearNotePlaying(note);
if (_sustain) {
setNoteSustained(note);
return;
}
}
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
noteOffIntern(note);
#else
if (_newSystem)
noteOffIntern(note);
else
sendMidi(0x80, note, 0x40);
#endif
}
void IMuseChannel_Midi::noteOn(byte note, byte velocity) {
if (_newSystem) {
if (isNotePlaying(note)) {
noteOffIntern(note);
} else if (isNoteSustained(note)) {
setNotePlaying(note);
clearNoteSustained(note);
noteOffIntern(note);
} else {
setNotePlaying(note);
}
}
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
noteOnIntern(note, velocity);
#else
if (_newSystem)
noteOnIntern(note, velocity);
else
sendMidi(0x90, note, velocity);
#endif
}
void IMuseChannel_Midi::controlChange(byte control, byte value) {
switch (control) {
case 1:
case 7:
case 10:
case 91:
case 93:
sendMidi(0xB0, control, value);
break;
case 17:
if (_newSystem)
_polyphony = value;
else
detune(value);
break;
case 18:
priority(value);
break;
case 123:
allNotesOff();
break;
default:
// The original SAMNMAX driver does not pass through "blindly". The
// only controls that get sent are 1, 7, 10, 91 and 93.
if (_newSystem)
warning("Unhandled Control: %d", control);
else
sendMidi(0xB0, control, value);
break;
}
}
void IMuseChannel_Midi::programChange(byte program) {
sendMidi(0xC0, program, 0);
}
void IMuseChannel_Midi::pitchBend(int16 bend) {
_pitchBend = bend + 0x2000;
sendMidi(0xE0, _pitchBend & 0x7F, (_pitchBend >> 7) & 0x7F);
}
void IMuseChannel_Midi::sustain(bool value) {
_sustain = value;
if (_newSystem) {
// For SAMNMAX, this is fully software controlled. No control change message gets sent.
if (_sustain)
return;
for (int i = 0; i < 128; ++i) {
if (isNoteSustained(i))
noteOffIntern(i);
}
} else {
sendMidi(0xB0, 0x40, value);
}
}
void IMuseChannel_Midi::allNotesOff() {
if (_newSystem) {
// For SAMNMAX, this is fully software controlled. No control change message gets sent.
if (_sustain)
return;
for (int i = 0; i < 128; ++i) {
if (isNotePlaying(i)) {
noteOffIntern(i);
clearNotePlaying(i);
} else if (isNoteSustained(i)) {
noteOffIntern(i);
clearNoteSustained(i);
}
}
} else {
sendMidi(0xB0, 0x7B, 0);
}
}
void IMuseChannel_Midi::noteOffIntern(byte note) {
if (!_activeChain || !validateTransmission(note))
return;
ChannelNode *node = nullptr;
for (ChannelNode *i = _activeChain; i; i = i->_next) {
if (i->_number == _number && i->_note == note) {
node = i;
break;
}
}
if (!node)
return;
sendNoteOff(note);
if (_newSystem)
_exhaust = (--_channelUsage > _polyphony);
disconnect(_activeChain, node);
connect(_idleChain, node);
}
void IMuseChannel_Midi::noteOnIntern(byte note, byte velocity) {
if (!validateTransmission(note))
return;
ChannelNode *node = nullptr;
if (_idleChain) {
node = _idleChain;
disconnect(_idleChain, node);
} else {
IMuseChannel_Midi *best = this;
for (ChannelNode *i = _activeChain; i; i = i->_next) {
assert (i->_in);
if ((best->_exhaust == i->_in->_exhaust && best->_prio >= i->_in->_prio) || (!best->_exhaust && i->_in->_exhaust)) {
best = i->_in;
node = i;
}
}
if (!node)
return;
IMuseChannel_Midi *prt = _drv->getPart(node->_number);
if (prt)
prt->sendMidi(0x80, node->_note, 0x40);
if (_newSystem && prt)
prt->_exhaust = (--prt->_channelUsage > prt->_polyphony);
disconnect(_activeChain, node);
}
assert(node);
node->_in = this;
node->_number = _number;
node->_note = note;
connect(_activeChain, node);
if (_newSystem)
_exhaust = (++_channelUsage > _polyphony);
sendNoteOn(note, velocity);
}
void IMuseChannel_Midi::sendNoteOff(byte note) {
sendMidi(0x80, note, 0x40);
}
void IMuseChannel_Midi::sendNoteOn(byte note, byte velocity) {
sendMidi(0x90, note, velocity);
}
IMuseDriver_GMidi::IMuseDriver_GMidi(MidiDriver::DeviceHandle dev, bool rolandGSMode, bool newSystem) : MidiDriver(), _drv(nullptr), _gsMode(rolandGSMode),
_imsParts(nullptr), _newSystem(newSystem), _numChannels(16), _notesPlaying(nullptr), _notesSustained(nullptr), _idleChain(nullptr), _activeChain(nullptr), _numVoices(12) {
_drv = MidiDriver::createMidi(dev);
assert(_drv);
}
IMuseDriver_GMidi::~IMuseDriver_GMidi() {
close();
delete _drv;
}
int IMuseDriver_GMidi::open() {
if (!_drv)
return MERR_CANNOT_CONNECT;
int res = _drv->open();
if (res)
return res;
createChannels();
if (_gsMode)
initDeviceAsRolandGS();
else
initDevice();
return res;
}
void IMuseDriver_GMidi::close() {
if (isOpen() && _drv) {
deinitDevice();
_drv->close();
}
releaseChannels();
}
MidiChannel *IMuseDriver_GMidi::allocateChannel() {
if (!isOpen())
return nullptr;
for (int i = 0; i < _numChannels; ++i) {
IMuseChannel_Midi *ch = _imsParts[i];
if (ch && ch->getNumber() != 9 && ch->allocate())
return ch;
}
return nullptr;
}
MidiChannel *IMuseDriver_GMidi::getPercussionChannel() {
if (!isOpen())
return nullptr;
IMuseChannel_Midi *ch = getPart(9);
if (ch) {
ch->release();
ch->allocate();
}
return ch;
}
IMuseChannel_Midi *IMuseDriver_GMidi::getPart(int number) {
for (int i = 0; i < _numChannels; ++i)
if (_imsParts[i]->getNumber() == number)
return _imsParts[i];
return nullptr;
}
void IMuseDriver_GMidi::createChannels() {
releaseChannels();
createParts();
for (int i = 0; i < _numVoices; ++i) {
ChannelNode *node = new ChannelNode();
assert(node);
connect(_idleChain, node);
}
if (_newSystem) {
_notesPlaying = new uint16[128]();
_notesSustained = new uint16[128]();
}
}
void IMuseDriver_GMidi::createParts() {
_imsParts = new IMuseChannel_Midi*[_numChannels];
assert(_imsParts);
for (int i = 0; i < _numChannels; ++i)
_imsParts[i] = new IMuseChannel_Midi(this, i);
}
void IMuseDriver_GMidi::releaseChannels() {
if (_imsParts) {
for (int i = 0; i < _numChannels; ++i)
delete _imsParts[i];
delete[] _imsParts;
_imsParts = nullptr;
}
int released = 0;
while (_idleChain) {
ChannelNode *node = _idleChain;
disconnect(_idleChain, node);
delete node;
released++;
}
while (_activeChain) {
ChannelNode *node = _activeChain;
disconnect(_activeChain, node);
delete node;
released++;
}
assert(released == 0 || released == _numVoices);
delete[] _notesPlaying;
_notesPlaying = nullptr;
delete[] _notesSustained;
_notesSustained = nullptr;
}
void IMuseDriver_GMidi::initDevice() {
// These are the init messages from the DOTT General Midi driver. This is the major part of the bug fix for bug
// no. 13460 ("DOTT: Incorrect MIDI pitch bending"). SAMNMAX has some less of the default settings (since
// the driver works a bit different), but it uses the same values for the pitch bend range.
for (int i = 0; i < 16; ++i) {
send(0x0064B0 | i);
send(0x0065B0 | i);
send(0x1006B0 | i);
send(0x7F07B0 | i);
send(0x3F0AB0 | i);
send(0x0000C0 | i);
send(0x4000E0 | i);
send(0x0001B0 | i);
send(0x0040B0 | i);
send(0x405BB0 | i);
send(0x005DB0 | i);
send(0x0000B0 | i);
send(0x007BB0 | i);
}
}
void IMuseDriver_GMidi::initDeviceAsRolandGS() {
byte buffer[12];
int i;
// General MIDI System On message
// Resets all GM devices to default settings
memcpy(&buffer[0], "\x7E\x7F\x09\x01", 4);
sysEx(buffer, 4);
debug(2, "GM SysEx: GM System On");
g_system->delayMillis(200);
// All GS devices recognize the GS Reset command,
// even using Roland's ID. It is impractical to
// support other manufacturers' devices for
// further GS settings, as there are limitless
// numbers of them out there that would each
// require individual SysEx commands with unique IDs.
// Roland GS SysEx ID
memcpy(&buffer[0], "\x41\x10\x42\x12", 4);
// GS Reset
memcpy(&buffer[4], "\x40\x00\x7F\x00\x41", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: GS Reset");
g_system->delayMillis(200);
// Set global Master Tune to 442.0kHz, as on the MT-32
memcpy(&buffer[4], "\x40\x00\x00\x00\x04\x04\x0F\x29", 8);
sysEx(buffer, 12);
debug(2, "GS SysEx: Master Tune set to 442.0kHz");
// Note: All Roland GS devices support CM-64/32L maps
// Set Channels 1-16 to SC-55 Map, then CM-64/32L Variation
for (i = 0; i < 16; ++i) {
_drv->send((127 << 16) | (0 << 8) | (0xB0 | i));
_drv->send((1 << 16) | (32 << 8) | (0xB0 | i));
_drv->send((0 << 16) | (0 << 8) | (0xC0 | i));
}
debug(2, "GS Program Change: CM-64/32L Map Selected");
// Set Percussion Channel to SC-55 Map (CC#32, 01H), then
// Switch Drum Map to CM-64/32L (MT-32 Compatible Drums)
getPercussionChannel()->controlChange(0, 0);
getPercussionChannel()->controlChange(32, 1);
send(127 << 8 | 0xC0 | 9);
debug(2, "GS Program Change: Drum Map is CM-64/32L");
// Set Master Chorus to 0. The MT-32 has no chorus capability.
memcpy(&buffer[4], "\x40\x01\x3A\x00\x05", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Master Chorus Level is 0");
// Set Channels 1-16 Reverb to 64, which is the
// equivalent of MT-32 default Reverb Level 5
for (i = 0; i < 16; ++i)
send((64 << 16) | (91 << 8) | (0xB0 | i));
debug(2, "GM Controller 91 Change: Channels 1-16 Reverb Level is 64");
// Set Channels 1-16 Pitch Bend Sensitivity to
// 12 semitones; then lock the RPN by setting null.
for (i = 0; i < 16; ++i)
setPitchBendRange(i, 12);
debug(2, "GM Controller 6 Change: Channels 1-16 Pitch Bend Sensitivity is 12 semitones");
// Set channels 1-16 Mod. LFO1 Pitch Depth to 4
memcpy(&buffer[4], "\x40\x20\x04\x04\x18", 5);
for (i = 0; i < 16; ++i) {
buffer[5] = 0x20 + i;
buffer[8] = 0x18 - i;
sysEx(buffer, 9);
}
debug(2, "GS SysEx: Channels 1-16 Mod. LFO1 Pitch Depth Level is 4");
// Set Percussion Channel Expression to 80
getPercussionChannel()->controlChange(11, 80);
debug(2, "GM Controller 11 Change: Percussion Channel Expression Level is 80");
// Turn off Percussion Channel Rx. Expression so that
// Expression cannot be modified. I don't know why, but
// Roland does it this way.
memcpy(&buffer[4], "\x40\x10\x0E\x00\x22", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Percussion Channel Rx. Expression is OFF");
// Change Reverb Character to 0. I don't think this
// sounds most like MT-32, but apparently Roland does.
memcpy(&buffer[4], "\x40\x01\x31\x00\x0E", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Reverb Character is 0");
// Change Reverb Pre-LF to 4, which is similar to
// what MT-32 reverb does.
memcpy(&buffer[4], "\x40\x01\x32\x04\x09", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Reverb Pre-LF is 4");
// Change Reverb Time to 106; the decay on Hall 2
// Reverb is too fast compared to the MT-32's
memcpy(&buffer[4], "\x40\x01\x34\x6A\x21", 5);
sysEx(buffer, 9);
debug(2, "GS SysEx: Reverb Time is 106");
}
void IMuseDriver_GMidi::deinitDevice() {
for (int i = 0; i < 16; ++i) {
send(0x0040B0 | i);
send(0x007BB0 | i);
}
}
/**************************
* MT-32 driver
***************************/
class IMuseChannel_MT32 : public IMuseChannel_Midi {
public:
IMuseChannel_MT32(IMuseDriver_MT32 *drv, int number);
~IMuseChannel_MT32() override {}
bool allocate() override;
// Regular messages
void programChange(byte program) override;
// Control Change and SCUMM specific functions
void volume(byte value) override;
void panPosition(byte value) override;
void modulationWheel(byte value) override;
void effectLevel(byte value) override;
void chorusLevel(byte value) override {}
void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override;
void setOutput(ChannelNode *out) override { _out = out; }
private:
bool validateTransmission(byte note) const { return _program < 128 && (!_newSystem || !(_number == 9 && note > 75)); }
void sendMidi(byte stat, byte par1, byte par2) override {
if (_drv && (_out || _number == 9))
_drv->send(((par2) << 16) | ((par1) << 8) | ((stat & 0xF0) | (_out ? _out->_number : 9)));
}
void sendNoteOff(byte note) override;
void sendNoteOn(byte note, byte velocity) override;
void sendSysexPatchData(byte offset, const byte *data, uint32 dataSize) const;
void sendSysexTimbreData(const byte *data, uint32 dataSize) const;
IMuseDriver_MT32 *_mt32Drv;
ChannelNode *_out;
byte _program;
byte _timbre;
byte _volume;
byte _panPos;
byte _reverbSwitch;
ChannelNode *&_hwRealChain;
const byte *_programsMapping;
const uint32 _sysexPatchAddrBase;
const uint32 _sysexTimbreAddrBase;
enum SysexMessageSize {
kSysexLengthTimbre = 254
};
};
IMuseChannel_MT32::IMuseChannel_MT32(IMuseDriver_MT32 *drv, int number) : IMuseChannel_Midi(drv, number), _out(nullptr), _program(0), _timbre(0xFF),
_volume(0x7F), _panPos(0x40), _reverbSwitch(1), _sysexPatchAddrBase(0x14000 + (number << 3)), _sysexTimbreAddrBase(0x20000 + (number << 8)),
_mt32Drv(drv), _programsMapping(drv ? drv->_programsMapping : nullptr), _hwRealChain(drv ? drv->_hwRealChain : _dummyNode) {
}
bool IMuseChannel_MT32::allocate() {
bool res = IMuseChannel_Midi::allocate();
if (res && !_newSystem) {
byte msg[] = { (byte)(_timbre >> 6), (byte)(_timbre & 0x3F), 0x18, 0x32, 0x10, 0x00, _reverbSwitch};
sendSysexPatchData(0, msg, sizeof(msg));
_program = _number;
}
return res;
}
void IMuseChannel_MT32::programChange(byte program) {
if (program > 127)
return;
if (_newSystem) {
if (_programsMapping)
program = _programsMapping[program];
_program = program;
} else if (_timbre != program) {
_timbre = program;
byte msg[2] = { (byte)(program >> 6), (byte)(program & 0x3F) };
sendSysexPatchData(0, msg, sizeof(msg));
}
if (_program < 128)
sendMidi(0xC0, _program, 0);
}
void IMuseChannel_MT32::volume(byte value) {
_volume = value;
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
if (_number != 9)
#else
if (!_newSystem || _number != 9)
#endif
sendMidi(0xB0, 0x07, value);
}
void IMuseChannel_MT32::panPosition(byte value) {
_panPos = value;
sendMidi(0xB0, 0x0A, value);
}
void IMuseChannel_MT32::modulationWheel(byte value) {
if (!_newSystem)
sendMidi(0xB0, 0x01, value);
}
void IMuseChannel_MT32::effectLevel(byte value) {
// The SAMNMAX Roland MT-32 driver ignores this (same with most of the other
// sysex magic that the older drivers did in several places).
if (_newSystem)
return;
value = value ? 1 : 0;
if (_reverbSwitch == value)
return;
_reverbSwitch = value;
sendSysexPatchData(6, &_reverbSwitch, 1);
if (_out)
_mt32Drv->sendMT32Sysex(_out->_addr + 6, &_reverbSwitch, 1);
}
void IMuseChannel_MT32::sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) {
if (type != 'ROL ') {
warning("IMuseChannel_MT32: Receiving '%c%c%c%c' instrument data. Probably loading a savegame with that sound setting", (type >> 24) & 0xFF, (type >> 16) & 0xFF, (type >> 8) & 0xFF, type & 0xFF);
return;
}
if (*instr++ != 0x41 || dataSize < 6) {
warning("IMuseChannel_MT32::sysEx_customInstrument(): Invalid sysex message received");
return;
}
byte partNo = *instr;
uint32 addr = (instr[3] << 14) | (instr[4] << 7) | instr[5];
if (dataSize == kSysexLengthTimbre) {
if (!(addr & 0xFFFF) || partNo < 16) {
sendSysexTimbreData(instr + 6, 246);
_timbre = 0xFF;
byte msg[2] = { 0x02, _program };
sendSysexPatchData(0, msg, sizeof(msg));
if (_out)
sendMidi(0xC0, _program, 0);
} else {
_mt32Drv->sendMT32Sysex(0x22000 + (partNo << 8), instr + 6, 246);
}
} else {
// We cannot arrive here, since our imuse code calls this function only for instruments.
// So this is just a reminder that the original driver handles more things than we do,
// (but these things are apparently never used and thus not needed).
warning("IMuseChannel_MT32::sysEx_customInstrument(): Unsupported sysex message received");
}
}
void IMuseChannel_MT32::sendNoteOff(byte note) {
sendMidi(0x80, note, 0x40);
}
void IMuseChannel_MT32::sendNoteOn(byte note, byte velocity) {
if (_number == 9) {
sendMidi(0xB0, 0x07, _volume);
sendMidi(0x90, note, velocity);
return;
}
if (!_out) {
ChannelNode *nodeReal = _hwRealChain;
while (nodeReal && nodeReal->_next)
nodeReal = nodeReal->_next;
assert(nodeReal);
assert(nodeReal->_in);
nodeReal->_in->setOutput(nullptr);
nodeReal->_in = this;
_out = nodeReal;
sendMidi(0xB0, 0x7B, 0);
sendMidi(0xB0, 0x07, _volume);
sendMidi(0xB0, 0x0A, _panPos);
sendMidi(0xC0, _program, 0);
sendMidi(0xE0, _pitchBend & 0x7F, (_pitchBend >> 7) & 0x7F);
}
disconnect(_hwRealChain, _out);
connect(_hwRealChain, _out);
sendMidi(0x90, note, velocity);
}
void IMuseChannel_MT32::sendSysexPatchData(byte offset, const byte *data, uint32 dataSize) const {
assert(!_newSystem);
_mt32Drv->sendMT32Sysex(_sysexPatchAddrBase + offset, data, dataSize);
}
void IMuseChannel_MT32::sendSysexTimbreData(const byte *data, uint32 dataSize) const {
assert(!_newSystem);
_mt32Drv->sendMT32Sysex(_sysexTimbreAddrBase, data, dataSize);
}
IMuseDriver_MT32::IMuseDriver_MT32(MidiDriver::DeviceHandle dev, bool newSystem) : IMuseDriver_GMidi(dev, false, newSystem), _programsMapping(nullptr), _hwRealChain(nullptr) {
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
_numChannels = 16;
#else
_numChannels = newSystem ? 16 : 9;
#endif
_numVoices = 9;
assert(_drv);
_drv->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
if (_newSystem)
_programsMapping = MidiDriver::_gmToMt32;
}
void IMuseDriver_MT32::initDevice() {
// Reset the MT-32
sendMT32Sysex(0x1FC000, 0, 0);
g_system->delayMillis(250);
// Setup master tune, reverb mode, reverb time, reverb level,
// channel mapping, partial reserve and master volume
static const char initSysex1[] = "\x40\x00\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x64";
sendMT32Sysex(0x40000, (const byte*)initSysex1, sizeof(initSysex1) - 1);
g_system->delayMillis(250);
if (!_newSystem) {
// Map percussion to notes 24 - 34 without reverb. It still happens in the DOTT driver, but not in the SAMNMAX one.
static const char initSysex2[] = "\x40\x64\x07\x00\x4a\x64\x06\x00\x41\x64\x07\x00\x4b\x64\x08\x00\x45\x64\x06\x00\x44\x64\x0b\x00\x51\x64\x05\x00\x43\x64\x08\x00\x50\x64\x07\x00\x42\x64\x03\x00\x4c\x64\x07\x00";
sendMT32Sysex(0xC090, (const byte*)initSysex2, sizeof(initSysex2) - 1);
g_system->delayMillis(250);
}
const byte pbRange = 0x10;
for (int i = 0; i < 128; ++i) {
sendMT32Sysex(0x014004 + (i << 3), &pbRange, 1);
g_system->delayMillis(5);
}
for (int i = 0; i < 16; ++i) {
send(0x0000C0 | i);
send(0x0040B0 | i);
send(0x007BB0 | i);
}
// Display a welcome message on MT-32 displays. Compute version string (truncated to 20 chars max.)
Common::String infoStr = Common::String("ScummVM ") + gScummVMVersion;
for (int i = (20 - infoStr.size()) >> 1; i > 0; --i)
infoStr = ' ' + infoStr + ' ';
sendMT32Sysex(0x80000, (const byte*)infoStr.c_str(), MIN<uint32>(infoStr.size(), 20));
g_system->delayMillis(1000);
}
void IMuseDriver_MT32::createChannels() {
releaseChannels();
IMuseDriver_GMidi::createChannels();
for (int i = 1; i < 9; ++i) {
ChannelNode *node = new ChannelNode();
assert(node);
node->_number = i;
node->_in = getPart(i);
assert(node->_in);
node->_in->setOutput(node);
node->_addr = 0xC000 + (i << 4);
connect(_hwRealChain, node);
}
}
void IMuseDriver_MT32::createParts() {
_imsParts = new IMuseChannel_Midi*[_numChannels];
assert(_imsParts);
for (int i = 0; i < _numChannels; ++i) {
IMuseChannel_MT32 *prt = new IMuseChannel_MT32(this, (i + 1) & 0x0F);
_imsParts[i] = prt;
}
}
void IMuseDriver_MT32::releaseChannels() {
IMuseDriver_GMidi::releaseChannels();
int released = 0;
while (_hwRealChain) {
ChannelNode *node = _hwRealChain;
disconnect(_hwRealChain, node);
delete node;
released++;
}
assert(released == 0 || released == 8);
}
void IMuseDriver_MT32::sendMT32Sysex(uint32 addr, const byte *data, uint32 dataSize) {
static const byte header[] = { 0x41, 0x10, 0x16, 0x12 };
byte *msg = new byte[sizeof(header) + 4 + dataSize];
memcpy(msg, header, sizeof(header));
byte *dst = msg + sizeof(header);
const byte *src = dst;
*dst++ = (addr >> 14) & 0x7F;
*dst++ = (addr >> 7) & 0x7F;
*dst++ = addr & 0x7F;
while (dataSize) {
*dst++ = *data++;
--dataSize;
}
uint8 checkSum = 0;
while (src < dst)
checkSum -= *src++;
*dst++ = checkSum & 0x7F;
sysEx(msg, dst - msg);
delete[] msg;
}
#undef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
} // End of namespace Scumm

View File

@ -19,22 +19,22 @@
*
*/
#ifndef SCUMM_IMUSE_DRV_MT32_H
#define SCUMM_IMUSE_DRV_MT32_H
#ifndef SCUMM_IMUSE_DRV_GMIDI_H
#define SCUMM_IMUSE_DRV_GMIDI_H
#include "audio/mididrv.h"
namespace Scumm {
class IMuseChannel_Midi;
class IMuseChannel_MT32;
class MT32RealChan;
class MT32ControlChan;
struct ChannelNode;
class IMuseDriver_MT32 : public MidiDriver {
friend class IMuseChannel_MT32;
class IMuseDriver_GMidi : public MidiDriver {
friend class IMuseChannel_Midi;
public:
IMuseDriver_MT32(MidiDriver::DeviceHandle dev, bool newSystem);
~IMuseDriver_MT32() override;
IMuseDriver_GMidi(MidiDriver::DeviceHandle dev, bool rolandGSMode, bool newSystem);
virtual ~IMuseDriver_GMidi() override;
int open() override;
void close() override;
@ -46,20 +46,28 @@ public:
uint32 getBaseTempo() override { return _drv ? _drv->getBaseTempo() : 0; }
void send(uint32 b) override { if (_drv) _drv->send(b); };
void sysEx(const byte *msg, uint16 length) override { if (_drv) _drv->sysEx(msg, length); }
virtual void setPitchBendRange(byte channel, uint range) override { if (_drv) _drv->setPitchBendRange(channel, range); }
// Channel allocation functions
MidiChannel *allocateChannel() override;
MidiChannel *getPercussionChannel() override;
protected:
IMuseChannel_Midi *getPart(int number);
virtual void createChannels();
virtual void createParts();
virtual void releaseChannels();
MidiDriver *_drv;
const bool _newSystem;
byte _numChannels;
byte _numVoices;
IMuseChannel_Midi **_imsParts;
private:
void initDevice();
void createChannels();
void releaseChannels();
IMuseChannel_MT32 *getPart(int number) const;
// Convenience function that allows to send the sysex message with the exact same arguments as they are used in the original drivers.
void sendMT32Sysex(uint32 addr, const byte *data, uint32 dataSize);
virtual void initDevice();
void initDeviceAsRolandGS();
virtual void deinitDevice();
void setNoteFlag(byte chan, byte note) { if (_notesPlaying && chan < 16 && note < 128) _notesPlaying[note] |= (1 << chan); }
void clearNoteFlag(byte chan, byte note) { if (_notesPlaying && chan < 16 && note < 128) _notesPlaying[note] &= ~(1 << chan); }
@ -68,24 +76,35 @@ private:
void clearSustainFlag(byte chan, byte note) { if (_notesSustained && chan < 16 && note < 128) _notesSustained[note] &= ~(1 << chan); }
bool querySustainFlag(byte chan, byte note) const { return (_notesSustained && chan < 16 && note < 128) ? _notesSustained[note] & (1 << chan) : false; }
MidiDriver *_drv;
const bool _newSystem;
const byte _numChannels;
const bool _gsMode;
IMuseChannel_MT32 **_imsParts;
MT32RealChan **_hwOutputChan;
MT32ControlChan **_controlChan;
MT32ControlChan *_idleChain;
MT32RealChan *_hwChain;
MT32ControlChan *_activeChain;
const byte *_programsMapping;
ChannelNode *_idleChain;
ChannelNode *_activeChain;
uint16 *_notesPlaying;
uint16 *_notesSustained;
};
class IMuseDriver_MT32 final : public IMuseDriver_GMidi {
friend class IMuseChannel_MT32;
public:
IMuseDriver_MT32(MidiDriver::DeviceHandle dev, bool newSystem);
~IMuseDriver_MT32() override {}
private:
void initDevice() override;
void createChannels() override;
void createParts() override;
void releaseChannels() override;
// Convenience function that allows to send the sysex message with the exact same arguments as they are used in the original drivers.
void sendMT32Sysex(uint32 addr, const byte *data, uint32 dataSize);
ChannelNode *_hwRealChain;
const byte *_programsMapping;
};
} // End of namespace Scumm
#endif

View File

@ -1,756 +0,0 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/str.h"
#include "common/system.h"
#include "base/version.h"
#include "scumm/imuse/drivers/mt32.h"
// This makes older titles play new system style, with 16 virtual channels and
// dynamic allocation (instead of playing on fixed channels).
//#define FORCE_NEWSTYLE_CHANNEL_ALLOCATION
namespace Scumm {
class IMuseChannel_MT32 : public MidiChannel {
public:
IMuseChannel_MT32(IMuseDriver_MT32 *drv, int number);
~IMuseChannel_MT32() override {}
MidiDriver *device() override { return _drv; }
byte getNumber() override { return _number; }
bool allocate();
void release() override { _allocated = false; }
void send(uint32 b) override { if (_drv) _drv->send((b & ~0x0F) | _number); }
// Regular messages
void noteOff(byte note) override;
void noteOn(byte note, byte velocity) override;
void controlChange(byte control, byte value) override;
void programChange(byte program) override;
void pitchBend(int16 bend) override;
// Control Change and SCUMM specific functions
void volume(byte value) override;
void panPosition(byte value) override;
void pitchBendFactor(byte value) override { _pitchBendSensitivity = value; }
void transpose(int8 value) override { _transpose = value; }
void detune(byte value) override { _detune = value; }
void priority(byte value) override { _prio = value; }
void modulationWheel(byte value) override;
void sustain(bool value) override;
void effectLevel(byte value) override;
void chorusLevel(byte value) override {}
void allNotesOff() override;
void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override;
void setOutput(MT32RealChan *out) { _out = out; }
private:
void noteOffIntern(byte note);
void noteOnIntern(byte note, byte velocity);
void sendSysexPatchData(byte offset, const byte *data, uint32 dataSize) const;
void sendSysexTimbreData(const byte *data, uint32 dataSize) const;
void setNotePlaying(byte note) { _drv->setNoteFlag(_number, note); }
void clearNotePlaying(byte note) { _drv->clearNoteFlag(_number, note); }
bool isNotePlaying(byte note) const { return _drv->queryNoteFlag(_number, note); }
void setNoteSustained(byte note) { _drv->setSustainFlag(_number, note); }
void clearNoteSustained(byte note) { _drv->clearSustainFlag(_number, note); }
bool isNoteSustained(byte note) const { return _drv->querySustainFlag(_number, note); }
IMuseDriver_MT32 *_drv;
const byte _number;
const bool _newSystem;
bool _allocated;
MT32RealChan *_out;
byte _program;
byte _timbre;
byte _volume;
byte _panPos;
byte _polyphony;
byte _channelUsage;
bool _exhaust;
byte _prio;
int8 _detune;
int8 _transpose;
byte _reverbSwitch;
byte _pitchBendSensitivity;
int16 _pitchBend;
bool _sustain;
MT32ControlChan *&_idleChain;
MT32RealChan *&_availHwChain;
MT32ControlChan *&_activeChain;
const byte *_programsMapping;
const uint32 _sysexPatchAddrBase;
const uint32 _sysexTimbreAddrBase;
enum SysexMessageSize {
kSysexLengthTimbre = 254
};
};
class MT32Chan {
public:
MT32Chan(int number, IMuseChannel_MT32 *in) : _prev(nullptr), _next(nullptr), _in(in), _number(number) {}
MT32Chan *_prev;
MT32Chan *_next;
IMuseChannel_MT32 *_in;
byte _number;
};
class MT32RealChan : public MT32Chan {
public:
MT32RealChan(int number, IMuseChannel_MT32 *in, MidiChannel *out) : MT32Chan(number, in), _out(out), _sysexTempAddrBase(0xC000 + (number << 4)) {
assert(_out && _number == out->getNumber());
if (in)
in->setOutput(this);
}
MidiChannel *_out;
const uint32 _sysexTempAddrBase;
};
class MT32ControlChan : public MT32Chan{
public:
MT32ControlChan() : MT32Chan(0, nullptr), _note(0) {}
byte _note;
};
template <typename MT32ChanTmpl>
void connect(MT32ChanTmpl *&chain, MT32Chan *node) {
if (!node || node->_prev || node->_next)
return;
if ((node->_next = chain))
chain->_prev = node;
chain = static_cast<MT32ChanTmpl*>(node);
}
template <typename MT32ChanTmpl>
void disconnect(MT32ChanTmpl *&chain, MT32Chan *node) {
if (!node || !chain)
return;
const MT32Chan *ch = static_cast<MT32Chan*>(chain);
while (ch && ch != node)
ch = ch->_next;
if (!ch)
return;
if (node->_next)
node->_next->_prev = node->_prev;
if (node->_prev)
node->_prev->_next = node->_next;
else
chain = static_cast<MT32ChanTmpl*>(node->_next);
node->_next = node->_prev = nullptr;
}
#define sendMidi(stat, par1, par2) _drv->send(((par2) << 16) | ((par1) << 8) | (stat))
IMuseChannel_MT32::IMuseChannel_MT32(IMuseDriver_MT32 *drv, int number) :MidiChannel(), _drv(drv), _number(number), _allocated(false), _out(nullptr), _sustain(false),
_program(0), _timbre(0xFF), _volume(0x7F), _panPos(0x40), _pitchBend(0x2000), _polyphony(1), _channelUsage(0), _exhaust(false), _prio(0), _detune(0), _transpose(0),
_pitchBendSensitivity(2), _reverbSwitch(1), _idleChain(_drv->_idleChain), _availHwChain(_drv->_hwChain), _activeChain(_drv->_activeChain),
_sysexPatchAddrBase(0x14000 + (number << 3)), _sysexTimbreAddrBase(0x20000 + (number << 8)), _programsMapping(_drv->_programsMapping), _newSystem(_drv->_newSystem) {
assert(_drv);
}
bool IMuseChannel_MT32::allocate() {
if (_allocated)
return false;
if (!_newSystem) {
byte msg[] = { (byte)(_timbre >> 6), (byte)(_timbre & 0x3F), 0x18, 0x32, 0x10, 0x00, _reverbSwitch};
sendSysexPatchData(0, msg, sizeof(msg));
_program = _number;
_prio = 0x80;
}
return (_allocated = true);
}
void IMuseChannel_MT32::noteOff(byte note) {
if (_newSystem) {
if (!isNotePlaying(note))
return;
clearNotePlaying(note);
if (_sustain) {
setNoteSustained(note);
return;
}
}
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
noteOffIntern(note);
#else
if (_newSystem)
noteOffIntern(note);
else if (_out || _number == 9)
sendMidi(0x80 | _number, note, 0x40);
#endif
}
void IMuseChannel_MT32::noteOn(byte note, byte velocity) {
if (_newSystem) {
if (isNotePlaying(note)) {
noteOffIntern(note);
} else if (isNoteSustained(note)) {
setNotePlaying(note);
clearNoteSustained(note);
noteOffIntern(note);
} else {
setNotePlaying(note);
}
}
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
noteOnIntern(note, velocity);
#else
if (_newSystem) {
noteOnIntern(note, velocity);
} else if (_out) {
sendMidi(0x90 | _out->_number, note, velocity);
} else if (_number == 9) {
sendMidi(0xB9, 0x07, _volume);
sendMidi(0x99, note, velocity);
}
#endif
}
void IMuseChannel_MT32::controlChange(byte control, byte value) {
switch (control) {
case 7:
volume(value);
break;
case 10:
panPosition(value);
break;
case 17:
if (_newSystem)
_polyphony = value;
else
detune(value);
break;
case 18:
priority(value);
break;
case 123:
allNotesOff();
break;
default:
warning("Unhandled Control: %d", control);
break;
}
}
void IMuseChannel_MT32::programChange(byte program) {
if (program > 127)
return;
if (_newSystem) {
if (_programsMapping)
program = _programsMapping[program];
_program = program;
} else if (_timbre != program) {
_timbre = program;
byte msg[2] = { (byte)(program >> 6), (byte)(program & 0x3F) };
sendSysexPatchData(0, msg, sizeof(msg));
}
if (_out && _program < 128)
sendMidi(0xC0 | _out->_number, _program, 0);
}
void IMuseChannel_MT32::pitchBend(int16 bend) {
_pitchBend = bend + 0x2000;
if (_out)
sendMidi(0xE0 | _out->_number, _pitchBend & 0x7F, (_pitchBend >> 7) & 0x7F);
}
void IMuseChannel_MT32::volume(byte value) {
_volume = value;
if (_out)
sendMidi(0xB0 | _out->_number, 0x07, value);
}
void IMuseChannel_MT32::panPosition(byte value) {
_panPos = value;
if (_out)
sendMidi(0xB0 | _out->_number, 0x0A, value);
}
void IMuseChannel_MT32::modulationWheel(byte value) {
if (_out && !_newSystem)
sendMidi(0xB0 | _out->_number, 0x01, value);
}
void IMuseChannel_MT32::sustain(bool value) {
_sustain = value;
if (_newSystem) {
// For SAMNMAX, this is fully software controlled. No control change message gets sent.
if (_sustain)
return;
for (int i = 0; i < 128; ++i) {
if (isNoteSustained(i))
noteOffIntern(i);
}
} else if (_out) {
sendMidi(0xB0 | _out->_number, 0x40, value);
}
}
void IMuseChannel_MT32::effectLevel(byte value) {
// The SAMNMAX Roland MT-32 driver ignores this (same with most of the other
// sysex magic that the older drivers did in several places).
if (_newSystem)
return;
value = value ? 1 : 0;
if (_reverbSwitch == value)
return;
_reverbSwitch = value;
sendSysexPatchData(6, &_reverbSwitch, 1);
if (_out)
_drv->sendMT32Sysex(_out->_sysexTempAddrBase + 6, &_reverbSwitch, 1);
}
void IMuseChannel_MT32::allNotesOff() {
if (_newSystem) {
// For SAMNMAX, this is fully software controlled. No control change message gets sent.
if (_sustain)
return;
for (int i = 0; i < 128; ++i) {
if (isNotePlaying(i)) {
noteOffIntern(i);
clearNotePlaying(i);
} else if (isNoteSustained(i)) {
noteOffIntern(i);
clearNoteSustained(i);
}
}
} else if (_out) {
sendMidi(0xB0 | _out->_number, 0x7B, 0);
}
}
void IMuseChannel_MT32::sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) {
if (type != 'ROL ') {
warning("IMuseChannel_MT32: Receiving '%c%c%c%c' instrument data. Probably loading a savegame with that sound setting", (type >> 24) & 0xFF, (type >> 16) & 0xFF, (type >> 8) & 0xFF, type & 0xFF);
return;
}
if (*instr++ != 0x41 || dataSize < 6) {
warning("IMuseChannel_MT32::sysEx_customInstrument(): Invalid sysex message received");
return;
}
byte partNo = *instr;
uint32 addr = (instr[3] << 14) | (instr[4] << 7) | instr[5];
if (dataSize == kSysexLengthTimbre) {
if (!(addr & 0xFFFF) || partNo < 16) {
sendSysexTimbreData(instr + 6, 246);
_timbre = 0xFF;
byte msg[2] = { 0x02, _program };
sendSysexPatchData(0, msg, sizeof(msg));
if (_out)
sendMidi(0xC0 | _out->_number, _program, 0);
} else {
_drv->sendMT32Sysex(0x22000 + (partNo << 8), instr + 6, 246);
}
} else {
// We cannot arrive here, since our imuse code calls this function only for instruments.
// So this is just a reminder that the original driver handles more things than we do,
// (but these things are apparently never used and thus not needed).
warning("IMuseChannel_MT32::sysEx_customInstrument(): Unsupported sysex message received");
}
}
void IMuseChannel_MT32::noteOffIntern(byte note) {
if (_program > 127 || _activeChain == nullptr)
return;
MT32Chan *node = nullptr;
for (MT32ControlChan *i = _activeChain; i; i = static_cast<MT32ControlChan*>(i->_next)) {
if (i->_number == _number && i->_note == note) {
node = i;
break;
}
}
if (!node)
return;
if (_out)
sendMidi(0x80 | _out->_number, note, 0x40);
else
sendMidi(0x89, note, 0x40);
if (_newSystem)
_exhaust = (--_channelUsage > _polyphony);
disconnect(_activeChain, node);
connect(_idleChain, node);
}
void IMuseChannel_MT32::noteOnIntern(byte note, byte velocity) {
if (_program > 127 || (_newSystem && _number == 9 && note > 75))
return;
MT32ControlChan *node = nullptr;
if (_idleChain) {
node = _idleChain;
disconnect(_idleChain, node);
} else {
MT32Chan *foundChan = nullptr;
IMuseChannel_MT32 *best = this;
for (MT32Chan *i = _activeChain; i; i = i->_next) {
assert (i->_in);
if ((best->_exhaust == i->_in->_exhaust && best->_prio >= i->_in->_prio) || (!best->_exhaust && i->_in->_exhaust)) {
best = i->_in;
foundChan = i;
}
}
if (!foundChan)
return;
node = static_cast<MT32ControlChan*>(foundChan);
IMuseChannel_MT32 *prt = _drv->getPart(node->_number);
if (prt && prt->_out)
sendMidi(0x80 | prt->_out->_number, node->_note, 0x40);
else if (node->_number == 9)
sendMidi(0x89, node->_note, 0x40);
if (_newSystem && prt)
prt->_exhaust = (--prt->_channelUsage > prt->_polyphony);
disconnect(_activeChain, node);
}
assert(node);
node->_in = this;
node->_number = _number;
node->_note = note;
connect(_activeChain, node);
if (_newSystem)
_exhaust = (++_channelUsage > _polyphony);
if (_number == 9) {
sendMidi(0xB9, 0x07, _volume);
sendMidi(0x99, note, velocity);
return;
}
if (!_out) {
MT32RealChan *nodeReal = _availHwChain;
while (nodeReal && nodeReal->_next)
nodeReal = static_cast<MT32RealChan*>(nodeReal->_next);
assert(nodeReal);
assert(nodeReal->_in);
nodeReal->_in->_out = nullptr;
nodeReal->_in = this;
_out = nodeReal;
sendMidi(0xB0 | _out->_number, 0x7B, 0);
sendMidi(0xB0 | _out->_number, 0x07, _volume);
sendMidi(0xB0 | _out->_number, 0x0A, _panPos);
sendMidi(0xC0 | _out->_number, _program, 0);
sendMidi(0xE0 | _out->_number, _pitchBend & 0x7F, (_pitchBend >> 7) & 0x7F);
}
disconnect(_availHwChain, _out);
connect(_availHwChain, _out);
sendMidi(0x90 | _out->_number, note, velocity);
}
void IMuseChannel_MT32::sendSysexPatchData(byte offset, const byte *data, uint32 dataSize) const {
assert(!_newSystem);
_drv->sendMT32Sysex(_sysexPatchAddrBase + offset, data, dataSize);
}
void IMuseChannel_MT32::sendSysexTimbreData(const byte *data, uint32 dataSize) const {
assert(!_newSystem);
_drv->sendMT32Sysex(_sysexTimbreAddrBase, data, dataSize);
}
#undef sendMidi
IMuseDriver_MT32::IMuseDriver_MT32(MidiDriver::DeviceHandle dev, bool newSystem) : MidiDriver(), _newSystem(newSystem), _programsMapping(nullptr), _notesPlaying(nullptr),
_notesSustained(nullptr), _drv(nullptr), _imsParts(nullptr), _hwOutputChan(nullptr), _controlChan(nullptr), _idleChain(nullptr), _hwChain(nullptr), _activeChain(nullptr),
#ifdef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
_numChannels(16) {
#else
_numChannels(newSystem ? 16 : 9) {
#endif
_drv = MidiDriver::createMidi(dev);
assert(_drv);
_drv->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
if (newSystem)
_programsMapping = MidiDriver::_gmToMt32;
}
IMuseDriver_MT32::~IMuseDriver_MT32() {
close();
delete _drv;
}
int IMuseDriver_MT32::open() {
if (!_drv)
return MERR_CANNOT_CONNECT;
int res = _drv->open();
if (res)
return res;
initDevice();
createChannels();
return res;
}
void IMuseDriver_MT32::close() {
if (isOpen()) {
// Reset the MT-32
sendMT32Sysex(0x1FC000, 0, 0);
g_system->delayMillis(250);
if (_drv)
_drv->close();
}
releaseChannels();
}
MidiChannel *IMuseDriver_MT32::allocateChannel() {
if (!isOpen())
return nullptr;
for (int i = 0; i < _numChannels; ++i) {
IMuseChannel_MT32 *ch = _imsParts[i];
if (ch && ch->getNumber() != 9 && ch->allocate())
return ch;
}
return nullptr;
}
MidiChannel *IMuseDriver_MT32::getPercussionChannel() {
if (!isOpen())
return nullptr;
IMuseChannel_MT32 *ch = getPart(9);
if (ch) {
ch->release();
ch->allocate();
}
return ch;
}
void IMuseDriver_MT32::initDevice() {
// Reset the MT-32
sendMT32Sysex(0x1FC000, 0, 0);
g_system->delayMillis(250);
// Setup master tune, reverb mode, reverb time, reverb level,
// channel mapping, partial reserve and master volume
static const char initSysex1[] = "\x40\x00\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x64";
sendMT32Sysex(0x40000, (const byte*)initSysex1, sizeof(initSysex1) - 1);
g_system->delayMillis(250);
if (!_newSystem) {
// Map percussion to notes 24 - 34 without reverb. It still happens in the DOTT driver, but not in the SAMNMAX one.
static const char initSysex2[] = "\x40\x64\x07\x00\x4a\x64\x06\x00\x41\x64\x07\x00\x4b\x64\x08\x00\x45\x64\x06\x00\x44\x64\x0b\x00\x51\x64\x05\x00\x43\x64\x08\x00\x50\x64\x07\x00\x42\x64\x03\x00\x4c\x64\x07\x00";
sendMT32Sysex(0xC090, (const byte*)initSysex2, sizeof(initSysex2) - 1);
g_system->delayMillis(250);
}
const byte pbRange = 0x10;
for (int i = 0; i < 128; ++i) {
sendMT32Sysex(0x014004 + (i << 3), &pbRange, 1);
g_system->delayMillis(5);
}
for (int i = 0; i < 16; ++i) {
send(0x0000C0 | i);
send(0x0040B0 | i);
send(0x007BB0 | i);
}
// Compute version string (truncated to 20 chars max.)
Common::String infoStr = "ScummVM ";
infoStr += gScummVMVersion;
int len = infoStr.size();
if (len > 20)
len = 20;
// Display a welcome message on MT-32 displays.
byte buffer[28];
memcpy(&buffer[0], "\x41\x10\x16\x12\x20\x00\x00", 7);
memcpy(&buffer[7], " ", 20);
memcpy(buffer + 7 + (20 - len) / 2, infoStr.c_str(), len);
byte checksum = 0;
for (int i = 4; i < 27; ++i)
checksum -= buffer[i];
buffer[27] = checksum & 0x7F;
sysEx(buffer, 28);
g_system->delayMillis(1000);
}
void IMuseDriver_MT32::createChannels() {
releaseChannels();
_imsParts = new IMuseChannel_MT32*[_numChannels];
_hwOutputChan = new MT32RealChan*[8];
_controlChan = new MT32ControlChan*[9];
assert(_imsParts);
assert(_hwOutputChan);
assert(_controlChan);
for (int i = 0; i < _numChannels; ++i)
_imsParts[i] = new IMuseChannel_MT32(this, (i + 1) & 0x0F);
MidiChannel *driverChannels[16];
memset(driverChannels, 0, sizeof(driverChannels));
for (int i = 0; i < ARRAYSIZE(driverChannels); ++i)
driverChannels[i] = _drv->allocateChannel();
for (int i = 1; i < 9; ++i) {
MidiChannel *m = nullptr;
for (int ii = 0; m == nullptr && ii < ARRAYSIZE(driverChannels); ++ii) {
if (driverChannels[ii] && driverChannels[ii]->getNumber() == i)
SWAP(m, driverChannels[ii]);
}
if (!m)
error("IMuseDriver_MT32::createChannels(): Failed to create channels.");
_hwOutputChan[i - 1] = new MT32RealChan(i, getPart(i), m);
connect(_hwChain, _hwOutputChan[i - 1]);
}
for (int i = 0; i < ARRAYSIZE(driverChannels); ++i) {
if (driverChannels[i])
driverChannels[i]->release();
}
for (int i = 0; i < 9; ++i) {
_controlChan[i] = new MT32ControlChan();
connect(_idleChain, _controlChan[i]);
}
if (_newSystem) {
_notesPlaying = new uint16[128]();
_notesSustained = new uint16[128]();
}
}
void IMuseDriver_MT32::releaseChannels() {
if (_imsParts) {
for (int i = 0; i < _numChannels; ++i)
delete _imsParts[i];
delete[] _imsParts;
_imsParts = nullptr;
}
if (_hwOutputChan) {
for (int i = 0; i < 8; ++i)
delete _hwOutputChan[i];
delete[] _hwOutputChan;
_hwOutputChan = nullptr;
}
if (_controlChan) {
for (int i = 0; i < 9; ++i)
delete _controlChan[i];
delete[] _controlChan;
_controlChan = nullptr;
}
delete[] _notesPlaying;
_notesPlaying = nullptr;
delete[] _notesSustained;
_notesSustained = nullptr;
}
IMuseChannel_MT32 *IMuseDriver_MT32::getPart(int number) const {
for (int i = 0; i < _numChannels; ++i)
if (_imsParts[i]->getNumber() == number)
return _imsParts[i];
return nullptr;
}
void IMuseDriver_MT32::sendMT32Sysex(uint32 addr, const byte *data, uint32 dataSize) {
static const byte header[] = { 0x41, 0x10, 0x16, 0x12 };
byte *msg = new byte[sizeof(header) + 4 + dataSize];
memcpy(msg, header, sizeof(header));
byte *dst = msg + sizeof(header);
const byte *src = dst;
*dst++ = (addr >> 14) & 0x7F;
*dst++ = (addr >> 7) & 0x7F;
*dst++ = addr & 0x7F;
while (dataSize) {
*dst++ = *data++;
--dataSize;
}
uint8 checkSum = 0;
while (src < dst)
checkSum -= *src++;
*dst++ = checkSum & 0x7F;
sysEx(msg, dst - msg);
delete[] msg;
}
#undef FORCE_NEWSTYLE_CHANNEL_ALLOCATION
} // End of namespace Scumm

View File

@ -33,9 +33,8 @@ MODULE_OBJS := \
imuse/sysex_scumm.o \
imuse/drivers/amiga.o \
imuse/drivers/fmtowns.o \
imuse/drivers/gmidi.o \
imuse/drivers/midi.o \
imuse/drivers/mac_m68k.o \
imuse/drivers/mt32.o \
imuse/drivers/pcspk.o \
input.o \
ks_check.o \

View File

@ -81,8 +81,7 @@
#include "scumm/imuse/drivers/mac_m68k.h"
#include "scumm/imuse/drivers/amiga.h"
#include "scumm/imuse/drivers/fmtowns.h"
#include "scumm/imuse/drivers/gmidi.h"
#include "scumm/imuse/drivers/mt32.h"
#include "scumm/imuse/drivers/midi.h"
#include "scumm/detection_steam.h"
#include "backends/audiocd/audiocd.h"