/* 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 . * */ #include "audio/adlib_ms.h" #include "common/debug.h" bool OplInstrumentOperatorDefinition::isEmpty() { return freqMultMisc == 0 && level == 0 && decayAttack == 0 && releaseSustain == 0 && waveformSelect == 0; } bool OplInstrumentDefinition::isEmpty() { if (rhythmType != RHYTHM_TYPE_UNDEFINED) { return operator0.isEmpty() && (rhythmType != RHYTHM_TYPE_BASS_DRUM || operator1.isEmpty()); } else if (!fourOperator) { return operator0.isEmpty() && operator1.isEmpty(); } else { return operator0.isEmpty() && operator1.isEmpty() && operator2.isEmpty() && operator3.isEmpty(); } } uint8 OplInstrumentDefinition::getNumberOfOperators() { if (rhythmType == RHYTHM_TYPE_UNDEFINED) { return fourOperator ? 4 : 2; } else { // The bass drum rhythm instrument uses 2 operators; the others use // only 1. return rhythmType == RHYTHM_TYPE_BASS_DRUM ? 2 : 1; } } OplInstrumentOperatorDefinition &OplInstrumentDefinition::getOperatorDefinition(uint8 operatorNum) { assert((!fourOperator && operatorNum < 2) || operatorNum < 4); switch (operatorNum) { case 0: return operator0; case 1: return operator1; case 2: return operator2; case 3: return operator3; default: // Should not happen. return operator0; } } void AdLibBnkInstrumentOperatorDefinition::toOplInstrumentOperatorDefinition(OplInstrumentOperatorDefinition &operatorDef, uint8 waveformSelect) { // Combine the separate fields of the BNK format into complete register values. operatorDef.freqMultMisc = frequencyMultiplier | (keyScalingRate == 0 ? 0 : 0x10) | (envelopeGainType == 0 ? 0 : 0x20) | (vibrato == 0 ? 0 : 0x40) | (amplitudeModulation == 0 ? 0 : 0x80); operatorDef.level = level | (keyScalingLevel << 6); operatorDef.decayAttack = decay | (attack << 4); operatorDef.releaseSustain = release | (sustain << 4); operatorDef.waveformSelect = waveformSelect; } void AdLibBnkInstrumentDefinition::toOplInstrumentDefinition(OplInstrumentDefinition &instrumentDef) { instrumentDef.fourOperator = false; operator0.toOplInstrumentOperatorDefinition(instrumentDef.operator0, waveformSelect0); operator1.toOplInstrumentOperatorDefinition(instrumentDef.operator1, waveformSelect1); instrumentDef.connectionFeedback0 = (operator0.connection == 0 ? 1 : 0) | (operator0.feedback << 1); // BNK does not support 4 operator. instrumentDef.connectionFeedback1 = 0; // TODO Figure out if this is the same as rhythmVoiceNumber instrumentDef.rhythmNote = 0; instrumentDef.rhythmType = RHYTHM_TYPE_UNDEFINED; } // These are the melodic instrument definitions used by the Win95 SB16 driver. OplInstrumentDefinition MidiDriver_ADLIB_Multisource::OPL_INSTRUMENT_BANK[128] = { // 0x00 { false, { 0x01, 0x8F, 0xF2, 0xF4, 0x00 }, { 0x01, 0x06, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x4B, 0xF2, 0xF4, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x49, 0xF2, 0xF4, 0x00 }, { 0x01, 0x00, 0xF2, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x81, 0x12, 0xF2, 0xF7, 0x00 }, { 0x41, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x57, 0xF1, 0xF7, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x93, 0xF1, 0xF7, 0x00 }, { 0x01, 0x00, 0xF2, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x80, 0xA1, 0xF2, 0x00 }, { 0x16, 0x0E, 0xF2, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x92, 0xC2, 0xF8, 0x00 }, { 0x01, 0x00, 0xC2, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x08 { false, { 0x0C, 0x5C, 0xF6, 0xF4, 0x00 }, { 0x81, 0x00, 0xF3, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x07, 0x97, 0xF3, 0xF2, 0x00 }, { 0x11, 0x80, 0xF2, 0xF1, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x17, 0x21, 0x54, 0xF4, 0x00 }, { 0x01, 0x00, 0xF4, 0xF4, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x98, 0x62, 0xF3, 0xF6, 0x00 }, { 0x81, 0x00, 0xF2, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x18, 0x23, 0xF6, 0xF6, 0x00 }, { 0x01, 0x00, 0xE7, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x15, 0x91, 0xF6, 0xF6, 0x00 }, { 0x01, 0x00, 0xF6, 0xF6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x45, 0x59, 0xD3, 0xF3, 0x00 }, { 0x81, 0x80, 0xA3, 0xF3, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x03, 0x49, 0x75, 0xF5, 0x01 }, { 0x81, 0x80, 0xB5, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x10 { false, { 0x71, 0x92, 0xF6, 0x14, 0x00 }, { 0x31, 0x00, 0xF1, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x72, 0x14, 0xC7, 0x58, 0x00 }, { 0x30, 0x00, 0xC7, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x70, 0x44, 0xAA, 0x18, 0x00 }, { 0xB1, 0x00, 0x8A, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x23, 0x93, 0x97, 0x23, 0x01 }, { 0xB1, 0x00, 0x55, 0x14, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x61, 0x13, 0x97, 0x04, 0x01 }, { 0xB1, 0x80, 0x55, 0x04, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x24, 0x48, 0x98, 0x2A, 0x01 }, { 0xB1, 0x00, 0x46, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x61, 0x13, 0x91, 0x06, 0x01 }, { 0x21, 0x00, 0x61, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x13, 0x71, 0x06, 0x00 }, { 0xA1, 0x89, 0x61, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x18 { false, { 0x02, 0x9C, 0xF3, 0x94, 0x01 }, { 0x41, 0x80, 0xF3, 0xC8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x03, 0x54, 0xF3, 0x9A, 0x01 }, { 0x11, 0x00, 0xF1, 0xE7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x23, 0x5F, 0xF1, 0x3A, 0x00 }, { 0x21, 0x00, 0xF2, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x03, 0x87, 0xF6, 0x22, 0x01 }, { 0x21, 0x80, 0xF3, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x03, 0x47, 0xF9, 0x54, 0x00 }, { 0x21, 0x00, 0xF6, 0x3A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x23, 0x4A, 0x91, 0x41, 0x01 }, { 0x21, 0x05, 0x84, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x23, 0x4A, 0x95, 0x19, 0x01 }, { 0x21, 0x00, 0x94, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x09, 0xA1, 0x20, 0x4F, 0x00 }, { 0x84, 0x80, 0xD1, 0xF8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x20 { false, { 0x21, 0x1E, 0x94, 0x06, 0x00 }, { 0xA2, 0x00, 0xC3, 0xA6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x12, 0xF1, 0x28, 0x00 }, { 0x31, 0x00, 0xF1, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x8D, 0xF1, 0xE8, 0x00 }, { 0x31, 0x00, 0xF1, 0x78, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x5B, 0x51, 0x28, 0x00 }, { 0x32, 0x00, 0x71, 0x48, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x8B, 0xA1, 0x9A, 0x00 }, { 0x21, 0x40, 0xF2, 0xDF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x8B, 0xA2, 0x16, 0x00 }, { 0x21, 0x08, 0xA1, 0xDF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x8B, 0xF4, 0xE8, 0x00 }, { 0x31, 0x00, 0xF1, 0x78, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x12, 0xF1, 0x28, 0x00 }, { 0x31, 0x00, 0xF1, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x28 { false, { 0x31, 0x15, 0xDD, 0x13, 0x01 }, { 0x21, 0x00, 0x56, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x16, 0xDD, 0x13, 0x01 }, { 0x21, 0x00, 0x66, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x71, 0x49, 0xD1, 0x1C, 0x01 }, { 0x31, 0x00, 0x61, 0x0C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x4D, 0x71, 0x12, 0x01 }, { 0x23, 0x80, 0x72, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xF1, 0x40, 0xF1, 0x21, 0x01 }, { 0xE1, 0x00, 0x6F, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x02, 0x1A, 0xF5, 0x75, 0x01 }, { 0x01, 0x80, 0x85, 0x35, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x02, 0x1D, 0xF5, 0x75, 0x01 }, { 0x01, 0x80, 0xF3, 0xF4, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x10, 0x41, 0xF5, 0x05, 0x01 }, { 0x11, 0x00, 0xF2, 0xC3, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x30 { false, { 0x21, 0x9B, 0xB1, 0x25, 0x01 }, { 0xA2, 0x01, 0x72, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA1, 0x98, 0x7F, 0x03, 0x01 }, { 0x21, 0x00, 0x3F, 0x07, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA1, 0x93, 0xC1, 0x12, 0x00 }, { 0x61, 0x00, 0x4F, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x18, 0xC1, 0x22, 0x00 }, { 0x61, 0x00, 0x4F, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x5B, 0xF4, 0x15, 0x00 }, { 0x72, 0x83, 0x8A, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA1, 0x90, 0x74, 0x39, 0x00 }, { 0x61, 0x00, 0x71, 0x67, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x71, 0x57, 0x54, 0x05, 0x00 }, { 0x72, 0x00, 0x7A, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x90, 0x00, 0x54, 0x63, 0x00 }, { 0x41, 0x00, 0xA5, 0x45, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x38 { false, { 0x21, 0x92, 0x85, 0x17, 0x00 }, { 0x21, 0x01, 0x8F, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x94, 0x75, 0x17, 0x00 }, { 0x21, 0x05, 0x8F, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x94, 0x76, 0x15, 0x00 }, { 0x61, 0x00, 0x82, 0x37, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x43, 0x9E, 0x17, 0x01 }, { 0x21, 0x00, 0x62, 0x2C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x9B, 0x61, 0x6A, 0x00 }, { 0x21, 0x00, 0x7F, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x61, 0x8A, 0x75, 0x1F, 0x00 }, { 0x22, 0x06, 0x74, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA1, 0x86, 0x72, 0x55, 0x01 }, { 0x21, 0x83, 0x71, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x4D, 0x54, 0x3C, 0x00 }, { 0x21, 0x00, 0xA6, 0x1C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x40 { false, { 0x31, 0x8F, 0x93, 0x02, 0x01 }, { 0x61, 0x00, 0x72, 0x0B, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x8E, 0x93, 0x03, 0x01 }, { 0x61, 0x00, 0x72, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x91, 0x93, 0x03, 0x01 }, { 0x61, 0x00, 0x82, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x8E, 0x93, 0x0F, 0x01 }, { 0x61, 0x00, 0x72, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x4B, 0xAA, 0x16, 0x01 }, { 0x21, 0x00, 0x8F, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x90, 0x7E, 0x17, 0x01 }, { 0x21, 0x00, 0x8B, 0x0C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x81, 0x75, 0x19, 0x01 }, { 0x32, 0x00, 0x61, 0x19, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x32, 0x90, 0x9B, 0x21, 0x00 }, { 0x21, 0x00, 0x72, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x48 { false, { 0xE1, 0x1F, 0x85, 0x5F, 0x00 }, { 0xE1, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xE1, 0x46, 0x88, 0x5F, 0x00 }, { 0xE1, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA1, 0x9C, 0x75, 0x1F, 0x00 }, { 0x21, 0x00, 0x75, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x8B, 0x84, 0x58, 0x00 }, { 0x21, 0x00, 0x65, 0x1A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xE1, 0x4C, 0x66, 0x56, 0x00 }, { 0xA1, 0x00, 0x65, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x62, 0xCB, 0x76, 0x46, 0x00 }, { 0xA1, 0x00, 0x55, 0x36, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x62, 0x99, 0x57, 0x07, 0x00 }, { 0xA1, 0x00, 0x56, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3B, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x62, 0x93, 0x77, 0x07, 0x00 }, { 0xA1, 0x00, 0x76, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3B, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x50 { false, { 0x22, 0x59, 0xFF, 0x03, 0x02 }, { 0x21, 0x00, 0xFF, 0x0F, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x0E, 0xFF, 0x0F, 0x01 }, { 0x21, 0x00, 0xFF, 0x0F, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x22, 0x46, 0x86, 0x55, 0x00 }, { 0x21, 0x80, 0x64, 0x18, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x45, 0x66, 0x12, 0x00 }, { 0xA1, 0x00, 0x96, 0x0A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x8B, 0x92, 0x2A, 0x01 }, { 0x22, 0x00, 0x91, 0x2A, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA2, 0x9E, 0xDF, 0x05, 0x00 }, { 0x61, 0x40, 0x6F, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x20, 0x1A, 0xEF, 0x01, 0x00 }, { 0x60, 0x00, 0x8F, 0x06, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x8F, 0xF1, 0x29, 0x00 }, { 0x21, 0x80, 0xF4, 0x09, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x58 { false, { 0x77, 0xA5, 0x53, 0x94, 0x00 }, { 0xA1, 0x00, 0xA0, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x61, 0x1F, 0xA8, 0x11, 0x00 }, { 0xB1, 0x80, 0x25, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x61, 0x17, 0x91, 0x34, 0x00 }, { 0x61, 0x00, 0x55, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x71, 0x5D, 0x54, 0x01, 0x00 }, { 0x72, 0x00, 0x6A, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x97, 0x21, 0x43, 0x00 }, { 0xA2, 0x00, 0x42, 0x35, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA1, 0x1C, 0xA1, 0x77, 0x01 }, { 0x21, 0x00, 0x31, 0x47, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x89, 0x11, 0x33, 0x00 }, { 0x61, 0x03, 0x42, 0x25, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA1, 0x15, 0x11, 0x47, 0x01 }, { 0x21, 0x00, 0xCF, 0x07, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x60 { false, { 0x3A, 0xCE, 0xF8, 0xF6, 0x00 }, { 0x51, 0x00, 0x86, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x32, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x15, 0x21, 0x23, 0x01 }, { 0x21, 0x00, 0x41, 0x13, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x06, 0x5B, 0x74, 0x95, 0x00 }, { 0x01, 0x00, 0xA5, 0x72, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x22, 0x92, 0xB1, 0x81, 0x00 }, { 0x61, 0x83, 0xF2, 0x26, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x41, 0x4D, 0xF1, 0x51, 0x01 }, { 0x42, 0x00, 0xF2, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x61, 0x94, 0x11, 0x51, 0x01 }, { 0xA3, 0x80, 0x11, 0x13, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x61, 0x8C, 0x11, 0x31, 0x00 }, { 0xA1, 0x80, 0x1D, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xA4, 0x4C, 0xF3, 0x73, 0x01 }, { 0x61, 0x00, 0x81, 0x23, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x68 { false, { 0x02, 0x85, 0xD2, 0x53, 0x00 }, { 0x07, 0x03, 0xF2, 0xF6, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x11, 0x0C, 0xA3, 0x11, 0x01 }, { 0x13, 0x80, 0xA2, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x11, 0x06, 0xF6, 0x41, 0x01 }, { 0x11, 0x00, 0xF2, 0xE6, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x93, 0x91, 0xD4, 0x32, 0x00 }, { 0x91, 0x00, 0xEB, 0x11, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x04, 0x4F, 0xFA, 0x56, 0x00 }, { 0x01, 0x00, 0xC2, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x21, 0x49, 0x7C, 0x20, 0x00 }, { 0x22, 0x00, 0x6F, 0x0C, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x31, 0x85, 0xDD, 0x33, 0x01 }, { 0x21, 0x00, 0x56, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x20, 0x04, 0xDA, 0x05, 0x02 }, { 0x21, 0x81, 0x8F, 0x0B, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x70 { false, { 0x05, 0x6A, 0xF1, 0xE5, 0x00 }, { 0x03, 0x80, 0xC3, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x07, 0x15, 0xEC, 0x26, 0x00 }, { 0x02, 0x00, 0xF8, 0x16, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x05, 0x9D, 0x67, 0x35, 0x00 }, { 0x01, 0x00, 0xDF, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x18, 0x96, 0xFA, 0x28, 0x00 }, { 0x12, 0x00, 0xF8, 0xE5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x10, 0x86, 0xA8, 0x07, 0x00 }, { 0x00, 0x03, 0xFA, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x11, 0x41, 0xF8, 0x47, 0x02 }, { 0x10, 0x03, 0xF3, 0x03, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x8E, 0xF1, 0x06, 0x02 }, { 0x10, 0x00, 0xF3, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x00, 0x1F, 0x00, 0x00 }, { 0xC0, 0x00, 0x1F, 0xFF, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x78 { false, { 0x06, 0x80, 0xF8, 0x24, 0x00 }, { 0x03, 0x88, 0x56, 0x84, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x00, 0xF8, 0x00, 0x00 }, { 0xD0, 0x05, 0x34, 0x04, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xC0, 0x00, 0x1F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0xD5, 0x95, 0x37, 0xA3, 0x00 }, { 0xDA, 0x40, 0x56, 0x37, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x35, 0x5C, 0xB2, 0x61, 0x02 }, { 0x14, 0x08, 0xF4, 0x15, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x4F, 0xF5, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x26, 0x00, 0xFF, 0x01, 0x00 }, { 0xE4, 0x00, 0x12, 0x16, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0xF3, 0xF0, 0x00 }, { 0x00, 0x00, 0xF6, 0xC9, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED } }; // These are the rhythm instrument definitions used by the Win95 SB16 driver. OplInstrumentDefinition MidiDriver_ADLIB_Multisource::OPL_RHYTHM_BANK[62] = { // GS percussion start // 0x1B { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // 0x20 { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED }, // GM percussion start // 0x23 { false, { 0x10, 0x44, 0xF8, 0x77, 0x02 }, { 0x11, 0x00, 0xF3, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x23, RHYTHM_TYPE_UNDEFINED }, { false, { 0x10, 0x44, 0xF8, 0x77, 0x02 }, { 0x11, 0x00, 0xF3, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x23, RHYTHM_TYPE_UNDEFINED }, { false, { 0x02, 0x07, 0xF9, 0xFF, 0x00 }, { 0x11, 0x00, 0xF8, 0xFF, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x34, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0xFC, 0x05, 0x02 }, { 0x00, 0x00, 0xFA, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x02, 0xFF, 0x07, 0x00 }, { 0x01, 0x00, 0xFF, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x3A, RHYTHM_TYPE_UNDEFINED }, // 0x28 { false, { 0x00, 0x00, 0xFC, 0x05, 0x02 }, { 0x00, 0x00, 0xFA, 0x17, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x2F, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0C, 0x00, 0xF6, 0x08, 0x00 }, { 0x12, 0x00, 0xFB, 0x47, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x31, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0C, 0x00, 0xF6, 0x08, 0x00 }, { 0x12, 0x05, 0x7B, 0x47, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x33, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0C, 0x00, 0xF6, 0x02, 0x00 }, { 0x12, 0x00, 0xCB, 0x43, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3A, 0x00, 0x2B, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x36, RHYTHM_TYPE_UNDEFINED }, // 0x30 { false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x39, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x48, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0xF6, 0x0C, 0x00 }, { 0x00, 0x00, 0xF6, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x34, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x08, 0xF8, 0x42, 0x00 }, { 0x07, 0x4A, 0xF4, 0xE4, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4C, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x00, 0xF5, 0x30, 0x00 }, { 0xD0, 0x0A, 0x9F, 0x02, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x0A, 0xE4, 0xE4, 0x03 }, { 0x07, 0x5D, 0xF5, 0xE5, 0x01 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x24, RHYTHM_TYPE_UNDEFINED }, { false, { 0x02, 0x03, 0xB4, 0x04, 0x00 }, { 0x05, 0x0A, 0x97, 0xF7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4C, RHYTHM_TYPE_UNDEFINED }, { false, { 0x4E, 0x00, 0xF6, 0x00, 0x00 }, { 0x9E, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED }, // 0x38 { false, { 0x11, 0x45, 0xF8, 0x37, 0x02 }, { 0x10, 0x08, 0xF3, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x38, 0x00, 0x53, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x00, 0xF6, 0x00, 0x00 }, { 0xD0, 0x00, 0x9F, 0x02, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x54, RHYTHM_TYPE_UNDEFINED }, { false, { 0x80, 0x00, 0xFF, 0x03, 0x03 }, { 0x10, 0x0D, 0xFF, 0x14, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3C, 0x00, 0x18, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x08, 0xF8, 0x42, 0x00 }, { 0x07, 0x4A, 0xF4, 0xE4, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x4D, RHYTHM_TYPE_UNDEFINED }, { false, { 0x06, 0x0B, 0xF5, 0x0C, 0x00 }, { 0x02, 0x00, 0xF5, 0x08, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x00, 0xFA, 0xBF, 0x00 }, { 0x02, 0x00, 0xC8, 0x97, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x37, 0x00, 0x41, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x51, 0xFA, 0x87, 0x00 }, { 0x01, 0x00, 0xFA, 0xB7, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x3B, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x54, 0xFA, 0x8D, 0x00 }, { 0x02, 0x00, 0xF8, 0xB8, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x33, RHYTHM_TYPE_UNDEFINED }, // 0x40 { false, { 0x01, 0x59, 0xFA, 0x88, 0x00 }, { 0x02, 0x00, 0xF8, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x36, 0x00, 0x2D, RHYTHM_TYPE_UNDEFINED }, { false, { 0x01, 0x00, 0xF9, 0x0A, 0x03 }, { 0x00, 0x00, 0xFA, 0x06, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x47, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x80, 0xF9, 0x89, 0x03 }, { 0x00, 0x00, 0xF6, 0x6C, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED }, { false, { 0x03, 0x80, 0xF8, 0x88, 0x03 }, { 0x0C, 0x08, 0xF6, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3F, 0x00, 0x3A, RHYTHM_TYPE_UNDEFINED }, { false, { 0x03, 0x85, 0xF8, 0x88, 0x03 }, { 0x0C, 0x00, 0xF6, 0xB6, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3F, 0x00, 0x35, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x40, 0x76, 0x4F, 0x00 }, { 0x00, 0x08, 0x77, 0x18, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x40, RHYTHM_TYPE_UNDEFINED }, { false, { 0x0E, 0x40, 0xC8, 0x49, 0x00 }, { 0x03, 0x00, 0x9B, 0x69, 0x02 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x47, RHYTHM_TYPE_UNDEFINED }, { false, { 0xD7, 0xDC, 0xAD, 0x05, 0x03 }, { 0xC7, 0x00, 0x8D, 0x05, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3D, RHYTHM_TYPE_UNDEFINED }, // 0x48 { false, { 0xD7, 0xDC, 0xA8, 0x04, 0x03 }, { 0xC7, 0x00, 0x88, 0x04, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x3D, RHYTHM_TYPE_UNDEFINED }, { false, { 0x80, 0x00, 0xF6, 0x06, 0x03 }, { 0x11, 0x00, 0x67, 0x17, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED }, { false, { 0x80, 0x00, 0xF5, 0x05, 0x02 }, { 0x11, 0x09, 0x46, 0x16, 0x03 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x3E, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED }, { false, { 0x06, 0x3F, 0x00, 0xF4, 0x00 }, { 0x15, 0x00, 0xF7, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x31, 0x00, 0x45, RHYTHM_TYPE_UNDEFINED }, { false, { 0x06, 0x3F, 0x00, 0xF4, 0x03 }, { 0x12, 0x00, 0xF7, 0xF5, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x30, 0x00, 0x44, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x3F, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x4A, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x3C, RHYTHM_TYPE_UNDEFINED }, // 0x50 { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x50, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x40, RHYTHM_TYPE_UNDEFINED }, // GM percussion end { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x45, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x49, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x4B, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x44, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x30, RHYTHM_TYPE_UNDEFINED }, { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x35, RHYTHM_TYPE_UNDEFINED }, // 0x58 { false, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00 }, 0x00, 0x00, 0x00, RHYTHM_TYPE_UNDEFINED } // GS percussion end }; // Rhythm mode uses OPL channels 6, 7 and 8. The remaining channels are // available for melodic instruments. uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL2[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL2_RHYTHM[] = { 0, 1, 2, 3, 4, 5 }; uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL3[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }; uint8 MidiDriver_ADLIB_Multisource::MELODIC_CHANNELS_OPL3_RHYTHM[] = { 0, 1, 2, 3, 4, 5, 9, 10, 11, 12, 13, 14, 15, 16, 17 }; const uint8 MidiDriver_ADLIB_Multisource::OPL_REGISTER_RHYTHM_OFFSETS[OPL_NUM_RHYTHM_INSTRUMENTS] = { 0x11, 0x15, 0x12, 0x14, 0x10 }; const uint8 MidiDriver_ADLIB_Multisource::OPL_RHYTHM_INSTRUMENT_CHANNELS[OPL_NUM_RHYTHM_INSTRUMENTS] = { 7, 8, 8, 7, 6 }; // These are the note frequency values used by the Win95 SB16 driver. const uint16 MidiDriver_ADLIB_Multisource::OPL_NOTE_FREQUENCIES[12] = { 0x0AB7, 0x0B5A, 0x0C07, 0x0CBE, 0x0D80, 0x0E4D, 0x0F27, 0x100E, 0x1102, 0x1205, 0x1318, 0x143A }; // These are the volume values used by the Win95 SB16 driver. const uint8 MidiDriver_ADLIB_Multisource::OPL_VOLUME_LOOKUP[32] = { 0x50, 0x3F, 0x28, 0x24, 0x20, 0x1C, 0x17, 0x15, 0x13, 0x11, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00 }; MidiDriver_ADLIB_Multisource::MidiChannelControlData::MidiChannelControlData() { init(); } void MidiDriver_ADLIB_Multisource::MidiChannelControlData::init() { program = 0; channelPressure = 0; pitchBend = MIDI_PITCH_BEND_DEFAULT; modulation = 0; volume = 0; panning = MIDI_PANNING_DEFAULT; expression = MIDI_EXPRESSION_DEFAULT; sustain = false; rpn = MIDI_RPN_NULL; pitchBendSensitivity = GM_PITCH_BEND_SENSITIVITY_DEFAULT; pitchBendSensitivityCents = 0; masterTuningFine = MIDI_MASTER_TUNING_FINE_DEFAULT; masterTuningCoarse = MIDI_MASTER_TUNING_COARSE_DEFAULT; } MidiDriver_ADLIB_Multisource::ActiveNote::ActiveNote() { init(); } void MidiDriver_ADLIB_Multisource::ActiveNote::init() { noteActive = false; noteSustained = false; note = 0; velocity = 0; channel = 0xFF; source = 0xFF; oplNote = 0; oplFrequency = 0; noteCounterValue = 0; instrumentId = 0; instrumentDef = nullptr; channelAllocated = false; } bool MidiDriver_ADLIB_Multisource::detectOplType(OPL::Config::OplType oplType) { return OPL::Config::detect(oplType) >= 0; } MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(OPL::Config::OplType oplType, int timerFrequency) : _oplType(oplType), _opl(nullptr), _isOpen(false), _accuracyMode(ACCURACY_MODE_SB16_WIN95), _allocationMode(ALLOCATION_MODE_DYNAMIC), _rhythmModeIgnoreNoteOffs(false), _defaultChannelVolume(0), _noteSelect(NOTE_SELECT_MODE_0), _modulationDepth(MODULATION_DEPTH_HIGH), _vibratoDepth(VIBRATO_DEPTH_HIGH), _rhythmMode(false), _instrumentBank(OPL_INSTRUMENT_BANK), _rhythmBank(OPL_RHYTHM_BANK), _rhythmBankFirstNote(GS_RHYTHM_FIRST_NOTE), _rhythmBankLastNote(GS_RHYTHM_LAST_NOTE), _melodicChannels(nullptr), _numMelodicChannels(0), _noteCounter(1), _oplFrequencyConversionFactor(pow(2, 20) / 49716.0f), _timerFrequency(timerFrequency) { memset(_channelAllocations, 0xFF, sizeof(_channelAllocations)); Common::fill(_shadowRegisters, _shadowRegisters + sizeof(_shadowRegisters), 0); _timerRate = 1000000 / _timerFrequency; } MidiDriver_ADLIB_Multisource::~MidiDriver_ADLIB_Multisource() { close(); } int MidiDriver_ADLIB_Multisource::open() { if (_isOpen) return MERR_ALREADY_OPEN; int8 detectResult = OPL::Config::detect(_oplType); if (detectResult == -1 && _oplType == OPL::Config::kDualOpl2) { // Try to emulate dual OPL2 on OPL3 // TODO Implement this in fmopl detectResult = OPL::Config::detect(OPL::Config::kOpl3); } if (detectResult == -1) return MERR_DEVICE_NOT_AVAILABLE; // Create the emulator / hardware interface. _opl = OPL::Config::create(_oplType); if (!_opl) return MERR_CANNOT_CONNECT; _isOpen = true; // Initialize emulator / hardware interface. if (!_opl->init()) return MERR_CANNOT_CONNECT; // Set the melodic channels applicable for the OPL chip type. determineMelodicChannels(); // Set default MIDI channel volume on control data. for (int i = 0; i < MAXIMUM_SOURCES; i++) { for (int j = 0; j < MIDI_CHANNEL_COUNT; j++) { _controlData[i][j].volume = _defaultChannelVolume; } applyControllerDefaults(i); } // Set default OPL register values. initOpl(); // Start the emulator / hardware interface. This will also start the timer // callbacks. _opl->start(new Common::Functor0Mem(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(i + 1)); break; } } } } else { // Find the OPL channel playing this note. for (int i = 0; i < _numMelodicChannels; i++) { uint8 oplChannel = _melodicChannels[i]; if (_activeNotes[oplChannel].noteActive && _activeNotes[oplChannel].source == source && _activeNotes[oplChannel].channel == channel && _activeNotes[oplChannel].note == note) { if (_controlData[source][channel].sustain) { // Sustain controller is on. Sustain the note instead of // ending it. _activeNotes[oplChannel].noteSustained = true; } else { writeKeyOff(oplChannel); } } } } _activeNotesMutex.unlock(); } void MidiDriver_ADLIB_Multisource::noteOn(uint8 channel, uint8 note, uint8 velocity, uint8 source) { if (velocity == 0) { // Note on with velocity 0 is a note off. noteOff(channel, note, velocity, source); return; } InstrumentInfo instrument = determineInstrument(channel, source, note); // If rhythm mode is on and the note is on the rhythm channel, this note // will be played using the OPL rhythm register. bool rhythmNote = _rhythmMode && channel == MIDI_RHYTHM_CHANNEL; if (!instrument.instrumentDef || instrument.instrumentDef->isEmpty() || (rhythmNote && instrument.instrumentDef->rhythmType == RHYTHM_TYPE_UNDEFINED)) { // Instrument definition contains no data or it is not suitable for // rhythm mode, so the note cannot be played. return; } _activeNotesMutex.lock(); // Determine the OPL channel to use and the active note data to update. uint8 oplChannel = 0xFF; ActiveNote *activeNote = nullptr; if (rhythmNote) { activeNote = &_activeRhythmNotes[instrument.instrumentDef->rhythmType - 1]; } else { // Allocate a melodic OPL channel. oplChannel = allocateOplChannel(channel, source, instrument.instrumentId); if (oplChannel != 0xFF) activeNote = &_activeNotes[oplChannel]; } if (activeNote != nullptr) { if (activeNote->noteActive) { // Turn off the note currently playing on this OPL channel or // rhythm instrument. writeKeyOff(oplChannel, instrument.instrumentDef->rhythmType); } // Update the active note data. activeNote->noteActive = true; activeNote->noteSustained = false; activeNote->note = note; activeNote->velocity = velocity; activeNote->channel = channel; activeNote->source = source; activeNote->oplNote = instrument.oplNote; // Increase the note counter when playing a new note. activeNote->noteCounterValue = _noteCounter++; activeNote->instrumentId = instrument.instrumentId; activeNote->instrumentDef = instrument.instrumentDef; // Write out the instrument definition, volume and panning. writeInstrument(oplChannel, instrument); // Calculate and write frequency and block and write key on bit. writeFrequency(oplChannel, instrument.instrumentDef->rhythmType); if (rhythmNote) // Update the rhythm register. writeRhythm(); } _activeNotesMutex.unlock(); } void MidiDriver_ADLIB_Multisource::polyAftertouch(uint8 channel, uint8 note, uint8 pressure, uint8 source) { // Because this event is not required by General MIDI and not implemented // in the Win95 SB16 driver, there is no default implementation. } void MidiDriver_ADLIB_Multisource::controlChange(uint8 channel, uint8 controller, uint8 value, uint8 source) { // Call the function for handling each controller. switch (controller) { case MIDI_CONTROLLER_MODULATION: modulation(channel, value, source); break; case MIDI_CONTROLLER_DATA_ENTRY_MSB: dataEntry(channel, value, 0xFF, source); break; case MIDI_CONTROLLER_VOLUME: volume(channel, value, source); break; case MIDI_CONTROLLER_PANNING: panning(channel, value, source); break; case MIDI_CONTROLLER_EXPRESSION: expression(channel, value, source); break; case MIDI_CONTROLLER_DATA_ENTRY_LSB: dataEntry(channel, 0xFF, value, source); break; case MIDI_CONTROLLER_SUSTAIN: sustain(channel, value, source); break; case MIDI_CONTROLLER_RPN_LSB: registeredParameterNumber(channel, 0xFF, value, source); break; case MIDI_CONTROLLER_RPN_MSB: registeredParameterNumber(channel, value, 0xFF, source); break; case MIDI_CONTROLLER_ALL_SOUND_OFF: allSoundOff(channel, source); break; case MIDI_CONTROLLER_RESET_ALL_CONTROLLERS: resetAllControllers(channel, source); break; case MIDI_CONTROLLER_ALL_NOTES_OFF: case MIDI_CONTROLLER_OMNI_OFF: case MIDI_CONTROLLER_OMNI_ON: case MIDI_CONTROLLER_MONO_ON: case MIDI_CONTROLLER_POLY_ON: // The omni/mono/poly events also act as an all notes off. allNotesOff(channel, source); break; default: //debug("MidiDriver_ADLIB_Multisource::controlChange - Unsupported controller %X", controller); break; } } void MidiDriver_ADLIB_Multisource::programChange(uint8 channel, uint8 program, uint8 source) { // Just set the MIDI program value; this event does not affect active notes. _controlData[source][channel].program = program; } void MidiDriver_ADLIB_Multisource::channelAftertouch(uint8 channel, uint8 pressure, uint8 source) { // Even though this event is required by General MIDI, it is not implemented // in the Win95 SB16 driver, so there is no default implementation. } void MidiDriver_ADLIB_Multisource::pitchBend(uint8 channel, uint8 pitchBendLsb, uint8 pitchBendMsb, uint8 source) { _controlData[source][channel].pitchBend = ((uint16)pitchBendMsb) << 7 | pitchBendLsb; // Recalculate and write the frequencies of the active notes on this MIDI // channel to let the new pitch bend value take effect. recalculateFrequencies(channel, source); } void MidiDriver_ADLIB_Multisource::sysEx(const byte *msg, uint16 length) { if (length >= 4 && msg[0] == 0x7E && msg[2] == 0x09 && msg[3] == 0x01) { // F0 7E 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(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(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); } }