mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 21:59:17 +00:00
36001426dc
This commit adds a MidiParser implementation for Ultima 6's M format and MidiDrivers for AdLib and MT-32. This replaces the old implementation based on AdPlug, which supports AdLib only and does not have ScummVM's support for various OPL emulators and devices. Music for Savage Empire and Martian Dreams has been temporarily disabled, because these games use a different music format and there is no MidiParser yet.
1935 lines
96 KiB
C++
1935 lines
96 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),
|
|
_instrumentWriteMode(INSTRUMENT_WRITE_MODE_NOTE_ON),
|
|
_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;
|
|
|
|
if (_instrumentWriteMode == INSTRUMENT_WRITE_MODE_NOTE_ON) {
|
|
// 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;
|
|
|
|
if (_instrumentWriteMode == INSTRUMENT_WRITE_MODE_PROGRAM_CHANGE && !(_rhythmMode && channel == MIDI_RHYTHM_CHANNEL)) {
|
|
InstrumentInfo instrument = determineInstrument(channel, source, 0);
|
|
|
|
if (!instrument.instrumentDef || instrument.instrumentDef->isEmpty()) {
|
|
// Instrument definition contains no data.
|
|
return;
|
|
}
|
|
|
|
_activeNotesMutex.lock();
|
|
|
|
// Determine the OPL channel to use and the active note data to update.
|
|
uint8 oplChannel = 0xFF;
|
|
ActiveNote *activeNote = nullptr;
|
|
// Allocate a melodic OPL channel.
|
|
oplChannel = allocateOplChannel(channel, source, instrument.instrumentId);
|
|
if (oplChannel != 0xFF) {
|
|
activeNote = &_activeNotes[oplChannel];
|
|
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->channel = channel;
|
|
activeNote->source = source;
|
|
activeNote->instrumentId = instrument.instrumentId;
|
|
activeNote->instrumentDef = instrument.instrumentDef;
|
|
|
|
writeInstrument(oplChannel, instrument);
|
|
}
|
|
|
|
_activeNotesMutex.unlock();
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|