Files
scummvm-cursorfix/engines/ultima/nuvie/sound/mididrv_m_mt32.cpp
2026-02-02 04:50:13 +01:00

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