scummvm/audio/adlib_ms.cpp
Coen Rampen b2cf9a580b AUDIO: Add AdLib MS driver callback frequency
This change allows a consumer to specify the desired timer callback frequency
for the AdLib multisource MIDI driver.
2022-05-09 17:19:44 +02:00

1897 lines
95 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 "audio/adlib_ms.h"
#include "common/debug.h"
bool OplInstrumentOperatorDefinition::isEmpty() {
return freqMultMisc == 0 && level == 0 && decayAttack == 0 &&
releaseSustain == 0 && waveformSelect == 0;
}
bool OplInstrumentDefinition::isEmpty() {
if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
return operator0.isEmpty() &&
(rhythmType != RHYTHM_TYPE_BASS_DRUM || operator1.isEmpty());
} else if (!fourOperator) {
return operator0.isEmpty() && operator1.isEmpty();
} else {
return operator0.isEmpty() && operator1.isEmpty() &&
operator2.isEmpty() && operator3.isEmpty();
}
}
uint8 OplInstrumentDefinition::getNumberOfOperators() {
if (rhythmType == RHYTHM_TYPE_UNDEFINED) {
return fourOperator ? 4 : 2;
} else {
// The bass drum rhythm instrument uses 2 operators; the others use
// only 1.
return rhythmType == RHYTHM_TYPE_BASS_DRUM ? 2 : 1;
}
}
OplInstrumentOperatorDefinition &OplInstrumentDefinition::getOperatorDefinition(uint8 operatorNum) {
assert((!fourOperator && operatorNum < 2) || operatorNum < 4);
switch (operatorNum) {
case 0:
return operator0;
case 1:
return operator1;
case 2:
return operator2;
case 3:
return operator3;
default:
// Should not happen.
return operator0;
}
}
void AdLibBnkInstrumentOperatorDefinition::toOplInstrumentOperatorDefinition(OplInstrumentOperatorDefinition &operatorDef, uint8 waveformSelect) {
// Combine the separate fields of the BNK format into complete register values.
operatorDef.freqMultMisc = frequencyMultiplier | (keyScalingRate == 0 ? 0 : 0x10) |
(envelopeGainType == 0 ? 0 : 0x20) | (vibrato == 0 ? 0 : 0x40) | (amplitudeModulation == 0 ? 0 : 0x80);
operatorDef.level = level | (keyScalingLevel << 6);
operatorDef.decayAttack = decay | (attack << 4);
operatorDef.releaseSustain = release | (sustain << 4);
operatorDef.waveformSelect = waveformSelect;
}
void AdLibBnkInstrumentDefinition::toOplInstrumentDefinition(OplInstrumentDefinition &instrumentDef) {
instrumentDef.fourOperator = false;
operator0.toOplInstrumentOperatorDefinition(instrumentDef.operator0, waveformSelect0);
operator1.toOplInstrumentOperatorDefinition(instrumentDef.operator1, waveformSelect1);
instrumentDef.connectionFeedback0 = (operator0.connection == 0 ? 1 : 0) | (operator0.feedback << 1);
// BNK does not support 4 operator.
instrumentDef.connectionFeedback1 = 0;
// TODO Figure out if this is the same as rhythmVoiceNumber
instrumentDef.rhythmNote = 0;
instrumentDef.rhythmType = RHYTHM_TYPE_UNDEFINED;
}
// These are the melodic instrument definitions used by the Win95 SB16 driver.
OplInstrumentDefinition MidiDriver_ADLIB_Multisource::OPL_INSTRUMENT_BANK[128] = {
// 0x00
{ false, { 0x01, 0x8F, 0xF2, 0xF4, 0x00 }, { 0x01, 0x06, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x4B, 0xF2, 0xF4, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x49, 0xF2, 0xF4, 0x00 }, { 0x01, 0x00, 0xF2, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x81, 0x12, 0xF2, 0xF7, 0x00 }, { 0x41, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x57, 0xF1, 0xF7, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x93, 0xF1, 0xF7, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x80, 0xA1, 0xF2, 0x00 }, { 0x16, 0x0E, 0xF2, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x92, 0xC2, 0xF8, 0x00 }, { 0x01, 0x00, 0xC2, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x08
{ false, { 0x0C, 0x5C, 0xF6, 0xF4, 0x00 }, { 0x81, 0x00, 0xF3, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x07, 0x97, 0xF3, 0xF2, 0x00 }, { 0x11, 0x80, 0xF2, 0xF1, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x17, 0x21, 0x54, 0xF4, 0x00 }, { 0x01, 0x00, 0xF4, 0xF4, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x98, 0x62, 0xF3, 0xF6, 0x00 }, { 0x81, 0x00, 0xF2, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x18, 0x23, 0xF6, 0xF6, 0x00 }, { 0x01, 0x00, 0xE7, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x15, 0x91, 0xF6, 0xF6, 0x00 }, { 0x01, 0x00, 0xF6, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x45, 0x59, 0xD3, 0xF3, 0x00 }, { 0x81, 0x80, 0xA3, 0xF3, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x03, 0x49, 0x75, 0xF5, 0x01 }, { 0x81, 0x80, 0xB5, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x10
{ false, { 0x71, 0x92, 0xF6, 0x14, 0x00 }, { 0x31, 0x00, 0xF1, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x72, 0x14, 0xC7, 0x58, 0x00 }, { 0x30, 0x00, 0xC7, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x70, 0x44, 0xAA, 0x18, 0x00 }, { 0xB1, 0x00, 0x8A, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x23, 0x93, 0x97, 0x23, 0x01 }, { 0xB1, 0x00, 0x55, 0x14, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x61, 0x13, 0x97, 0x04, 0x01 }, { 0xB1, 0x80, 0x55, 0x04, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x24, 0x48, 0x98, 0x2A, 0x01 }, { 0xB1, 0x00, 0x46, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x61, 0x13, 0x91, 0x06, 0x01 }, { 0x21, 0x00, 0x61, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x13, 0x71, 0x06, 0x00 }, { 0xA1, 0x89, 0x61, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x18
{ false, { 0x02, 0x9C, 0xF3, 0x94, 0x01 }, { 0x41, 0x80, 0xF3, 0xC8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x03, 0x54, 0xF3, 0x9A, 0x01 }, { 0x11, 0x00, 0xF1, 0xE7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x23, 0x5F, 0xF1, 0x3A, 0x00 }, { 0x21, 0x00, 0xF2, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x03, 0x87, 0xF6, 0x22, 0x01 }, { 0x21, 0x80, 0xF3, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x03, 0x47, 0xF9, 0x54, 0x00 }, { 0x21, 0x00, 0xF6, 0x3A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x23, 0x4A, 0x91, 0x41, 0x01 }, { 0x21, 0x05, 0x84, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x23, 0x4A, 0x95, 0x19, 0x01 }, { 0x21, 0x00, 0x94, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x09, 0xA1, 0x20, 0x4F, 0x00 }, { 0x84, 0x80, 0xD1, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x20
{ false, { 0x21, 0x1E, 0x94, 0x06, 0x00 }, { 0xA2, 0x00, 0xC3, 0xA6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x12, 0xF1, 0x28, 0x00 }, { 0x31, 0x00, 0xF1, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x8D, 0xF1, 0xE8, 0x00 }, { 0x31, 0x00, 0xF1, 0x78, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x5B, 0x51, 0x28, 0x00 }, { 0x32, 0x00, 0x71, 0x48, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x8B, 0xA1, 0x9A, 0x00 }, { 0x21, 0x40, 0xF2, 0xDF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x8B, 0xA2, 0x16, 0x00 }, { 0x21, 0x08, 0xA1, 0xDF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x8B, 0xF4, 0xE8, 0x00 }, { 0x31, 0x00, 0xF1, 0x78, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x12, 0xF1, 0x28, 0x00 }, { 0x31, 0x00, 0xF1, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x28
{ false, { 0x31, 0x15, 0xDD, 0x13, 0x01 }, { 0x21, 0x00, 0x56, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x16, 0xDD, 0x13, 0x01 }, { 0x21, 0x00, 0x66, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x71, 0x49, 0xD1, 0x1C, 0x01 }, { 0x31, 0x00, 0x61, 0x0C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x4D, 0x71, 0x12, 0x01 }, { 0x23, 0x80, 0x72, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xF1, 0x40, 0xF1, 0x21, 0x01 }, { 0xE1, 0x00, 0x6F, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x02, 0x1A, 0xF5, 0x75, 0x01 }, { 0x01, 0x80, 0x85, 0x35, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x02, 0x1D, 0xF5, 0x75, 0x01 }, { 0x01, 0x80, 0xF3, 0xF4, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x10, 0x41, 0xF5, 0x05, 0x01 }, { 0x11, 0x00, 0xF2, 0xC3, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x30
{ false, { 0x21, 0x9B, 0xB1, 0x25, 0x01 }, { 0xA2, 0x01, 0x72, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA1, 0x98, 0x7F, 0x03, 0x01 }, { 0x21, 0x00, 0x3F, 0x07, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA1, 0x93, 0xC1, 0x12, 0x00 }, { 0x61, 0x00, 0x4F, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x18, 0xC1, 0x22, 0x00 }, { 0x61, 0x00, 0x4F, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x5B, 0xF4, 0x15, 0x00 }, { 0x72, 0x83, 0x8A, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA1, 0x90, 0x74, 0x39, 0x00 }, { 0x61, 0x00, 0x71, 0x67, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x71, 0x57, 0x54, 0x05, 0x00 }, { 0x72, 0x00, 0x7A, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x90, 0x00, 0x54, 0x63, 0x00 }, { 0x41, 0x00, 0xA5, 0x45, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x38
{ false, { 0x21, 0x92, 0x85, 0x17, 0x00 }, { 0x21, 0x01, 0x8F, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x94, 0x75, 0x17, 0x00 }, { 0x21, 0x05, 0x8F, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x94, 0x76, 0x15, 0x00 }, { 0x61, 0x00, 0x82, 0x37, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x43, 0x9E, 0x17, 0x01 }, { 0x21, 0x00, 0x62, 0x2C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x9B, 0x61, 0x6A, 0x00 }, { 0x21, 0x00, 0x7F, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x61, 0x8A, 0x75, 0x1F, 0x00 }, { 0x22, 0x06, 0x74, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA1, 0x86, 0x72, 0x55, 0x01 }, { 0x21, 0x83, 0x71, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x4D, 0x54, 0x3C, 0x00 }, { 0x21, 0x00, 0xA6, 0x1C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x40
{ false, { 0x31, 0x8F, 0x93, 0x02, 0x01 }, { 0x61, 0x00, 0x72, 0x0B, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x8E, 0x93, 0x03, 0x01 }, { 0x61, 0x00, 0x72, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x91, 0x93, 0x03, 0x01 }, { 0x61, 0x00, 0x82, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x8E, 0x93, 0x0F, 0x01 }, { 0x61, 0x00, 0x72, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x4B, 0xAA, 0x16, 0x01 }, { 0x21, 0x00, 0x8F, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x90, 0x7E, 0x17, 0x01 }, { 0x21, 0x00, 0x8B, 0x0C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x81, 0x75, 0x19, 0x01 }, { 0x32, 0x00, 0x61, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x32, 0x90, 0x9B, 0x21, 0x00 }, { 0x21, 0x00, 0x72, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x48
{ false, { 0xE1, 0x1F, 0x85, 0x5F, 0x00 }, { 0xE1, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xE1, 0x46, 0x88, 0x5F, 0x00 }, { 0xE1, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA1, 0x9C, 0x75, 0x1F, 0x00 }, { 0x21, 0x00, 0x75, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x8B, 0x84, 0x58, 0x00 }, { 0x21, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xE1, 0x4C, 0x66, 0x56, 0x00 }, { 0xA1, 0x00, 0x65, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x62, 0xCB, 0x76, 0x46, 0x00 }, { 0xA1, 0x00, 0x55, 0x36, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x62, 0x99, 0x57, 0x07, 0x00 }, { 0xA1, 0x00, 0x56, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3B, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x62, 0x93, 0x77, 0x07, 0x00 }, { 0xA1, 0x00, 0x76, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3B, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x50
{ false, { 0x22, 0x59, 0xFF, 0x03, 0x02 }, { 0x21, 0x00, 0xFF, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x0E, 0xFF, 0x0F, 0x01 }, { 0x21, 0x00, 0xFF, 0x0F, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x22, 0x46, 0x86, 0x55, 0x00 }, { 0x21, 0x80, 0x64, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x45, 0x66, 0x12, 0x00 }, { 0xA1, 0x00, 0x96, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x8B, 0x92, 0x2A, 0x01 }, { 0x22, 0x00, 0x91, 0x2A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA2, 0x9E, 0xDF, 0x05, 0x00 }, { 0x61, 0x40, 0x6F, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x20, 0x1A, 0xEF, 0x01, 0x00 }, { 0x60, 0x00, 0x8F, 0x06, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x8F, 0xF1, 0x29, 0x00 }, { 0x21, 0x80, 0xF4, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x58
{ false, { 0x77, 0xA5, 0x53, 0x94, 0x00 }, { 0xA1, 0x00, 0xA0, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x61, 0x1F, 0xA8, 0x11, 0x00 }, { 0xB1, 0x80, 0x25, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x61, 0x17, 0x91, 0x34, 0x00 }, { 0x61, 0x00, 0x55, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x71, 0x5D, 0x54, 0x01, 0x00 }, { 0x72, 0x00, 0x6A, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x97, 0x21, 0x43, 0x00 }, { 0xA2, 0x00, 0x42, 0x35, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA1, 0x1C, 0xA1, 0x77, 0x01 }, { 0x21, 0x00, 0x31, 0x47, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x89, 0x11, 0x33, 0x00 }, { 0x61, 0x03, 0x42, 0x25, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA1, 0x15, 0x11, 0x47, 0x01 }, { 0x21, 0x00, 0xCF, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x60
{ false, { 0x3A, 0xCE, 0xF8, 0xF6, 0x00 }, { 0x51, 0x00, 0x86, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x15, 0x21, 0x23, 0x01 }, { 0x21, 0x00, 0x41, 0x13, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x06, 0x5B, 0x74, 0x95, 0x00 }, { 0x01, 0x00, 0xA5, 0x72, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x22, 0x92, 0xB1, 0x81, 0x00 }, { 0x61, 0x83, 0xF2, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x41, 0x4D, 0xF1, 0x51, 0x01 }, { 0x42, 0x00, 0xF2, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x61, 0x94, 0x11, 0x51, 0x01 }, { 0xA3, 0x80, 0x11, 0x13, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x61, 0x8C, 0x11, 0x31, 0x00 }, { 0xA1, 0x80, 0x1D, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xA4, 0x4C, 0xF3, 0x73, 0x01 }, { 0x61, 0x00, 0x81, 0x23, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x68
{ false, { 0x02, 0x85, 0xD2, 0x53, 0x00 }, { 0x07, 0x03, 0xF2, 0xF6, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x11, 0x0C, 0xA3, 0x11, 0x01 }, { 0x13, 0x80, 0xA2, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x11, 0x06, 0xF6, 0x41, 0x01 }, { 0x11, 0x00, 0xF2, 0xE6, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x93, 0x91, 0xD4, 0x32, 0x00 }, { 0x91, 0x00, 0xEB, 0x11, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x04, 0x4F, 0xFA, 0x56, 0x00 }, { 0x01, 0x00, 0xC2, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x21, 0x49, 0x7C, 0x20, 0x00 }, { 0x22, 0x00, 0x6F, 0x0C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x31, 0x85, 0xDD, 0x33, 0x01 }, { 0x21, 0x00, 0x56, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x20, 0x04, 0xDA, 0x05, 0x02 }, { 0x21, 0x81, 0x8F, 0x0B, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x70
{ false, { 0x05, 0x6A, 0xF1, 0xE5, 0x00 }, { 0x03, 0x80, 0xC3, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x07, 0x15, 0xEC, 0x26, 0x00 }, { 0x02, 0x00, 0xF8, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x05, 0x9D, 0x67, 0x35, 0x00 }, { 0x01, 0x00, 0xDF, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x18, 0x96, 0xFA, 0x28, 0x00 }, { 0x12, 0x00, 0xF8, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x10, 0x86, 0xA8, 0x07, 0x00 }, { 0x00, 0x03, 0xFA, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x11, 0x41, 0xF8, 0x47, 0x02 }, { 0x10, 0x03, 0xF3, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x8E, 0xF1, 0x06, 0x02 }, { 0x10, 0x00, 0xF3, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x00, 0x1F, 0x00, 0x00 }, { 0xC0, 0x00, 0x1F, 0xFF, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x78
{ false, { 0x06, 0x80, 0xF8, 0x24, 0x00 }, { 0x03, 0x88, 0x56, 0x84, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x00, 0xF8, 0x00, 0x00 }, { 0xD0, 0x05, 0x34, 0x04, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xC0, 0x00, 0x1F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xD5, 0x95, 0x37, 0xA3, 0x00 }, { 0xDA, 0x40, 0x56, 0x37, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x35, 0x5C, 0xB2, 0x61, 0x02 }, { 0x14, 0x08, 0xF4, 0x15, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x4F, 0xF5, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x26, 0x00, 0xFF, 0x01, 0x00 }, { 0xE4, 0x00, 0x12, 0x16, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0xF3, 0xF0, 0x00 }, { 0x00, 0x00, 0xF6, 0xC9, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }
};
// These are the rhythm instrument definitions used by the Win95 SB16 driver.
OplInstrumentDefinition MidiDriver_ADLIB_Multisource::OPL_RHYTHM_BANK[62] = {
// GS percussion start
// 0x1B
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// 0x20
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED },
// GM percussion start
// 0x23
{ false, { 0x10, 0x44, 0xF8, 0x77, 0x02 }, { 0x11, 0x00, 0xF3, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x23, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x10, 0x44, 0xF8, 0x77, 0x02 }, { 0x11, 0x00, 0xF3, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x23, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x02, 0x07, 0xF9, 0xFF, 0x00 }, { 0x11, 0x00, 0xF8, 0xFF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x34, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0xFC, 0x05, 0x02 }, { 0x00, 0x00, 0xFA, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x02, 0xFF, 0x07, 0x00 }, { 0x01, 0x00, 0xFF, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x3A, RHYTHM_TYPE_UNDEFINED },
// 0x28
{ false, { 0x00, 0x00, 0xFC, 0x05, 0x02 }, { 0x00, 0x00, 0xFA, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x2F, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0C, 0x00, 0xF6, 0x08, 0x00 }, { 0x12, 0x00, 0xFB, 0x47, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x31, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0C, 0x00, 0xF6, 0x08, 0x00 }, { 0x12, 0x05, 0x7B, 0x47, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x33, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0C, 0x00, 0xF6, 0x02, 0x00 }, { 0x12, 0x00, 0xCB, 0x43, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x36, RHYTHM_TYPE_UNDEFINED },
// 0x30
{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x39, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x48, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x08, 0xF8, 0x42, 0x00 }, { 0x07, 0x4A, 0xF4, 0xE4, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4C, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x00, 0xF5, 0x30, 0x00 }, { 0xD0, 0x0A, 0x9F, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x0A, 0xE4, 0xE4, 0x03 }, { 0x07, 0x5D, 0xF5, 0xE5, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x24, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x02, 0x03, 0xB4, 0x04, 0x00 }, { 0x05, 0x0A, 0x97, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4C, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x4E, 0x00, 0xF6, 0x00, 0x00 }, { 0x9E, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED },
// 0x38
{ false, { 0x11, 0x45, 0xF8, 0x37, 0x02 }, { 0x10, 0x08, 0xF3, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x53, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x80, 0x00, 0xFF, 0x03, 0x03 }, { 0x10, 0x0D, 0xFF, 0x14, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x18, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x08, 0xF8, 0x42, 0x00 }, { 0x07, 0x4A, 0xF4, 0xE4, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4D, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x06, 0x0B, 0xF5, 0x0C, 0x00 }, { 0x02, 0x00, 0xF5, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x00, 0xFA, 0xBF, 0x00 }, { 0x02, 0x00, 0xC8, 0x97, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x37, 0x00, 0x41, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x51, 0xFA, 0x87, 0x00 }, { 0x01, 0x00, 0xFA, 0xB7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x3B, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x54, 0xFA, 0x8D, 0x00 }, { 0x02, 0x00, 0xF8, 0xB8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x33, RHYTHM_TYPE_UNDEFINED },
// 0x40
{ false, { 0x01, 0x59, 0xFA, 0x88, 0x00 }, { 0x02, 0x00, 0xF8, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x2D, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x01, 0x00, 0xF9, 0x0A, 0x03 }, { 0x00, 0x00, 0xFA, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x47, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x80, 0xF9, 0x89, 0x03 }, { 0x00, 0x00, 0xF6, 0x6C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x03, 0x80, 0xF8, 0x88, 0x03 }, { 0x0C, 0x08, 0xF6, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3F, 0x00, 0x3A, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x03, 0x85, 0xF8, 0x88, 0x03 }, { 0x0C, 0x00, 0xF6, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3F, 0x00, 0x35, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x40, 0x76, 0x4F, 0x00 }, { 0x00, 0x08, 0x77, 0x18, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x40, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x0E, 0x40, 0xC8, 0x49, 0x00 }, { 0x03, 0x00, 0x9B, 0x69, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x47, RHYTHM_TYPE_UNDEFINED },
{ false, { 0xD7, 0xDC, 0xAD, 0x05, 0x03 }, { 0xC7, 0x00, 0x8D, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3D, RHYTHM_TYPE_UNDEFINED },
// 0x48
{ false, { 0xD7, 0xDC, 0xA8, 0x04, 0x03 }, { 0xC7, 0x00, 0x88, 0x04, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3D, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x80, 0x00, 0xF6, 0x06, 0x03 }, { 0x11, 0x00, 0x67, 0x17, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x80, 0x00, 0xF5, 0x05, 0x02 }, { 0x11, 0x09, 0x46, 0x16, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x06, 0x3F, 0x00, 0xF4, 0x00 }, { 0x15, 0x00, 0xF7, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x31, 0x00, 0x45, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x06, 0x3F, 0x00, 0xF4, 0x03 }, { 0x12, 0x00, 0xF7, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x44, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x3F, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x4A, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED },
// 0x50
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x50, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x40, RHYTHM_TYPE_UNDEFINED },
// GM percussion end
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x45, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x49, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x4B, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x44, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED },
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x35, RHYTHM_TYPE_UNDEFINED },
// 0x58
{ false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }
// GS percussion end
};
// Rhythm mode uses OPL channels 6, 7 and 8. The remaining channels are
// available for melodic instruments.
uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL2[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL2_RHYTHM[] = { 0, 1, 2, 3, 4, 5 };
uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL3[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL3_RHYTHM[] = { 0, 1, 2, 3, 4, 5, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
const uint8 MidiDriver_ADLIB_Multisource::OPL_REGISTER_RHYTHM_OFFSETS[OPL_NUM_RHYTHM_INSTRUMENTS] = { 0x11, 0x15, 0x12, 0x14, 0x10 };
const uint8 MidiDriver_ADLIB_Multisource::OPL_RHYTHM_INSTRUMENT_CHANNELS[OPL_NUM_RHYTHM_INSTRUMENTS] = { 7, 8, 8, 7, 6 };
// These are the note frequency values used by the Win95 SB16 driver.
const uint16 MidiDriver_ADLIB_Multisource::OPL_NOTE_FREQUENCIES[12] = {
0x0AB7, 0x0B5A, 0x0C07, 0x0CBE, 0x0D80, 0x0E4D, 0x0F27, 0x100E, 0x1102, 0x1205, 0x1318, 0x143A
};
// These are the volume values used by the Win95 SB16 driver.
const uint8 MidiDriver_ADLIB_Multisource::OPL_VOLUME_LOOKUP[32] = {
0x50, 0x3F, 0x28, 0x24, 0x20, 0x1C, 0x17, 0x15, 0x13, 0x11, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A,
0x09, 0x08, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00
};
MidiDriver_ADLIB_Multisource::MidiChannelControlData::MidiChannelControlData() {
init();
}
void MidiDriver_ADLIB_Multisource::MidiChannelControlData::init() {
program = 0;
channelPressure = 0;
pitchBend = MIDI_PITCH_BEND_DEFAULT;
modulation = 0;
volume = 0;
panning = MIDI_PANNING_DEFAULT;
expression = MIDI_EXPRESSION_DEFAULT;
sustain = false;
rpn = MIDI_RPN_NULL;
pitchBendSensitivity = GM_PITCH_BEND_SENSITIVITY_DEFAULT;
pitchBendSensitivityCents = 0;
masterTuningFine = MIDI_MASTER_TUNING_FINE_DEFAULT;
masterTuningCoarse = MIDI_MASTER_TUNING_COARSE_DEFAULT;
}
MidiDriver_ADLIB_Multisource::ActiveNote::ActiveNote() {
init();
}
void MidiDriver_ADLIB_Multisource::ActiveNote::init() {
noteActive = false;
noteSustained = false;
note = 0;
velocity = 0;
channel = 0xFF;
source = 0xFF;
oplNote = 0;
oplFrequency = 0;
noteCounterValue = 0;
instrumentId = 0;
instrumentDef = nullptr;
channelAllocated = false;
}
bool MidiDriver_ADLIB_Multisource::detectOplType(OPL::Config::OplType oplType) {
return OPL::Config::detect(oplType) >= 0;
}
MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(OPL::Config::OplType oplType, int timerFrequency) :
_oplType(oplType),
_opl(nullptr),
_isOpen(false),
_accuracyMode(ACCURACY_MODE_SB16_WIN95),
_allocationMode(ALLOCATION_MODE_DYNAMIC),
_rhythmModeIgnoreNoteOffs(false),
_defaultChannelVolume(0),
_noteSelect(NOTE_SELECT_MODE_0),
_modulationDepth(MODULATION_DEPTH_HIGH),
_vibratoDepth(VIBRATO_DEPTH_HIGH),
_rhythmMode(false),
_instrumentBank(OPL_INSTRUMENT_BANK),
_rhythmBank(OPL_RHYTHM_BANK),
_rhythmBankFirstNote(GS_RHYTHM_FIRST_NOTE),
_rhythmBankLastNote(GS_RHYTHM_LAST_NOTE),
_melodicChannels(nullptr),
_numMelodicChannels(0),
_noteCounter(1),
_oplFrequencyConversionFactor(pow(2, 20) / 49716.0f),
_timerFrequency(timerFrequency) {
memset(_channelAllocations, 0xFF, sizeof(_channelAllocations));
Common::fill(_shadowRegisters, _shadowRegisters + sizeof(_shadowRegisters), 0);
_timerRate = 1000000 / _timerFrequency;
}
MidiDriver_ADLIB_Multisource::~MidiDriver_ADLIB_Multisource() {
close();
}
int MidiDriver_ADLIB_Multisource::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
int8 detectResult = OPL::Config::detect(_oplType);
if (detectResult == -1 && _oplType == OPL::Config::kDualOpl2) {
// Try to emulate dual OPL2 on OPL3
// TODO Implement this in fmopl
detectResult = OPL::Config::detect(OPL::Config::kOpl3);
}
if (detectResult == -1)
return MERR_DEVICE_NOT_AVAILABLE;
// Create the emulator / hardware interface.
_opl = OPL::Config::create(_oplType);
if (!_opl)
return MERR_CANNOT_CONNECT;
_isOpen = true;
// Initialize emulator / hardware interface.
if (!_opl->init())
return MERR_CANNOT_CONNECT;
// Set the melodic channels applicable for the OPL chip type.
determineMelodicChannels();
// Set default MIDI channel volume on control data.
for (int i = 0; i < MAXIMUM_SOURCES; i++) {
for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) {
_controlData[i][j].volume = _defaultChannelVolume;
}
applyControllerDefaults(i);
}
// Set default OPL register values.
initOpl();
// Start the emulator / hardware interface. This will also start the timer
// callbacks.
_opl->start(new Common::Functor0Mem<void, MidiDriver_ADLIB_Multisource>(this, &MidiDriver_ADLIB_Multisource::onTimer), _timerFrequency);
return 0;
}
bool MidiDriver_ADLIB_Multisource::isOpen() const {
return _isOpen;
}
void MidiDriver_ADLIB_Multisource::close() {
if (!_isOpen)
return;
_isOpen = false;
stopAllNotes(true);
if (_opl) {
_opl->stop();
delete _opl;
_opl = nullptr;
}
}
uint32 MidiDriver_ADLIB_Multisource::property(int prop, uint32 param) {
switch (prop) {
case PROP_OPL_ACCURACY_MODE:
if (param == 0xFFFF)
return _accuracyMode;
switch (param) {
case ACCURACY_MODE_GM:
_accuracyMode = ACCURACY_MODE_GM;
break;
case ACCURACY_MODE_SB16_WIN95:
default:
_accuracyMode = ACCURACY_MODE_SB16_WIN95;
}
break;
case PROP_OPL_CHANNEL_ALLOCATION_MODE:
if (param == 0xFFFF)
return _allocationMode;
switch (param) {
case ALLOCATION_MODE_STATIC:
_allocationMode = ALLOCATION_MODE_STATIC;
break;
case ALLOCATION_MODE_DYNAMIC:
default:
_allocationMode = ALLOCATION_MODE_DYNAMIC;
}
break;
case PROP_OPL_RHYTHM_MODE_IGNORE_NOTE_OFF:
if (param == 0xFFFF)
return _rhythmModeIgnoreNoteOffs;
_rhythmModeIgnoreNoteOffs = (param != 0);
break;
default:
return MidiDriver_Multisource::property(prop, param);
}
return 0;
}
uint32 MidiDriver_ADLIB_Multisource::getBaseTempo() {
return _timerRate;
}
MidiChannel *MidiDriver_ADLIB_Multisource::allocateChannel() {
// This driver does not use MidiChannel objects.
return nullptr;
}
MidiChannel *MidiDriver_ADLIB_Multisource::getPercussionChannel() {
// This driver does not use MidiChannel objects.
return nullptr;
}
void MidiDriver_ADLIB_Multisource::send(int8 source, uint32 b) {
byte command = b & 0xF0;
if (source == -1) {
// Source -1 is a shorthand to set controller values for all sources.
if (command == MIDI_COMMAND_NOTE_OFF || command == MIDI_COMMAND_NOTE_ON) {
// Notes should not be sent using source -1, but use source 0 in
// case this happens.
source = 0;
} else {
// Send controller event using all sources.
for (int i = 0; i < MAXIMUM_SOURCES; i++) {
send(i, b);
}
return;
}
}
// Extract the MIDI bytes.
byte channel = b & 0x0F;
byte op1 = (b >> 8) & 0xFF;
byte op2 = (b >> 16) & 0xFF;
switch (command) {
case MIDI_COMMAND_NOTE_OFF:
noteOff(channel, op1, op2, source);
break;
case MIDI_COMMAND_NOTE_ON:
noteOn(channel, op1, op2, source);
break;
case MIDI_COMMAND_POLYPHONIC_AFTERTOUCH: // Not supported by GM
polyAftertouch(channel, op1, op2, source);
break;
case MIDI_COMMAND_CONTROL_CHANGE:
controlChange(channel, op1, op2, source);
break;
case MIDI_COMMAND_PROGRAM_CHANGE:
programChange(channel, op1, source);
break;
case MIDI_COMMAND_CHANNEL_AFTERTOUCH:
channelAftertouch(channel, op1, source);
break;
case MIDI_COMMAND_PITCH_BEND:
pitchBend(channel, op1, op2, source);
break;
case MIDI_COMMAND_SYSTEM:
// The only supported system event is SysEx and that should be sent
// using the sysEx functions.
warning("MidiDriver_ADLIB_Multisource: send received system event (not processed): %x", b);
break;
default:
warning("MidiDriver_ADLIB_Multisource: Received unknown event %02x", command);
break;
}
}
void MidiDriver_ADLIB_Multisource::noteOff(uint8 channel, uint8 note, uint8 velocity, uint8 source) {
_activeNotesMutex.lock();
if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
if (!_rhythmModeIgnoreNoteOffs) {
// Find the OPL rhythm instrument playing this note.
for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
if (_activeRhythmNotes[i].noteActive && _activeRhythmNotes[i].source == source &&
_activeRhythmNotes[i].note == note) {
writeKeyOff(0, static_cast<OplInstrumentRhythmType>(i + 1));
break;
}
}
}
} else {
// Find the OPL channel playing this note.
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].source == source &&
_activeNotes[oplChannel].channel == channel && _activeNotes[oplChannel].note == note) {
if (_controlData[source][channel].sustain) {
// Sustain controller is on. Sustain the note instead of
// ending it.
_activeNotes[oplChannel].noteSustained = true;
} else {
writeKeyOff(oplChannel);
}
}
}
}
_activeNotesMutex.unlock();
}
void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 velocity, uint8 source) {
if (velocity == 0) {
// Note on with velocity 0 is a note off.
noteOff(channel, note, velocity, source);
return;
}
InstrumentInfo instrument = determineInstrument(channel, source, note);
// If rhythm mode is on and the note is on the rhythm channel, this note
// will be played using the OPL rhythm register.
bool rhythmNote = _rhythmMode && channel == MIDI_RHYTHM_CHANNEL;
if (!instrument.instrumentDef || instrument.instrumentDef->isEmpty() ||
(rhythmNote && instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED)) {
// Instrument definition contains no data or it is not suitable for
// rhythm mode, so the note cannot be played.
return;
}
_activeNotesMutex.lock();
// Determine the OPL channel to use and the active note data to update.
uint8 oplChannel = 0xFF;
ActiveNote *activeNote = nullptr;
if (rhythmNote) {
activeNote = &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1];
} else {
// Allocate a melodic OPL channel.
oplChannel = allocateOplChannel(channel, source, instrument.instrumentId);
if (oplChannel != 0xFF)
activeNote = &_activeNotes[oplChannel];
}
if (activeNote != nullptr) {
if (activeNote->noteActive) {
// Turn off the note currently playing on this OPL channel or
// rhythm instrument.
writeKeyOff(oplChannel, instrument.instrumentDef->rhythmType);
}
// Update the active note data.
activeNote->noteActive = true;
activeNote->noteSustained = false;
activeNote->note = note;
activeNote->velocity = velocity;
activeNote->channel = channel;
activeNote->source = source;
activeNote->oplNote = instrument.oplNote;
// Increase the note counter when playing a new note.
activeNote->noteCounterValue = _noteCounter++;
activeNote->instrumentId = instrument.instrumentId;
activeNote->instrumentDef = instrument.instrumentDef;
// Write out the instrument definition, volume and panning.
writeInstrument(oplChannel, instrument);
// Calculate and write frequency and block and write key on bit.
writeFrequency(oplChannel, instrument.instrumentDef->rhythmType);
if (rhythmNote)
// Update the rhythm register.
writeRhythm();
}
_activeNotesMutex.unlock();
}
void MidiDriver_ADLIB_Multisource::polyAftertouch(uint8 channel, uint8 note, uint8 pressure, uint8 source) {
// Because this event is not required by General MIDI and not implemented
// in the Win95 SB16 driver, there is no default implementation.
}
void MidiDriver_ADLIB_Multisource::controlChange(uint8 channel, uint8 controller, uint8 value, uint8 source) {
// Call the function for handling each controller.
switch (controller) {
case MIDI_CONTROLLER_MODULATION:
modulation(channel, value, source);
break;
case MIDI_CONTROLLER_DATA_ENTRY_MSB:
dataEntry(channel, value, 0xFF, source);
break;
case MIDI_CONTROLLER_VOLUME:
volume(channel, value, source);
break;
case MIDI_CONTROLLER_PANNING:
panning(channel, value, source);
break;
case MIDI_CONTROLLER_EXPRESSION:
expression(channel, value, source);
break;
case MIDI_CONTROLLER_DATA_ENTRY_LSB:
dataEntry(channel, 0xFF, value, source);
break;
case MIDI_CONTROLLER_SUSTAIN:
sustain(channel, value, source);
break;
case MIDI_CONTROLLER_RPN_LSB:
registeredParameterNumber(channel, 0xFF, value, source);
break;
case MIDI_CONTROLLER_RPN_MSB:
registeredParameterNumber(channel, value, 0xFF, source);
break;
case MIDI_CONTROLLER_ALL_SOUND_OFF:
allSoundOff(channel, source);
break;
case MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
resetAllControllers(channel, source);
break;
case MIDI_CONTROLLER_ALL_NOTES_OFF:
case MIDI_CONTROLLER_OMNI_OFF:
case MIDI_CONTROLLER_OMNI_ON:
case MIDI_CONTROLLER_MONO_ON:
case MIDI_CONTROLLER_POLY_ON:
// The omni/mono/poly events also act as an all notes off.
allNotesOff(channel, source);
break;
default:
//debug("MidiDriver_ADLIB_Multisource::controlChange - Unsupported controller %X", controller);
break;
}
}
void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, uint8 source) {
// Just set the MIDI program value; this event does not affect active notes.
_controlData[source][channel].program = program;
}
void MidiDriver_ADLIB_Multisource::channelAftertouch(uint8 channel, uint8 pressure, uint8 source) {
// Even though this event is required by General MIDI, it is not implemented
// in the Win95 SB16 driver, so there is no default implementation.
}
void MidiDriver_ADLIB_Multisource::pitchBend(uint8 channel, uint8 pitchBendLsb, uint8 pitchBendMsb, uint8 source) {
_controlData[source][channel].pitchBend = ((uint16)pitchBendMsb) << 7 | pitchBendLsb;
// Recalculate and write the frequencies of the active notes on this MIDI
// channel to let the new pitch bend value take effect.
recalculateFrequencies(channel, source);
}
void MidiDriver_ADLIB_Multisource::sysEx(const byte *msg, uint16 length) {
if (length >= 4 && msg[0] == 0x7E && msg[2] == 0x09 && msg[3] == 0x01) {
// F0 7E <device ID> 09 01 F7
// General MIDI System On
// Reset the MIDI context and the OPL chip.
stopAllNotes(true);
for (int i = 0; i < MAXIMUM_SOURCES; i++) {
for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) {
_controlData[i][j].init();
}
}
setRhythmMode(false);
for (int i = 0; i < _numMelodicChannels; i++) {
_activeNotes[_melodicChannels[i]].init();
}
memset(_channelAllocations, 0xFF, sizeof(_channelAllocations));
_noteCounter = 1;
initOpl();
} else {
// Ignore other SysEx messages.
warning("MidiDriver_ADLIB_Multisource::sysEx - Unrecognized SysEx");
}
}
void MidiDriver_ADLIB_Multisource::metaEvent(int8 source, byte type, byte *data, uint16 length) {
if (type == MIDI_META_END_OF_TRACK && source >= 0)
// Stop hanging notes and release resources used by this source.
deinitSource(source);
}
void MidiDriver_ADLIB_Multisource::deinitSource(uint8 source) {
// Turn off sustained notes.
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
sustain(i, 0, source);
}
// Stop fades and turn off non-sustained notes.
MidiDriver_Multisource::deinitSource(source);
_allocationMutex.lock();
// Deallocate channels
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
_channelAllocations[source][i] = 0xFF;
}
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].channelAllocated && _activeNotes[oplChannel].source == source) {
_activeNotes[oplChannel].channelAllocated = false;
}
}
_allocationMutex.unlock();
applyControllerDefaults(source);
}
void MidiDriver_ADLIB_Multisource::applyControllerDefaults(uint8 source) {
if (source == 0xFF) {
// Apply controller defaults for all sources.
for (int i = 0; i < MAXIMUM_SOURCES; i++) {
applyControllerDefaults(i);
}
} else {
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
if (_controllerDefaults.program[i] >= 0) {
_controlData[source][i].program = _controllerDefaults.program[i];
}
if (_controllerDefaults.channelPressure >= 0) {
_controlData[source][i].channelPressure = _controllerDefaults.channelPressure;
}
if (_controllerDefaults.pitchBend >= 0) {
_controlData[source][i].pitchBend = _controllerDefaults.pitchBend;
}
if (_controllerDefaults.modulation >= 0) {
_controlData[source][i].modulation = _controllerDefaults.modulation;
}
if (_controllerDefaults.volume >= 0) {
_controlData[source][i].volume = _controllerDefaults.volume;
}
if (_controllerDefaults.panning >= 0) {
_controlData[source][i].panning = _controllerDefaults.panning;
}
if (_controllerDefaults.expression >= 0) {
_controlData[source][i].expression = _controllerDefaults.expression;
}
if (_controllerDefaults.rpn >= 0) {
_controlData[source][i].rpn = _controllerDefaults.rpn;
}
if (_controllerDefaults.pitchBendSensitivity >= 0) {
_controlData[source][i].pitchBendSensitivity = _controllerDefaults.pitchBendSensitivity;
_controlData[source][i].pitchBendSensitivityCents = 0;
}
// Controller defaults not supported by this driver:
// instrument bank, drumkit.
// Sustain is turned off by deinitSource.
}
}
}
void MidiDriver_ADLIB_Multisource::modulation(uint8 channel, uint8 modulation, uint8 source) {
// Even though this controller is required by General MIDI, it is not
// implemented in the Win95 SB16 driver, so there is no default
// implementation.
}
void MidiDriver_ADLIB_Multisource::dataEntry(uint8 channel, uint8 dataMsb, uint8 dataLsb, uint8 source) {
// Set the data on the currently active RPN.
switch (_controlData[source][channel].rpn) {
case MIDI_RPN_PITCH_BEND_SENSITIVITY:
// MSB = semitones, LSB = cents.
if (dataMsb != 0xFF) {
_controlData[source][channel].pitchBendSensitivity = dataMsb;
}
if (dataLsb != 0xFF) {
_controlData[source][channel].pitchBendSensitivityCents = dataLsb;
}
// Apply the new pitch bend sensitivity to any active notes.
recalculateFrequencies(channel, source);
break;
case MIDI_RPN_MASTER_TUNING_FINE:
// MSB and LSB are combined to a fraction of a semitone.
if (dataMsb != 0xFF) {
_controlData[source][channel].masterTuningFine &= 0x00FF;
_controlData[source][channel].masterTuningFine |= dataMsb << 8;
}
if (dataLsb != 0xFF) {
_controlData[source][channel].masterTuningFine &= 0xFF00;
_controlData[source][channel].masterTuningFine |= dataLsb;
}
// Apply the new master tuning to any active notes.
recalculateFrequencies(channel, source);
break;
case MIDI_RPN_MASTER_TUNING_COARSE:
// MSB = semitones, LSB is ignored.
if (dataMsb != 0xFF) {
_controlData[source][channel].masterTuningCoarse = dataMsb;
}
// Apply the new master tuning to any active notes.
recalculateFrequencies(channel, source);
break;
default:
// Ignore data entry if null or an unknown RPN is active.
break;
}
}
void MidiDriver_ADLIB_Multisource::volume(uint8 channel, uint8 volume, uint8 source) {
if (_controlData[source][channel].volume == volume)
return;
_controlData[source][channel].volume = volume;
// Apply the new channel volume to any active notes.
recalculateVolumes(channel, source);
}
void MidiDriver_ADLIB_Multisource::panning(uint8 channel, uint8 panning, uint8 source) {
if (_controlData[source][channel].panning == panning)
return;
_controlData[source][channel].panning = panning;
_activeNotesMutex.lock();
// Apply the new channel panning to any active notes.
if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
if (_activeRhythmNotes[i].noteActive && _activeRhythmNotes[i].source == source) {
writePanning(0xFF, static_cast<OplInstrumentRhythmType>(i + 1));
}
}
} else {
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].channel == channel &&
_activeNotes[oplChannel].source == source) {
writePanning(oplChannel);
}
}
}
_activeNotesMutex.unlock();
}
void MidiDriver_ADLIB_Multisource::expression(uint8 channel, uint8 expression, uint8 source) {
if (_controlData[source][channel].expression == expression)
return;
_controlData[source][channel].expression = expression;
// Apply the new expression value to any active notes.
recalculateVolumes(channel, source);
}
void MidiDriver_ADLIB_Multisource::sustain(uint8 channel, uint8 sustain, uint8 source) {
if (sustain >= 0x40) {
// Turn on sustain.
_controlData[source][channel].sustain = true;
} else if (_controlData[source][channel].sustain) {
// Sustain is currently on. Turn it off.
_controlData[source][channel].sustain = false;
_activeNotesMutex.lock();
// Turn off any sustained notes on this channel.
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].noteSustained &&
_activeNotes[oplChannel].channel == channel && _activeNotes[oplChannel].source == source) {
writeKeyOff(oplChannel);
}
}
_activeNotesMutex.unlock();
}
}
void MidiDriver_ADLIB_Multisource::registeredParameterNumber(uint8 channel, uint8 rpnMsb, uint8 rpnLsb, uint8 source) {
// Set the currently active RPN. MSB and LSB combined form the RPN number.
if (rpnMsb != 0xFF) {
_controlData[source][channel].rpn &= 0x00FF;
_controlData[source][channel].rpn |= rpnMsb << 8;
}
if (rpnLsb != 0xFF) {
_controlData[source][channel].rpn &= 0xFF00;
_controlData[source][channel].rpn |= rpnLsb;
}
}
void MidiDriver_ADLIB_Multisource::allSoundOff(uint8 channel, uint8 source) {
// It is not possible to immediately terminate the sound on an OPL chip
// (skipping the "release" of the notes), so just turn the notes off.
stopAllNotes(source, channel);
}
void MidiDriver_ADLIB_Multisource::resetAllControllers(uint8 channel, uint8 source) {
modulation(channel, 0, source);
expression(channel, MIDI_EXPRESSION_DEFAULT, source);
sustain(channel, 0, source);
registeredParameterNumber(channel, MIDI_RPN_NULL >> 8, MIDI_RPN_NULL & 0xFF, source);
pitchBend(channel, MIDI_PITCH_BEND_DEFAULT & 0x7F, MIDI_PITCH_BEND_DEFAULT >> 7, source);
channelAftertouch(channel, 0, source);
// TODO Polyphonic aftertouch should also be reset; not implemented because
// polyphonic aftertouch is not implemented.
}
void MidiDriver_ADLIB_Multisource::allNotesOff(uint8 channel, uint8 source) {
_activeNotesMutex.lock();
// Execute a note off for all active notes on this MIDI channel. This will
// turn the notes off if sustain is off and sustain the notes if it is on.
if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
if (_activeRhythmNotes[i].noteActive && _activeRhythmNotes[i].source == source) {
noteOff(channel, _activeRhythmNotes[i].note, 0, source);
}
}
} else {
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].noteActive && !_activeNotes[oplChannel].noteSustained &&
_activeNotes[oplChannel].source == source && _activeNotes[oplChannel].channel == channel) {
noteOff(channel, _activeNotes[oplChannel].note, 0, source);
}
}
}
_activeNotesMutex.unlock();
}
void MidiDriver_ADLIB_Multisource::stopAllNotes(bool stopSustainedNotes) {
// Just write the key off bit on all OPL channels. No special handling is
// needed to make sure sustained notes are turned off.
for (int i = 0; i < _numMelodicChannels; i++) {
// Force the register write to prevent accidental hanging notes.
writeKeyOff(_melodicChannels[i], RHYTHM_TYPE_UNDEFINED, true);
}
if (_rhythmMode) {
for (int i = 0; i < 5; i++) {
_activeRhythmNotes[i].noteActive = false;
}
writeRhythm(true);
}
}
void MidiDriver_ADLIB_Multisource::stopAllNotes(uint8 source, uint8 channel) {
_activeNotesMutex.lock();
// Write the key off bit for all active notes on this MIDI channel and
// source.
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].noteActive && (source == 0xFF || _activeNotes[oplChannel].source == source) &&
(channel == 0xFF || _activeNotes[oplChannel].channel == channel)) {
writeKeyOff(oplChannel);
}
}
if (_rhythmMode && !_rhythmModeIgnoreNoteOffs && (channel == 0xFF || channel == MIDI_RHYTHM_CHANNEL)) {
bool rhythmChanged = false;
for (int i = 0; i < 5; i++) {
if (_activeRhythmNotes[i].noteActive && (source == 0xFF || _activeRhythmNotes[i].source == source)) {
_activeRhythmNotes[i].noteActive = false;
rhythmChanged = true;
}
}
if (rhythmChanged)
writeRhythm();
}
_activeNotesMutex.unlock();
}
void MidiDriver_ADLIB_Multisource::applySourceVolume(uint8 source) {
// Recalculate the volume of the active notes on all MIDI channels of this
// source.
recalculateVolumes(0xFF, source);
}
void MidiDriver_ADLIB_Multisource::initOpl() {
// Clear test flags and enable waveform select for OPL2 chips.
writeRegister(OPL_REGISTER_TEST, _oplType == OPL::Config::kOpl3 ? 0 : 0x20, true);
if (_oplType != OPL::Config::kOpl2) {
writeRegister(OPL_REGISTER_TEST | OPL_REGISTER_SET_2_OFFSET, _oplType == OPL::Config::kOpl3 ? 0 : 0x20, true);
}
// Clear, stop and mask the timers and reset the interrupt.
writeRegister(OPL_REGISTER_TIMER1, 0, true);
writeRegister(OPL_REGISTER_TIMER2, 0, true);
writeRegister(OPL_REGISTER_TIMERCONTROL, 0x60, true);
writeRegister(OPL_REGISTER_TIMERCONTROL, 0x80, true);
if (_oplType == OPL::Config::kDualOpl2) {
writeRegister(OPL_REGISTER_TIMER1 | OPL_REGISTER_SET_2_OFFSET, 0, true);
writeRegister(OPL_REGISTER_TIMER2 | OPL_REGISTER_SET_2_OFFSET, 0, true);
writeRegister(OPL_REGISTER_TIMERCONTROL | OPL_REGISTER_SET_2_OFFSET, 0x60, true);
writeRegister(OPL_REGISTER_TIMERCONTROL | OPL_REGISTER_SET_2_OFFSET, 0x80, true);
}
if (_oplType == OPL::Config::kOpl3) {
// Turn off 4 operator mode for all channels.
writeRegister(OPL3_REGISTER_CONNECTIONSELECT, 0, true);
// Enable "new" OPL3 functionality.
writeRegister(OPL3_REGISTER_NEW, 1, true);
}
// Set note select mode and disable CSM mode for OPL2 chips.
writeRegister(OPL_REGISTER_NOTESELECT_CSM, _noteSelect << 6, true);
if (_oplType == OPL::Config::kDualOpl2) {
writeRegister(OPL_REGISTER_NOTESELECT_CSM | OPL_REGISTER_SET_2_OFFSET, _noteSelect << 6, true);
}
// Set operator registers to default values.
for (int i = 0; i < 5; i++) {
uint8 baseReg = 0;
uint8 value = 0;
switch (i) {
case 0:
baseReg = OPL_REGISTER_BASE_FREQMULT_MISC;
break;
case 1:
baseReg = OPL_REGISTER_BASE_LEVEL;
// Set volume to the default MIDI channel volume.
// Convert from MIDI to OPL register value.
value = 0x3F - (_defaultChannelVolume >> 1);
break;
case 2:
baseReg = OPL_REGISTER_BASE_DECAY_ATTACK;
break;
case 3:
baseReg = OPL_REGISTER_BASE_RELEASE_SUSTAIN;
break;
case 4:
baseReg = OPL_REGISTER_BASE_WAVEFORMSELECT;
break;
}
for (int j = 0; j < (_oplType == OPL::Config::kOpl2 ? OPL2_NUM_CHANNELS : OPL3_NUM_CHANNELS); j++) {
writeRegister(baseReg + determineOperatorRegisterOffset(j, 0), value, true);
writeRegister(baseReg + determineOperatorRegisterOffset(j, 1), value, true);
}
}
// Set channel registers to default values.
for (int i = 0; i < 3; i++) {
uint8 baseReg = 0;
uint8 value = 0;
switch (i) {
case 0:
baseReg = OPL_REGISTER_BASE_FNUMLOW;
break;
case 1:
baseReg = OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON;
break;
case 2:
baseReg = OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING;
if (_oplType == OPL::Config::kOpl3) {
// Set default panning to center.
value = OPL_PANNING_CENTER;
}
break;
}
for (int j = 0; j < (_oplType == OPL::Config::kOpl2 ? OPL2_NUM_CHANNELS : OPL3_NUM_CHANNELS); j++) {
writeRegister(baseReg + determineChannelRegisterOffset(j), value, true);
}
}
// Set rhythm mode, modulation and vibrato depth.
writeRhythm(true);
}
void MidiDriver_ADLIB_Multisource::recalculateFrequencies(uint8 channel, uint8 source) {
_activeNotesMutex.lock();
// Calculate and write the frequency of all active notes on this MIDI
// channel and source.
if (_rhythmMode && channel == MIDI_RHYTHM_CHANNEL) {
// Always rewrite bass drum frequency if it is active.
if (_activeRhythmNotes[RHYTHM_TYPE_BASS_DRUM - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_BASS_DRUM - 1].source == source) {
writeFrequency(0xFF, RHYTHM_TYPE_BASS_DRUM);
}
// Snare drum and hi-hat share the same frequency setting. If both are
// active, use the most recently played instrument.
OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED;
bool snareActive = _activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].source == source;
bool hiHatActive = _activeRhythmNotes[RHYTHM_TYPE_HI_HAT - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_HI_HAT - 1].source == source;
if (snareActive && hiHatActive) {
rhythmType = (_activeRhythmNotes[RHYTHM_TYPE_SNARE_DRUM - 1].noteCounterValue >=
_activeRhythmNotes[RHYTHM_TYPE_HI_HAT - 1].noteCounterValue ? RHYTHM_TYPE_SNARE_DRUM : RHYTHM_TYPE_HI_HAT);
} else if (snareActive) {
rhythmType = RHYTHM_TYPE_SNARE_DRUM;
} else if (hiHatActive) {
rhythmType = RHYTHM_TYPE_HI_HAT;
}
if (rhythmType != RHYTHM_TYPE_UNDEFINED)
writeFrequency(0xFF, rhythmType);
// Tom tom and cymbal share the same frequency setting. If both are
// active, use the most recently played instrument.
rhythmType = RHYTHM_TYPE_UNDEFINED;
bool tomTomActive = _activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].source == source;
bool cymbalActive = _activeRhythmNotes[RHYTHM_TYPE_CYMBAL - 1].noteActive && _activeRhythmNotes[RHYTHM_TYPE_CYMBAL - 1].source == source;
if (tomTomActive && cymbalActive) {
rhythmType = (_activeRhythmNotes[RHYTHM_TYPE_TOM_TOM - 1].noteCounterValue >=
_activeRhythmNotes[RHYTHM_TYPE_CYMBAL - 1].noteCounterValue ? RHYTHM_TYPE_TOM_TOM : RHYTHM_TYPE_CYMBAL);
} else if (tomTomActive) {
rhythmType = RHYTHM_TYPE_TOM_TOM;
} else if (cymbalActive) {
rhythmType = RHYTHM_TYPE_CYMBAL;
}
if (rhythmType != RHYTHM_TYPE_UNDEFINED)
writeFrequency(0xFF, rhythmType);
} else {
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].channel == channel &&
_activeNotes[oplChannel].source == source) {
writeFrequency(oplChannel);
}
}
}
_activeNotesMutex.unlock();
}
void MidiDriver_ADLIB_Multisource::recalculateVolumes(uint8 channel, uint8 source) {
_activeNotesMutex.lock();
// Calculate and write the volume of all operators of all active notes on
// this MIDI channel and source.
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].noteActive &&
(channel == 0xFF || _activeNotes[oplChannel].channel == channel) &&
(source == 0xFF || _activeNotes[oplChannel].source == source)) {
for (int j = 0; j < _activeNotes[oplChannel].instrumentDef->getNumberOfOperators(); j++) {
writeVolume(oplChannel, j);
}
}
}
if (_rhythmMode && (channel == 0xFF || channel == MIDI_RHYTHM_CHANNEL)) {
for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
if (_activeRhythmNotes[i].noteActive && (source == 0xFF || _activeRhythmNotes[i].source == source)) {
for (int j = 0; j < _activeRhythmNotes[i].instrumentDef->getNumberOfOperators(); j++) {
writeVolume(0xFF, j, static_cast<OplInstrumentRhythmType>(i + 1));
}
}
}
}
_activeNotesMutex.unlock();
}
MidiDriver_ADLIB_Multisource::InstrumentInfo MidiDriver_ADLIB_Multisource::determineInstrument(uint8 channel, uint8 source, uint8 note) {
InstrumentInfo instrument = { 0, nullptr, 0 };
if (channel == MIDI_RHYTHM_CHANNEL) {
// On the rhythm channel, the note played indicates which instrument
// should be used.
if (note < _rhythmBankFirstNote || note > _rhythmBankLastNote)
// No rhythm instrument assigned to this note number.
return instrument;
// Set the high bit for rhythm instrument IDs.
instrument.instrumentId = 0x80 | note;
instrument.instrumentDef = &_rhythmBank[note - _rhythmBankFirstNote];
// Get the note to play from the instrument definition.
instrument.oplNote = instrument.instrumentDef->rhythmNote;
} else {
// On non-rhythm channels, use the active instrument (program) on the
// MIDI channel.
byte program = _controlData[source][channel].program;
if (_instrumentRemapping)
// Apply instrument remapping (if specified).
program = _instrumentRemapping[program];
instrument.instrumentId = program;
instrument.instrumentDef = &_instrumentBank[instrument.instrumentId];
instrument.oplNote = note;
}
return instrument;
}
uint8 MidiDriver_ADLIB_Multisource::allocateOplChannel(uint8 channel, uint8 source, uint8 instrumentId) {
uint8 allocatedChannel = 0xFF;
if (_allocationMode == ALLOCATION_MODE_DYNAMIC) {
// In dynamic channel allocation mode, each note is allocated a new
// OPL channel. The following criteria are used, in this order:
// - The channel with the lowest number that has not yet been used to
// play a note (note counter value is 0).
// - The channel with the lowest note counter value that is not
// currently playing a note.
// - The channel with the lowest note counter value that is playing a
// note using the same instrument.
// - The channel with the lowest note counter value (i.e. playing the
// oldest note).
// This will always return a channel; if a note is currently playing,
// it will be aborted.
uint8 unusedChannel = 0xFF, inactiveChannel = 0xFF, instrumentChannel = 0xFF, lowestCounterChannel = 0xFF;
uint32 inactiveNoteCounter = 0xFFFF, instrumentNoteCounter = 0xFFFF, lowestNoteCounter = 0xFFFF;
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].channelAllocated)
// Channel has been statically allocated. Try the next channel.
continue;
if (_activeNotes[oplChannel].noteCounterValue == 0) {
// This channel is unused. No need to look any further.
unusedChannel = oplChannel;
break;
}
if (!_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].noteCounterValue < inactiveNoteCounter) {
// A channel not playing a note with a lower note counter value
// has been found.
inactiveNoteCounter = _activeNotes[oplChannel].noteCounterValue;
inactiveChannel = oplChannel;
continue;
}
if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].instrumentId == instrumentId &&
_activeNotes[oplChannel].noteCounterValue < instrumentNoteCounter) {
// A channel playing a note using the same instrument with a
// lower note counter value has been found.
instrumentNoteCounter = _activeNotes[oplChannel].noteCounterValue;
instrumentChannel = oplChannel;
}
if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].noteCounterValue < lowestNoteCounter) {
// A channel playing a note with a lower note counter value has
// been found.
lowestNoteCounter = _activeNotes[oplChannel].noteCounterValue;
lowestCounterChannel = oplChannel;
}
}
if (unusedChannel != 0xFF)
// An unused channel has been found. Use this.
allocatedChannel = unusedChannel;
else if (inactiveChannel != 0xFF)
// An inactive channel has been found. Use this.
allocatedChannel = inactiveChannel;
else if (instrumentChannel != 0xFF)
// An active channel using the same instrument has been found.
// Use this.
allocatedChannel = instrumentChannel;
else
// Just use the channel playing the oldest note.
allocatedChannel = lowestCounterChannel;
} else {
// In static allocation mode, each MIDI channel of each source is
// allocated a fixed OPL channel to use. All notes on that MIDI channel
// are played using the allocated OPL channel. If a new MIDI channel
// needs an OPL channel and all OPL channels have already been
// allocated, allocation will fail.
allocatedChannel = 0xFF;
_allocationMutex.lock();
if (_channelAllocations[source][channel] != 0xFF) {
// An OPL channel has already been allocated to this MIDI channel
// for this source. Use the previously allocated channel.
allocatedChannel = _channelAllocations[source][channel];
} else {
// No OPL channel has been allocated yet. Find a free OPL channel.
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (!_activeNotes[oplChannel].channelAllocated) {
// Found a free channel. Allocate this.
_activeNotes[oplChannel].channelAllocated = true;
_activeNotes[oplChannel].source = source;
_activeNotes[oplChannel].channel = channel;
_channelAllocations[source][channel] = oplChannel;
allocatedChannel = oplChannel;
break;
}
}
// If no free channel could be found, allocatedChannel will be 0xFF.
}
_allocationMutex.unlock();
}
return allocatedChannel;
}
void MidiDriver_ADLIB_Multisource::determineMelodicChannels() {
if (_oplType == OPL::Config::kOpl2 || _oplType == OPL::Config::kDualOpl2) {
_numMelodicChannels = OPL2_NUM_CHANNELS;
if (_rhythmMode) {
// Rhythm mode uses 3 OPL channels for rhythm instruments.
_numMelodicChannels -= 3;
_melodicChannels = MELODIC_CHANNELS_OPL2_RHYTHM;
} else {
// Use all available OPL channels as melodic channels.
_melodicChannels = MELODIC_CHANNELS_OPL2;
}
} else {
_numMelodicChannels = OPL3_NUM_CHANNELS;
if (_rhythmMode) {
_numMelodicChannels -= 3;
_melodicChannels = MELODIC_CHANNELS_OPL3_RHYTHM;
} else {
_melodicChannels = MELODIC_CHANNELS_OPL3;
}
}
}
uint16 MidiDriver_ADLIB_Multisource::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
// Split note into octave and octave note.
uint8 octaveNote = note % 12;
uint8 octave = note / 12;
// Calculate OPL octave (block) and frequency (F-num).
uint8 block;
uint32 oplFrequency;
if (_accuracyMode == ACCURACY_MODE_SB16_WIN95) {
// Frequency calculation using the algorithm of the Win95 SB16 driver.
// Look up the octave note OPL frequency. These values assume octave 5.
oplFrequency = OPL_NOTE_FREQUENCIES[octaveNote];
// Correct for octaves other than 5 by doubling or halving the OPL
// frequency for each octave higher or lower, respectively.
if (octave > 5) {
oplFrequency <<= (octave - 5);
} else {
oplFrequency >>= (5 - octave);
}
// The resulting value is likely larger than the 10 bit length of the
// F-num in the OPL registers. This is correct later by increasing the
// block.
block = 1;
} else {
// Frequency calculation using a more accurate algorithm.
// Calculate the note frequency in Hertz by relating it to a known
// frequency (in this case A4 (0x45) = 440 Hz). Formula is
// freq * 2 ^ (semitones / 12).
float noteFrequency = 440 * (pow(2, (note - 0x45) / 12.0f));
// Convert the frequency in Hz to the format used by the OPL registers.
// Note that the resulting value is double the actual frequency because
// of the use of block 0 (which halves the frequency). This allows for
// slightly higher precision in the pitch bend calculation.
oplFrequency = round(noteFrequency * _oplFrequencyConversionFactor);
block = 0;
}
// Calculate and apply pitch bend and tuning.
oplFrequency += calculatePitchBend(channel, source, oplFrequency);
// Shift the frequency down to the 10 bits used by the OPL registers.
// Increase the block to compensate.
while (oplFrequency > 0x3FF) {
oplFrequency >>= 1;
block++;
}
// Maximum supported block value is 7, so clip higher values. The highest
// MIDI notes exceed the maximum OPL frequency, so these will be transposed
// down 1 or 2 octaves.
block = MIN(block, (uint8)7);
// Combine the block and frequency in the OPL Ax and Bx register format.
return oplFrequency | (block << 10);
}
int32 MidiDriver_ADLIB_Multisource::calculatePitchBend(uint8 channel, uint8 source, uint16 oplFrequency) {
int32 pitchBend;
if (_accuracyMode == ACCURACY_MODE_SB16_WIN95) {
// Pitch bend calculation using the algorithm of the Win95 SB16 driver.
// Convert the 14 bit MIDI pitch bend value to a 16 bit signed value.
// WORKAROUND The conversion to signed in the Win95 SB16 driver is
// slightly inaccurate and causes minimum pitch bend to underflow to
// maximum pitch bend. This is corrected here by clipping the result to
// the int16 minimum value.
pitchBend = MAX(-0x8000, (_controlData[source][channel].pitchBend << 2) - 0x8001);
// Scale pitch bend by 0x1F (up) or 0x1B (down), which is a fixed
// distance of 2 semitones up or down (pitch bend sensitivity is not
// supported by this algorithm).
pitchBend *= (pitchBend > 0 ? 0x1F : 0x1B);
pitchBend >>= 8;
// Scale by the OPL note frequency.
pitchBend *= oplFrequency;
pitchBend >>= 0xF;
} else {
// Pitch bend calculation using a more accurate algorithm.
// Calculate the pitch bend in cents.
int16 signedPitchBend = _controlData[source][channel].pitchBend - 0x2000;
uint16 pitchBendSensitivityCents = (_controlData[source][channel].pitchBendSensitivity * 100) +
_controlData[source][channel].pitchBendSensitivityCents;
// Pitch bend upwards has 1 less resolution than downwards
// (0x2001-0x3FFF vs 0x0000-0x1FFF).
float pitchBendCents = signedPitchBend * pitchBendSensitivityCents /
(signedPitchBend > 0 ? 8191.0f : 8192.0f);
// Calculate the tuning in cents.
float tuningCents = ((_controlData[source][channel].masterTuningCoarse - 0x40) * 100) +
((_controlData[source][channel].masterTuningFine - 0x2000) * 100 / 8192.0f);
// Calculate pitch bend (formula is freq * 2 ^ (cents / 1200)).
// Note that if unrealistically large values for pitch bend sensitivity
// and/or tuning are used, the result could overflow int32. Since this is
// far into the ultrasonic frequencies, this should not occur in practice.
pitchBend = round(oplFrequency * pow(2, (pitchBendCents + tuningCents) / 1200.0f) - oplFrequency);
}
return pitchBend;
}
uint8 MidiDriver_ADLIB_Multisource::calculateVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
// Get the volume (level) for this operator from the instrument definition.
uint8 operatorDefVolume = instrumentDef.getOperatorDefinition(operatorNum).level & 0x3F;
// Determine if volume settings should be applied to this operator. Carrier
// operators in FM synthesis and all operators in additive synthesis need
// to have volume settings applied; modulator operators just use the
// instrument definition volume.
bool applyVolume = false;
if (instrumentDef.rhythmType != RHYTHM_TYPE_UNDEFINED) {
applyVolume = (instrumentDef.rhythmType != RHYTHM_TYPE_BASS_DRUM || operatorNum == 1);
} else if (instrumentDef.fourOperator) {
// 4 operator instruments have 4 different operator connections.
uint8 connection = (instrumentDef.connectionFeedback0 & 0x01) | ((instrumentDef.connectionFeedback1 & 0x01) << 1);
switch (connection) {
case 0x00:
// 4FM
// Operator 3 is a carrier.
applyVolume = (operatorNum == 3);
break;
case 0x01:
// 1ADD+3FM
// Operator 0 is additive and operator 3 is a carrier.
applyVolume = (operatorNum == 0 || operatorNum == 3);
break;
case 0x10:
// 2FM+2FM
// Operators 1 and 3 are carriers.
applyVolume = (operatorNum == 1 || operatorNum == 3);
break;
case 0x11:
// 1ADD+2FM+1ADD
// Operators 0 and 3 are additive and operator 2 is a carrier.
applyVolume = (operatorNum == 0 || operatorNum == 2 || operatorNum == 3);
break;
default:
// Should not happen.
applyVolume = false;
}
} else {
// 2 operator instruments have 2 different operator connections:
// additive (0x01) or FM (0x00) synthesis. Carrier operators in FM
// synthesis and all operators in additive synthesis need to have
// volume settings applied; modulator operators just use the instrument
// definition volume. In FM synthesis connection, operator 1 is a
// carrier.
applyVolume = (instrumentDef.connectionFeedback0 & 0x01) == 0x01 || operatorNum == 1;
}
if (!applyVolume)
// No need to apply volume settings; just use the instrument definition
// operator volume.
return operatorDefVolume;
// Calculate the volume based on note velocity, channel volume and
// expression.
uint8 unscaledVolume = calculateUnscaledVolume(channel, source, velocity, instrumentDef, operatorNum);
uint8 invertedVolume = 0x3F - unscaledVolume;
// Scale by source volume.
invertedVolume = (invertedVolume * _sources[source].volume) / _sources[source].neutralVolume;
if (_userVolumeScaling) {
if (_userMute) {
invertedVolume = 0;
} else {
// Scale by user volume.
uint16 userVolume = (_sources[source].type == SOURCE_TYPE_SFX ? _userSfxVolume : _userMusicVolume); // Treat SOURCE_TYPE_UNDEFINED as music
invertedVolume = (invertedVolume * userVolume) >> 8;
}
}
// Source volume scaling might clip volume, so reduce to maximum.
invertedVolume = MIN((uint8)0x3F, invertedVolume);
uint8 scaledVolume = 0x3F - invertedVolume;
return scaledVolume;
}
uint8 MidiDriver_ADLIB_Multisource::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
uint8 unscaledVolume;
// Get the volume (level) for this operator from the instrument definition.
uint8 operatorVolume = instrumentDef.getOperatorDefinition(operatorNum).level & 0x3F;
if (_accuracyMode == ACCURACY_MODE_SB16_WIN95) {
// Volume calculation using the algorithm of the Win95 SB16 driver.
// Shift velocity and channel volume to a 5 bit value and look up the OPL
// volume value.
uint8 velocityVolume = OPL_VOLUME_LOOKUP[velocity >> 2];
uint8 channelVolume = OPL_VOLUME_LOOKUP[_controlData[source][channel].volume >> 2];
// Add velocity and channel OPL volume to get the unscaled volume. The
// operator volume is an additional (negative) volume adjustment to balance
// the instruments.
// Note that large OPL volume values can exceed the 0x3F limit; this is
// handled below. (0x3F means maximum attenuation - no sound.)
unscaledVolume = velocityVolume + channelVolume + operatorVolume;
} else {
// Volume calculation using an algorithm more accurate to the General MIDI
// standard.
// Calculate the volume in dB according to the GM formula:
// 40 log(velocity * volume * expression / 127 ^ 3)
// Note that velocity is not specified in detail in the MIDI standards;
// we use the same volume curve as channel volume and expression.
float volumeDb = 40 * log10((velocity * _controlData[source][channel].volume * _controlData[source][channel].expression) / 2048383.0f);
// Convert to OPL volume (every unit is 0.75 dB attenuation). The
// operator volume is an additional (negative) volume adjustment to balance
// the instruments.
unscaledVolume = volumeDb / -0.75f + operatorVolume;
}
// Clip the volume to the maximum value.
return MIN((uint8)0x3F, unscaledVolume);
}
uint8 MidiDriver_ADLIB_Multisource::calculatePanning(uint8 channel, uint8 source) {
if (_oplType != OPL::Config::kOpl3)
return 0;
// MIDI panning is converted to OPL panning using these values:
// 0x00...L...0x2F 0x30...C...0x50 0x51...R...0x7F
if (_controlData[source][channel].panning <= OPL_MIDI_PANNING_LEFT_LIMIT) {
return OPL_PANNING_LEFT;
} else if (_controlData[source][channel].panning >= OPL_MIDI_PANNING_RIGHT_LIMIT) {
return OPL_PANNING_RIGHT;
} else {
return OPL_PANNING_CENTER;
}
}
void MidiDriver_ADLIB_Multisource::setRhythmMode(bool rhythmMode) {
if (_rhythmMode == rhythmMode)
return;
_allocationMutex.lock();
_activeNotesMutex.lock();
if (!_rhythmMode && rhythmMode) {
// Rhythm mode is turned on.
// Reset the OPL channels that will be used for rhythm mode.
for (int i = 6; i <= 8; i++) {
writeKeyOff(i);
for (int j = 0; j < MAXIMUM_SOURCES; j++) {
_channelAllocations[j][i] = 0xFF;
}
_activeNotes[i].init();
}
// Initialize the rhythm note data.
for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
_activeRhythmNotes[i].init();
}
} else if (_rhythmMode && !rhythmMode) {
// Rhythm mode is turned off.
// Turn off any active rhythm notes.
for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
_activeRhythmNotes[i].noteActive = false;
}
}
_rhythmMode = rhythmMode;
determineMelodicChannels();
writeRhythm();
_activeNotesMutex.unlock();
_allocationMutex.unlock();
}
uint16 MidiDriver_ADLIB_Multisource::determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType, bool fourOperator) {
assert(!fourOperator || oplChannel < 6);
assert(fourOperator || operatorNum < 2);
uint16 offset = 0;
if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
// Look up the offset for rhythm instruments.
offset = OPL_REGISTER_RHYTHM_OFFSETS[rhythmType - 1];
if (rhythmType == RHYTHM_TYPE_BASS_DRUM && operatorNum == 1)
// Bass drum is the only rhythm instrument with 2 operators.
offset += 3;
} else if (fourOperator) {
// 4 operator register offset for each channel and operator:
//
// Channel | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 1 | 2 |
// Operator | 0 | 1 | 2 | 3 |
// Register | 0 | 1 | 2 | 3 | 4 | 5 | 8 | 9 | A | B | C | D |
//
// Channels 3-5 are in the second register set (add 0x100 to the register).
offset += (oplChannel / 3) * OPL_REGISTER_SET_2_OFFSET;
offset += operatorNum / 2 * 8;
offset += (operatorNum % 2) * 3;
offset += oplChannel % 3;
} else {
// 2 operator register offset for each channel and operator:
//
// Channel | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | 3 | 4 | 5 | 6 | 7 | 8 | 6 | 7 | 8 |
// Operator | 0 | 1 | 0 | 1 | 0 | 1 |
// Register | 0 | 1 | 2 | 3 | 4 | 5 | 8 | 9 | A | B | C | D |10 |11 |12 |13 |14 |15 |
//
// Channels 9-17 are in the second register set (add 0x100 to the register).
offset += (oplChannel / 9) * OPL_REGISTER_SET_2_OFFSET;
offset += (oplChannel % 9) / 3 * 8;
offset += (oplChannel % 9) % 3;
offset += operatorNum * 3;
}
return offset;
}
uint16 MidiDriver_ADLIB_Multisource::determineChannelRegisterOffset(uint8 oplChannel, bool fourOperator) {
assert(!fourOperator || oplChannel < 6);
// In 4 operator mode, only the first three channel registers are used in
// each register set.
uint8 numChannelsPerSet = fourOperator ? 3 : 9;
uint16 offset = (oplChannel / numChannelsPerSet) * OPL_REGISTER_SET_2_OFFSET;
return offset + (oplChannel % numChannelsPerSet);
}
void MidiDriver_ADLIB_Multisource::writeInstrument(uint8 oplChannel, InstrumentInfo instrument) {
ActiveNote *activeNote = (instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED ? &_activeNotes[oplChannel] : &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1]);
activeNote->instrumentDef = instrument.instrumentDef;
// Calculate operator volumes and write operator definitions to
// the OPL registers.
for (int i = 0; i < instrument.instrumentDef->getNumberOfOperators(); i++) {
uint16 operatorOffset = determineOperatorRegisterOffset(oplChannel, i, instrument.instrumentDef->rhythmType, instrument.instrumentDef->fourOperator);
const OplInstrumentOperatorDefinition &operatorDef = instrument.instrumentDef->getOperatorDefinition(i);
writeRegister(OPL_REGISTER_BASE_FREQMULT_MISC + operatorOffset, operatorDef.freqMultMisc);
writeVolume(oplChannel, i, instrument.instrumentDef->rhythmType);
writeRegister(OPL_REGISTER_BASE_DECAY_ATTACK + operatorOffset, operatorDef.decayAttack);
writeRegister(OPL_REGISTER_BASE_RELEASE_SUSTAIN + operatorOffset, operatorDef.releaseSustain);
writeRegister(OPL_REGISTER_BASE_WAVEFORMSELECT + operatorOffset, operatorDef.waveformSelect);
}
// Determine and write panning and write feedback and connection.
writePanning(oplChannel, instrument.instrumentDef->rhythmType);
}
void MidiDriver_ADLIB_Multisource::writeKeyOff(uint8 oplChannel, OplInstrumentRhythmType rhythmType, bool forceWrite) {
_activeNotesMutex.lock();
ActiveNote *activeNote = nullptr;
if (rhythmType == RHYTHM_TYPE_UNDEFINED) {
// Melodic instrument.
activeNote = &_activeNotes[oplChannel];
// Rewrite the current Bx register value with the key on bit set to 0.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + determineChannelRegisterOffset(oplChannel),
(activeNote->oplFrequency >> 8) & OPL_MASK_FNUMHIGH_BLOCK, forceWrite);
} else {
// Rhythm instrument.
activeNote = &_activeRhythmNotes[rhythmType - 1];
}
// Update the active note data.
activeNote->noteActive = false;
activeNote->noteSustained = false;
// Register the current note counter value when turning off a note.
activeNote->noteCounterValue = _noteCounter;
if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
// Rhythm instrument. Rewrite the rhythm register.
writeRhythm();
}
_activeNotesMutex.unlock();
}
void MidiDriver_ADLIB_Multisource::writeRhythm(bool forceWrite) {
uint8 value = (_modulationDepth << 7) | (_vibratoDepth << 6) | ((_rhythmMode ? 1 : 0) << 5);
if (_rhythmMode) {
// Add the key on bits for each rhythm instrument.
for (int i = 0; i < OPL_NUM_RHYTHM_INSTRUMENTS; i++) {
value |= ((_activeRhythmNotes[i].noteActive ? 1 : 0) << i);
}
}
writeRegister(OPL_REGISTER_RHYTHM, value, forceWrite);
if (_oplType == OPL::Config::kDualOpl2) {
writeRegister(OPL_REGISTER_RHYTHM | OPL_REGISTER_SET_2_OFFSET, value, forceWrite);
}
}
void MidiDriver_ADLIB_Multisource::writeVolume(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType) {
ActiveNote *activeNote = (rhythmType == RHYTHM_TYPE_UNDEFINED ? &_activeNotes[oplChannel] : &_activeRhythmNotes[rhythmType - 1]);
// Calculate operator volume.
uint16 registerOffset = determineOperatorRegisterOffset(
oplChannel, operatorNum, rhythmType, activeNote->instrumentDef->fourOperator);
const OplInstrumentOperatorDefinition &operatorDef =
activeNote->instrumentDef->getOperatorDefinition(operatorNum);
uint8 level = calculateVolume(activeNote->channel, activeNote->source, activeNote->velocity,
*activeNote->instrumentDef, operatorNum);
// Add key scaling level from the operator definition to the calculated
// level.
writeRegister(OPL_REGISTER_BASE_LEVEL + registerOffset, level | (operatorDef.level & ~OPL_MASK_LEVEL));
}
void MidiDriver_ADLIB_Multisource::writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
ActiveNote *activeNote;
if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
activeNote = &_activeRhythmNotes[rhythmType - 1];
oplChannel = OPL_RHYTHM_INSTRUMENT_CHANNELS[rhythmType - 1];
} else {
activeNote = &_activeNotes[oplChannel];
}
// Calculate channel panning.
uint16 registerOffset = determineChannelRegisterOffset(
oplChannel, activeNote->instrumentDef->fourOperator);
uint8 panning = calculatePanning(activeNote->channel, activeNote->source);
// Add connection and feedback from the instrument definition to the
// calculated panning.
writeRegister(OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING + registerOffset,
panning | (activeNote->instrumentDef->connectionFeedback0 & ~OPL_MASK_PANNING));
if (activeNote->instrumentDef->fourOperator)
// TODO Not sure if panning is necessary here.
writeRegister(OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING + registerOffset + 3,
panning | (activeNote->instrumentDef->connectionFeedback1 & ~OPL_MASK_PANNING));
}
void MidiDriver_ADLIB_Multisource::writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
_activeNotesMutex.lock();
ActiveNote *activeNote;
if (rhythmType != RHYTHM_TYPE_UNDEFINED) {
activeNote = &_activeRhythmNotes[rhythmType - 1];
oplChannel = OPL_RHYTHM_INSTRUMENT_CHANNELS[rhythmType - 1];
} else {
activeNote = &_activeNotes[oplChannel];
}
// Calculate the frequency.
uint16 channelOffset = determineChannelRegisterOffset(oplChannel, activeNote->instrumentDef->fourOperator);
uint16 frequency = calculateFrequency(activeNote->channel, activeNote->source, activeNote->oplNote);
activeNote->oplFrequency = frequency;
// Write the low 8 frequency bits.
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
(frequency >> 8) | (rhythmType == RHYTHM_TYPE_UNDEFINED && activeNote->noteActive ? OPL_MASK_KEYON : 0));
_activeNotesMutex.unlock();
}
void MidiDriver_ADLIB_Multisource::writeRegister(uint16 reg, uint8 value, bool forceWrite) {
//debug("Writing register %X %X", reg, value);
// Write the value to the register if it is a timer register, if forceWrite
// is specified or if the new register value is different from the current
// value.
if ((reg >= 1 && reg <= 3) || (_oplType == OPL::Config::kDualOpl2 && reg >= 0x101 && reg <= 0x103) ||
forceWrite || _shadowRegisters[reg] != value) {
_shadowRegisters[reg] = value;
_opl->writeReg(reg, value);
}
}