mirror of
https://github.com/libretro/scummvm.git
synced 2025-03-06 02:10:28 +00:00
SCUMM: (IMS) - add general midi driver
This is the GM counterpart for the MT32 driver, mostly based on SAMNMAX, but I have also added the necessary things from the DOTT driver.
This commit is contained in:
parent
6984faf958
commit
ead51cb45e
@ -23,14 +23,328 @@
|
||||
#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 {
|
||||
|
||||
IMuseDriver_GMidi::IMuseDriver_GMidi(MidiDriver::DeviceHandle dev, bool rolandGSMode, bool newSystem) : MidiDriver(), _drv(nullptr), _gsMode(rolandGSMode), _newSystem(newSystem) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -42,28 +356,37 @@ int IMuseDriver_GMidi::open() {
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
createChannels();
|
||||
|
||||
if (_gsMode)
|
||||
initDeviceAsRolandGS();
|
||||
else
|
||||
initDevice();
|
||||
initDevice();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void IMuseDriver_GMidi::close() {
|
||||
if (isOpen() && _drv)
|
||||
if (isOpen() && _drv) {
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
send(0x0040B0 | i);
|
||||
send(0x007BB0 | i);
|
||||
}
|
||||
_drv->close();
|
||||
}
|
||||
|
||||
//releaseChannels();
|
||||
releaseChannels();
|
||||
}
|
||||
|
||||
MidiChannel *IMuseDriver_GMidi::allocateChannel() {
|
||||
if (!isOpen())
|
||||
return nullptr;
|
||||
|
||||
// Pass through everything for now.
|
||||
//if (!_newSystem)
|
||||
return _drv->allocateChannel();
|
||||
for (int i = 0; i < _numChannels; ++i) {
|
||||
IMuseChannel_GMidi *ch = _imsParts[i];
|
||||
if (ch && i != 9 && ch->allocate())
|
||||
return ch;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@ -72,20 +395,13 @@ MidiChannel *IMuseDriver_GMidi::getPercussionChannel() {
|
||||
if (!isOpen())
|
||||
return nullptr;
|
||||
|
||||
// Pass through everything for now.
|
||||
//if (!_newSystem)
|
||||
return _drv->getPercussionChannel();
|
||||
|
||||
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.
|
||||
// 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);
|
||||
@ -209,4 +525,50 @@ void IMuseDriver_GMidi::initDeviceAsRolandGS() {
|
||||
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
|
||||
|
@ -26,7 +26,11 @@
|
||||
|
||||
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;
|
||||
@ -41,6 +45,7 @@ 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); }
|
||||
void setPitchBendRange(byte channel, uint range) override { if (_drv) _drv->setPitchBendRange(channel, range); }
|
||||
|
||||
// Channel allocation functions
|
||||
MidiChannel *allocateChannel() override;
|
||||
@ -49,10 +54,29 @@ public:
|
||||
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 _gsMode;
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user