206 lines
7.5 KiB
C++
206 lines
7.5 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 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 "mididrv_m_mt32.h"
|
|
|
|
namespace Ultima {
|
|
namespace Nuvie {
|
|
|
|
const uint8 MidiDriver_M_MT32::MIDI_NOTE_MAP[] = {
|
|
0x00, 0x0C, 0x0E, 0x10, 0x11, 0x13, 0x15, 0x17,
|
|
0x00, 0x0D, 0x0F, 0x11, 0x12, 0x14, 0x16, 0x18,
|
|
0x00, 0x0B, 0x0D, 0x0F, 0x10, 0x12, 0x14, 0x16
|
|
};
|
|
|
|
MidiDriver_M_MT32::MidiDriver_M_MT32() : MidiDriver_MT32GM(MT_MT32) {
|
|
Common::fill(_mInstrumentMidiChannels, _mInstrumentMidiChannels + sizeof(_mInstrumentMidiChannels), 1);
|
|
Common::fill(_mInstrumentMapping, _mInstrumentMapping + sizeof(_mInstrumentMapping), 0);
|
|
}
|
|
|
|
MidiDriver_M_MT32::~MidiDriver_M_MT32() { }
|
|
|
|
int MidiDriver_M_MT32::open(MidiDriver *driver, bool nativeMT32) {
|
|
int result = MidiDriver_MT32GM::open(driver, nativeMT32);
|
|
if (result == 0)
|
|
setInstrumentRemapping(_mInstrumentMapping);
|
|
|
|
return result;
|
|
}
|
|
|
|
void MidiDriver_M_MT32::send(int8 source, uint32 b) {
|
|
if (!_isOpen) {
|
|
// During the opening of the driver, some MIDI commands are sent to
|
|
// initialize the device. These are not M commands so they are sent
|
|
// straight to the device.
|
|
MidiDriver_MT32GM::send(source, b);
|
|
return;
|
|
}
|
|
|
|
byte mCommand = b & 0xF0;
|
|
if (mCommand >= 0x80) {
|
|
// These commands are either handled by the parser (call, return,
|
|
// set loop point, delay) or are not implemented for MT-32
|
|
// (load instrument, fade). Not all of them have the channel in the
|
|
// low nibble, so they are filtered out here.
|
|
return;
|
|
}
|
|
byte dataChannel = b & 0x0F;
|
|
byte data = (b >> 8) & 0xFF;
|
|
|
|
MChannelData &mChannelData = _mChannelData[dataChannel];
|
|
|
|
// Get the MIDI output channel assigned to this M data channel.
|
|
int8 outputChannel = source < 0 ? dataChannel : mapSourceChannel(source, dataChannel);
|
|
if (outputChannel < 0) {
|
|
warning("MidiDriver_M_MT32::send - Could not map data channel %i to an output channel", dataChannel);
|
|
return;
|
|
}
|
|
|
|
MidiChannelControlData &controlData = *_controlData[outputChannel];
|
|
|
|
byte midiNote;
|
|
byte mNote;
|
|
// Convert M to MIDI events
|
|
switch (mCommand) {
|
|
case 0x00: // Note off
|
|
mNote = data & 0x1F;
|
|
assert(mNote < 24);
|
|
midiNote = MIDI_NOTE_MAP[mNote] + ((data >> 5) * 12);
|
|
noteOnOff(outputChannel, MIDI_COMMAND_NOTE_OFF, midiNote, mChannelData.velocity, source, controlData);
|
|
mChannelData.activeNote = -1;
|
|
break;
|
|
case 0x10: // Note on
|
|
case 0x20: // Set pitch
|
|
// In the original driver, for Note on events, Note off is explicitly
|
|
// called first to turn off the previous note. However, the Note off
|
|
// event is not sent if there is no note active. For Set pitch,
|
|
// Note off is not explicitly called; Note on is called directly.
|
|
// However, Note on turns off any active notes first before sending the
|
|
// Note on event. So despite the different code paths, these events
|
|
// effectively do the same thing: turn off the currently active note on
|
|
// the channel, if there is one, then play the new note on the next
|
|
// tick.
|
|
|
|
if (mChannelData.activeNote >= 0) {
|
|
noteOnOff(outputChannel, MIDI_COMMAND_NOTE_OFF, mChannelData.activeNote, mChannelData.velocity, source, controlData);
|
|
mChannelData.activeNote = -1;
|
|
}
|
|
|
|
mNote = data & 0x1F;
|
|
assert(mNote < 24);
|
|
midiNote = MIDI_NOTE_MAP[mNote] + ((data >> 5) * 12);
|
|
// The new note is queued for playback on the next timer tick
|
|
// (see onTimer).
|
|
if (mChannelData.queuedNote >= 0) {
|
|
warning("MidiDriver_M_MT32::send - Note on on channel %i while a note is already queued", dataChannel);
|
|
}
|
|
mChannelData.queuedNote = midiNote;
|
|
|
|
break;
|
|
case 0x30: // Set level
|
|
// The OPL level is converted to a MIDI note velocity, which is used
|
|
// for notes subsequently played on the M channel. The active note is
|
|
// not affected.
|
|
mChannelData.velocity = (0x3F - (data & 0x3F)) * 1.5;
|
|
break;
|
|
case 0x70: // Program change
|
|
// When instrument assignments are set on the driver, each M instrument
|
|
// is assigned to a fixed MIDI output channel. When a program change
|
|
// event is encountered on an M channel, the MIDI output channel of
|
|
// that M channel is changed to the MIDI channel assigned to the new M
|
|
// instrument.
|
|
int8 newOutputChannel;
|
|
assert(data < 16);
|
|
newOutputChannel = _mInstrumentMidiChannels[data];
|
|
if (newOutputChannel < 0) {
|
|
warning("MidiDriver_M_MT32::send - Received program change for unmapped instrument %i", data);
|
|
break;
|
|
}
|
|
if (newOutputChannel != outputChannel && mChannelData.activeNote >= 0) {
|
|
// Turn off the active note.
|
|
noteOnOff(outputChannel, MIDI_COMMAND_NOTE_OFF, mChannelData.activeNote, mChannelData.velocity, source, controlData);
|
|
mChannelData.activeNote = -1;
|
|
}
|
|
_channelMap[source][dataChannel] = newOutputChannel;
|
|
// Because the assignment of instruments to output channels is fixed,
|
|
// a program change for each channel could be sent once when setting
|
|
// instrument assignments. However, the original driver sends a program
|
|
// change every time the instrument on an M channel is changed.
|
|
programChange(newOutputChannel, data, source, controlData);
|
|
break;
|
|
default:
|
|
// Modulation, slide and vibrato are not implemented for MT-32.
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MidiDriver_M_MT32::metaEvent(int8 source, byte type, const byte *data, uint16 length) {
|
|
// Load instrument is ignored for MT-32; instruments are set using
|
|
// setInstrumentAssignments.
|
|
}
|
|
|
|
void MidiDriver_M_MT32::setInstrumentAssignments(const MInstrumentAssignment *assignments) {
|
|
// Each M instrument used in the played track (up to 16) should get a MIDI
|
|
// output channel and a MIDI instrument assigned to it. The MIDI instrument
|
|
// is set on the output channel and when an M data channel switches to the
|
|
// corresponding M instrument, the data channel is mapped to that output
|
|
// channel.
|
|
for (int i = 0; i < 16; i++) {
|
|
if (assignments[i].midiChannel < 0)
|
|
break;
|
|
|
|
_mInstrumentMidiChannels[i] = assignments[i].midiChannel;
|
|
_mInstrumentMapping[i] = assignments[i].midiInstrument;
|
|
}
|
|
}
|
|
|
|
void MidiDriver_M_MT32::stopAllNotes(bool stopSustainedNotes) {
|
|
MidiDriver_MT32GM::stopAllNotes();
|
|
|
|
// Clear active and queued notes.
|
|
for (int i = 0; i < 9; i++) {
|
|
_mChannelData[i].activeNote = -1;
|
|
_mChannelData[i].queuedNote = -1;
|
|
}
|
|
}
|
|
|
|
void MidiDriver_M_MT32::onTimer() {
|
|
// Play the queued notes for each M channel.
|
|
for (int i = 0; i < 9; i++) {
|
|
if (_mChannelData[i].queuedNote >= 0) {
|
|
int8 outputChannel = mapSourceChannel(0, i);
|
|
if (outputChannel < 0) {
|
|
warning("MidiDriver_M_MT32::onTimer - Could not map data channel %i to an output channel", i);
|
|
continue;
|
|
}
|
|
MidiChannelControlData &controlData = *_controlData[outputChannel];
|
|
noteOnOff(outputChannel, MIDI_COMMAND_NOTE_ON, _mChannelData[i].queuedNote, _mChannelData[i].velocity, 0, controlData);
|
|
_mChannelData[i].activeNote = _mChannelData[i].queuedNote;
|
|
_mChannelData[i].queuedNote = -1;
|
|
}
|
|
}
|
|
|
|
MidiDriver_MT32GM::onTimer();
|
|
}
|
|
|
|
} // End of namespace Nuvie
|
|
} // End of namespace Ultima
|