diff options
Diffstat (limited to 'audio')
72 files changed, 4823 insertions, 1740 deletions
diff --git a/audio/audiostream.cpp b/audio/audiostream.cpp index 2d65d4afef..8bd4b95c49 100644 --- a/audio/audiostream.cpp +++ b/audio/audiostream.cpp @@ -98,6 +98,10 @@ LoopingAudioStream::LoopingAudioStream(RewindableAudioStream *stream, uint loops // TODO: Properly indicate error _loops = _completeIterations = 1; } + if (stream->endOfData()) { + // Apparently this is an empty stream + _loops = _completeIterations = 1; + } } int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) { @@ -118,6 +122,10 @@ int LoopingAudioStream::readBuffer(int16 *buffer, const int numSamples) { _loops = _completeIterations = 1; return samplesRead; } + if (_parent->endOfData()) { + // Apparently this is an empty stream + _loops = _completeIterations = 1; + } return samplesRead + readBuffer(buffer + samplesRead, remainingSamples); } diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp index 2fe509e1f3..61b0abaaca 100644 --- a/audio/decoders/adpcm.cpp +++ b/audio/decoders/adpcm.cpp @@ -268,7 +268,6 @@ static const int MSADPCMAdaptationTable[] = { 768, 614, 512, 409, 307, 230, 230, 230 }; - int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) { int32 predictor; @@ -290,40 +289,42 @@ int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) { int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { int samples; byte data; - int i = 0; - - samples = 0; + int i; - while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { - if (_blockPos[0] == _blockAlign) { - // read block header - for (i = 0; i < _channels; i++) { - _status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6); - _status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor]; - _status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor]; - } + for (samples = 0; samples < numSamples && !endOfData(); samples++) { + if (_decodedSampleCount == 0) { + if (_blockPos[0] == _blockAlign) { + // read block header + for (i = 0; i < _channels; i++) { + _status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6); + _status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor]; + _status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor]; + } - for (i = 0; i < _channels; i++) - _status.ch[i].delta = _stream->readSint16LE(); + for (i = 0; i < _channels; i++) + _status.ch[i].delta = _stream->readSint16LE(); - for (i = 0; i < _channels; i++) - _status.ch[i].sample1 = _stream->readSint16LE(); + for (i = 0; i < _channels; i++) + _status.ch[i].sample1 = _stream->readSint16LE(); - for (i = 0; i < _channels; i++) - buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE(); + for (i = 0; i < _channels; i++) + _decodedSamples[_decodedSampleCount++] = _status.ch[i].sample2 = _stream->readSint16LE(); - for (i = 0; i < _channels; i++) - buffer[samples++] = _status.ch[i].sample1; + for (i = 0; i < _channels; i++) + _decodedSamples[_decodedSampleCount++] = _status.ch[i].sample1; - _blockPos[0] = _channels * 7; + _blockPos[0] = _channels * 7; + } else { + data = _stream->readByte(); + _blockPos[0]++; + _decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f); + _decodedSamples[_decodedSampleCount++] = decodeMS(&_status.ch[_channels - 1], data & 0x0f); + } } - for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { - data = _stream->readByte(); - _blockPos[0]++; - buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f); - buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f); - } + // (1 - (count - 1)) ensures that _decodedSamples acts as a FIFO of depth 2 + buffer[samples] = _decodedSamples[1 - (_decodedSampleCount - 1)]; + _decodedSampleCount--; } return samples; @@ -432,7 +433,7 @@ int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) { return samp; } -RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) { +RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, ADPCMType type, int rate, int channels, uint32 blockAlign) { // If size is 0, report the entire size of the stream if (!size) size = stream->size(); diff --git a/audio/decoders/adpcm.h b/audio/decoders/adpcm.h index ac8d529917..d3c46574bf 100644 --- a/audio/decoders/adpcm.h +++ b/audio/decoders/adpcm.h @@ -51,7 +51,7 @@ class RewindableAudioStream; // http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs // Usually, if the audio stream we're trying to play has the FourCC header // string intact, it's easy to discern which encoding is used -enum typesADPCM { +enum ADPCMType { kADPCMOki, // Dialogic/Oki ADPCM (aka VOX) kADPCMMSIma, // Microsoft IMA ADPCM kADPCMMS, // Microsoft ADPCM @@ -76,9 +76,9 @@ enum typesADPCM { RewindableAudioStream *makeADPCMStream( Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, - uint32 size, typesADPCM type, - int rate = 22050, - int channels = 2, + uint32 size, ADPCMType type, + int rate, + int channels, uint32 blockAlign = 0); } // End of namespace Audio diff --git a/audio/decoders/adpcm_intern.h b/audio/decoders/adpcm_intern.h index 3b8d8c74d0..66a1aa605f 100644 --- a/audio/decoders/adpcm_intern.h +++ b/audio/decoders/adpcm_intern.h @@ -206,12 +206,19 @@ public: if (blockAlign == 0) error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM"); memset(&_status, 0, sizeof(_status)); + _decodedSampleCount = 0; } + virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos) && (_decodedSampleCount == 0); } + virtual int readBuffer(int16 *buffer, const int numSamples); protected: int16 decodeMS(ADPCMChannelStatus *c, byte); + +private: + uint8 _decodedSampleCount; + int16 _decodedSamples[4]; }; // Duck DK3 IMA ADPCM Decoder diff --git a/audio/decoders/aiff.h b/audio/decoders/aiff.h index afcdb6ae6c..0d96e73c26 100644 --- a/audio/decoders/aiff.h +++ b/audio/decoders/aiff.h @@ -48,7 +48,7 @@ class SeekableAudioStream; * successful. In that case, the stream's seek position will be set to the * start of the audio data, and size, rate and flags contain information * necessary for playback. Currently this function only supports uncompressed - * raw PCM data as well as IMA ADPCM. + * raw PCM. */ extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags); diff --git a/audio/decoders/qdm2.cpp b/audio/decoders/qdm2.cpp index 732de311aa..b70fc39e48 100644 --- a/audio/decoders/qdm2.cpp +++ b/audio/decoders/qdm2.cpp @@ -872,7 +872,7 @@ void initVlcSparse(VLC *vlc, int nb_bits, int nb_codes, codes, codes_wrap, codes_size, symbols, symbols_wrap, symbols_size, 0, 0, 4 | 2) < 0) { - free(&vlc->table); + free(vlc->table); return; // Error } diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp index 0588650ec6..787b547495 100644 --- a/audio/decoders/quicktime.cpp +++ b/audio/decoders/quicktime.cpp @@ -134,7 +134,7 @@ void QuickTimeAudioDecoder::init() { _audioTracks.push_back(new QuickTimeAudioTrack(this, _tracks[i])); } -Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format) { +Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format, uint32 descSize) { if (track->codecType == CODEC_TYPE_AUDIO) { debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format)); diff --git a/audio/decoders/quicktime_intern.h b/audio/decoders/quicktime_intern.h index f1ab037d89..bb5ff0cf5c 100644 --- a/audio/decoders/quicktime_intern.h +++ b/audio/decoders/quicktime_intern.h @@ -131,7 +131,7 @@ protected: }; // Common::QuickTimeParser API - virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format); + virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize); void init(); diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp index da655643a7..d2fe7dc0e8 100644 --- a/audio/fmopl.cpp +++ b/audio/fmopl.cpp @@ -137,7 +137,7 @@ OPL *Config::create(DriverId driver, OplType type) { return new MAME::OPL(); else warning("MAME OPL emulator only supports OPL2 emulation"); - return 0; + return 0; #ifndef DISABLE_DOSBOX_OPL case kDOSBox: diff --git a/audio/fmopl.h b/audio/fmopl.h index 323cc3d028..ad1794d873 100644 --- a/audio/fmopl.h +++ b/audio/fmopl.h @@ -129,7 +129,9 @@ public: /** * Function to directly write to a specific OPL register. - * This writes to *both* chips for a Dual OPL2. + * This writes to *both* chips for a Dual OPL2. We allow + * writing to secondary OPL registers by using register + * values >= 0x100. * * @param r hardware register number to write to * @param v value, which will be written diff --git a/audio/mididrv.h b/audio/mididrv.h index fb3e29bd60..56b4a265cb 100644 --- a/audio/mididrv.h +++ b/audio/mididrv.h @@ -194,7 +194,9 @@ public: enum { // PROP_TIMEDIV = 1, PROP_OLD_ADLIB = 2, - PROP_CHANNEL_MASK = 3 + PROP_CHANNEL_MASK = 3, + // HACK: Not so nice, but our SCUMM AdLib code is in audio/ + PROP_SCUMM_OPL3 = 4 }; /** diff --git a/audio/midiparser.cpp b/audio/midiparser.cpp index eec32c05d1..9c144c2479 100644 --- a/audio/midiparser.cpp +++ b/audio/midiparser.cpp @@ -46,6 +46,7 @@ _numTracks(0), _activeTrack(255), _abortParse(0) { memset(_activeNotes, 0, sizeof(_activeNotes)); + memset(_tracks, 0, sizeof(_tracks)); _nextEvent.start = NULL; _nextEvent.delta = 0; _nextEvent.event = 0; diff --git a/audio/midiparser.h b/audio/midiparser.h index a4dbf174e1..bb9749b97f 100644 --- a/audio/midiparser.h +++ b/audio/midiparser.h @@ -394,6 +394,7 @@ public: static MidiParser *createParser_SMF(); static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0); + static MidiParser *createParser_QT(); static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); } }; diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp new file mode 100644 index 0000000000..6214d28f95 --- /dev/null +++ b/audio/midiparser_qt.cpp @@ -0,0 +1,496 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/midiparser_qt.h" +#include "common/debug.h" +#include "common/memstream.h" + +bool MidiParser_QT::loadMusic(byte *data, uint32 size) { + if (size < 8) + return false; + + Common::SeekableReadStream *stream = new Common::MemoryReadStream(data, size, DisposeAfterUse::NO); + + // Attempt to detect what format we have + bool result; + if (READ_BE_UINT32(data + 4) == MKTAG('m', 'u', 's', 'i')) + result = loadFromTune(stream); + else + result = loadFromContainerStream(stream); + + if (!result) { + delete stream; + return false; + } + + return true; +} + +void MidiParser_QT::unloadMusic() { + MidiParser::unloadMusic(); + close(); + + // Unlike those lesser formats, we *do* hold track data + for (uint i = 0; i < _trackInfo.size(); i++) + free(_trackInfo[i].data); + + _trackInfo.clear(); +} + +bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + unloadMusic(); + + // a tune starts off with a sample description + stream->readUint32BE(); // header size + + if (stream->readUint32BE() != MKTAG('m', 'u', 's', 'i')) + return false; + + stream->readUint32BE(); // reserved + stream->readUint16BE(); // reserved + stream->readUint16BE(); // index + + stream->readUint32BE(); // flags, ignore + + MIDITrackInfo trackInfo; + trackInfo.size = stream->size() - stream->pos(); + assert(trackInfo.size > 0); + + trackInfo.data = (byte *)malloc(trackInfo.size); + stream->read(trackInfo.data, trackInfo.size); + + trackInfo.timeScale = 600; // the default + _trackInfo.push_back(trackInfo); + + initCommon(); + return true; +} + +bool MidiParser_QT::loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + unloadMusic(); + + if (!parseStream(stream, disposeAfterUse)) + return false; + + initFromContainerTracks(); + return true; +} + +bool MidiParser_QT::loadFromContainerFile(const Common::String &fileName) { + unloadMusic(); + + if (!parseFile(fileName)) + return false; + + initFromContainerTracks(); + return true; +} + +void MidiParser_QT::parseNextEvent(EventInfo &info) { + uint32 delta = 0; + + while (_queuedEvents.empty()) + delta += readNextEvent(); + + info = _queuedEvents.pop(); + info.delta = delta; +} + +uint32 MidiParser_QT::readNextEvent() { + if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) { + // Manually insert end of track when we reach the end + EventInfo info; + info.event = 0xFF; + info.ext.type = 0x2F; + _queuedEvents.push(info); + return 0; + } + + uint32 control = readUint32(); + + switch (control >> 28) { + case 0x0: + case 0x1: + // Rest + // We handle this by recursively adding up all the rests into the + // next event's delta + return readNextEvent() + (control & 0xFFFFFF); + case 0x2: + case 0x3: + // Note event + handleNoteEvent((control >> 24) & 0x1F, ((control >> 18) & 0x3F) + 32, (control >> 11) & 0x7F, control & 0x7FF); + break; + case 0x4: + case 0x5: + // Controller + handleControllerEvent((control >> 16) & 0xFF, (control >> 24) & 0x1F, (control >> 8) & 0xFF, control & 0xFF); + break; + case 0x6: + case 0x7: + // Marker + // Used for editing only, so we don't need to care about this + break; + case 0x9: { + // Extended note event + uint32 extra = readUint32(); + handleNoteEvent((control >> 16) & 0xFFF, (control >> 8) & 0xFF, (extra >> 22) & 0x7F, extra & 0x3FFFFF); + break; + } + case 0xA: { + // Extended controller + uint32 extra = readUint32(); + handleControllerEvent((extra >> 16) & 0x3FFF, (control >> 16) & 0xFFF, (extra >> 8) & 0xFF, extra & 0xFF); + break; + } + case 0xB: + // Knob + error("Encountered knob event in QuickTime MIDI"); + break; + case 0x8: + case 0xC: + case 0xD: + case 0xE: + // Reserved + readUint32(); + break; + case 0xF: + // General + handleGeneralEvent(control); + break; + } + + return 0; +} + +void MidiParser_QT::handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length) { + byte channel = getChannel(part); + + EventInfo info; + info.event = 0x90 | channel; + info.basic.param1 = pitch; + info.basic.param2 = velocity; + info.length = (velocity == 0) ? 0 : length; + _queuedEvents.push(info); +} + +void MidiParser_QT::handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart) { + byte channel = getChannel(part); + EventInfo info; + + if (control == 0) { + // "Bank select" + // QuickTime docs don't list this, but IHNM Mac calls this anyway + // We have to ignore this. + return; + } else if (control == 32) { + // Pitch bend + info.event = 0xE0 | channel; + + // Actually an 8.8 fixed point number + int16 value = (int16)((intPart << 8) | fracPart); + + if (value < -0x200 || value > 0x1FF) { + warning("QuickTime MIDI pitch bend value (%d) out of range, clipping", value); + value = CLIP<int16>(value, -0x200, 0x1FF); + } + + // Now convert the value to 'normal' MIDI values + value += 0x200; + value *= 16; + + // param1 holds the low 7 bits, param2 holds the high 7 bits + info.basic.param1 = value & 0x7F; + info.basic.param2 = value >> 7; + + _partMap[part].pitchBend = value; + } else { + // Regular controller + info.event = 0xB0 | channel; + info.basic.param1 = control; + info.basic.param2 = intPart; + + // TODO: Parse more controls to hold their status + switch (control) { + case 7: + _partMap[part].volume = intPart; + break; + case 10: + _partMap[part].pan = intPart; + break; + } + } + + _queuedEvents.push(info); +} + +void MidiParser_QT::handleGeneralEvent(uint32 control) { + uint32 part = (control >> 16) & 0xFFF; + uint32 dataSize = ((control & 0xFFFF) - 2) * 4; + byte subType = READ_BE_UINT16(_position._playPos + dataSize) & 0x3FFF; + + switch (subType) { + case 1: + // Note Request + // Currently we're only using the GM number from the request + assert(dataSize == 84); + + // We have to remap channels because GM needs percussion to be on the + // percussion channel but QuickTime can have that anywhere. + definePart(part, READ_BE_UINT32(_position._playPos + 80)); + break; + case 5: // Tune Difference + case 8: // MIDI Channel + case 10: // No-op + case 11: // Used Notes + // Should be safe to skip these + break; + default: + warning("Unhandled general event %d", subType); + } + + _position._playPos += dataSize + 4; +} + +void MidiParser_QT::definePart(uint32 part, uint32 instrument) { + if (_partMap.contains(part)) + warning("QuickTime MIDI part %d being redefined", part); + + PartStatus partStatus; + partStatus.instrument = instrument; + partStatus.volume = 127; + partStatus.pan = 64; + partStatus.pitchBend = 0x2000; + _partMap[part] = partStatus; +} + +byte MidiParser_QT::getChannel(uint32 part) { + // If we already mapped it, just go with it + if (!_channelMap.contains(part)) { + byte newChannel = findFreeChannel(part); + _channelMap[part] = newChannel; + setupPart(part); + } + + return _channelMap[part]; +} + +byte MidiParser_QT::findFreeChannel(uint32 part) { + if (_partMap[part].instrument != 0x4001) { + // Normal Instrument -> First Free Channel + if (allChannelsAllocated()) + deallocateFreeChannel(); + + for (int i = 0; i < 16; i++) + if (i != 9 && !isChannelAllocated(i)) // 9 is reserved for Percussion + return i; + + // Can't actually get here + } + + // Drum Kit -> Percussion Channel + deallocateChannel(9); + return 9; +} + +void MidiParser_QT::deallocateFreeChannel() { + for (int i = 0; i < 16; i++) { + if (i != 9 && !_activeNotes[i]) { + // TODO: Improve this by looking for the channel with the longest + // time since the last note. + deallocateChannel(i); + return; + } + } + + error("Exceeded QuickTime MIDI channel polyphony"); +} + +void MidiParser_QT::deallocateChannel(byte channel) { + for (ChannelMap::iterator it = _channelMap.begin(); it != _channelMap.end(); it++) { + if (it->_value == channel) { + _channelMap.erase(it); + return; + } + } +} + +bool MidiParser_QT::isChannelAllocated(byte channel) const { + for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++) + if (it->_value == channel) + return true; + + return false; +} + +bool MidiParser_QT::allChannelsAllocated() const { + // Less than 15? We definitely have room + if (_channelMap.size() < 15) + return false; + + // 15? One of the allocated channels might be the percussion one + if (_channelMap.size() == 15) + for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++) + if (it->_value == 9) + return false; + + // 16 -> definitely all allocated + return true; +} + +void MidiParser_QT::setupPart(uint32 part) { + PartStatus &status = _partMap[part]; + byte channel = _channelMap[part]; + EventInfo info; + + // First, the program change + if (channel != 9) { + // 9 is always percussion + info.event = 0xC0 | channel; + info.basic.param1 = status.instrument; + _queuedEvents.push(info); + } + + // Volume + info.event = 0xB0 | channel; + info.basic.param1 = 7; + info.basic.param2 = status.volume; + _queuedEvents.push(info); + + // Pan + info.event = 0xB0 | channel; + info.basic.param1 = 10; + info.basic.param2 = status.pan; + _queuedEvents.push(info); + + // Pitch Bend + info.event = 0xE0 | channel; + info.basic.param1 = status.pitchBend & 0x7F; + info.basic.param2 = status.pitchBend >> 7; + _queuedEvents.push(info); +} + +void MidiParser_QT::resetTracking() { + MidiParser::resetTracking(); + _channelMap.clear(); + _queuedEvents.clear(); + _partMap.clear(); +} + +Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format, uint32 descSize) { + if (track->codecType == CODEC_TYPE_MIDI) { + debug(0, "MIDI Codec FourCC '%s'", tag2str(format)); + + _fd->readUint32BE(); // flags, ignore + descSize -= 4; + + MIDISampleDesc *entry = new MIDISampleDesc(track, format); + entry->_requestSize = descSize; + entry->_requestData = (byte *)malloc(descSize); + _fd->read(entry->_requestData, descSize); + return entry; + } + + return 0; +} + +MidiParser_QT::MIDISampleDesc::MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) : + Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) { +} + +void MidiParser_QT::initFromContainerTracks() { + const Common::Array<Common::QuickTimeParser::Track *> &tracks = Common::QuickTimeParser::_tracks; + + for (uint32 i = 0; i < tracks.size(); i++) { + if (tracks[i]->codecType == CODEC_TYPE_MIDI) { + assert(tracks[i]->sampleDescs.size() == 1); + + if (tracks[i]->editCount != 1) + warning("Unhandled QuickTime MIDI edit lists, things may go awry"); + + MIDITrackInfo trackInfo; + trackInfo.data = readWholeTrack(tracks[i], trackInfo.size); + trackInfo.timeScale = tracks[i]->timeScale; + _trackInfo.push_back(trackInfo); + } + } + + initCommon(); +} + +void MidiParser_QT::initCommon() { + // Now we have all our info needed in _trackInfo from whatever container + // form, we can fill in the MidiParser tracks. + + _numTracks = _trackInfo.size(); + assert(_numTracks > 0); + + for (uint32 i = 0; i < _trackInfo.size(); i++) + MidiParser::_tracks[i] = _trackInfo[i].data; + + _ppqn = _trackInfo[0].timeScale; + resetTracking(); + setTempo(1000000); + setTrack(0); +} + +byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize) { + // This just goes through all chunks and appends them together + + Common::MemoryWriteStreamDynamic output; + uint32 curSample = 0; + + // Read in the note request data first + MIDISampleDesc *entry = (MIDISampleDesc *)track->sampleDescs[0]; + output.write(entry->_requestData, entry->_requestSize); + + for (uint i = 0; i < track->chunkCount; i++) { + _fd->seek(track->chunkOffsets[i]); + + uint32 sampleCount = 0; + + for (uint32 j = 0; j < track->sampleToChunkCount; j++) + if (i >= track->sampleToChunk[j].first) + sampleCount = track->sampleToChunk[j].count; + + for (uint32 j = 0; j < sampleCount; j++, curSample++) { + uint32 size = (track->sampleSize != 0) ? track->sampleSize : track->sampleSizes[curSample]; + + byte *data = new byte[size]; + _fd->read(data, size); + output.write(data, size); + delete[] data; + } + } + + trackSize = output.size(); + return output.getData(); +} + +uint32 MidiParser_QT::readUint32() { + uint32 value = READ_BE_UINT32(_position._playPos); + _position._playPos += 4; + return value; +} + +MidiParser *MidiParser::createParser_QT() { + return new MidiParser_QT(); +} diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h new file mode 100644 index 0000000000..d6d0f40a48 --- /dev/null +++ b/audio/midiparser_qt.h @@ -0,0 +1,134 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AUDIO_MIDIPARSER_QT_H +#define AUDIO_MIDIPARSER_QT_H + +#include "audio/midiparser.h" +#include "common/array.h" +#include "common/hashmap.h" +#include "common/queue.h" +#include "common/quicktime.h" + +/** + * The QuickTime Music version of MidiParser. + * + * QuickTime Music is actually a superset of MIDI. It has its own custom + * instruments and supports more than 15 non-percussion channels. It also + * has custom control changes and a more advanced pitch bend (which we + * convert to GM pitch bend as best as possible). We then use the fallback + * GM instrument that each QuickTime instrument definition has to provide. + * + * Furthermore, Apple's documentation on this is terrible. You know + * documentation is bad when it contradicts itself three times on the same + * subject (like about setting the GM instrument field to percussion). + * + * This is as close to a proper QuickTime Music parser as we can currently + * implement using our MidiParser interface. + */ +class MidiParser_QT : public MidiParser, public Common::QuickTimeParser { +public: + MidiParser_QT() {} + ~MidiParser_QT() {} + + // MidiParser + bool loadMusic(byte *data, uint32 size); + void unloadMusic(); + + /** + * Load the MIDI from a 'Tune' resource + */ + bool loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + + /** + * Load the MIDI from a QuickTime stream + */ + bool loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + + /** + * Load the MIDI from a QuickTime file + */ + bool loadFromContainerFile(const Common::String &fileName); + +protected: + // MidiParser + void parseNextEvent(EventInfo &info); + void resetTracking(); + + // QuickTimeParser + SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize); + +private: + struct MIDITrackInfo { + byte *data; + uint32 size; + uint32 timeScale; + }; + + struct PartStatus { + uint32 instrument; + byte volume; + byte pan; + uint16 pitchBend; + }; + + class MIDISampleDesc : public SampleDesc { + public: + MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); + ~MIDISampleDesc() {} + + byte *_requestData; + uint32 _requestSize; + }; + + uint32 readNextEvent(); + void handleGeneralEvent(uint32 control); + void handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart); + void handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length); + + void definePart(uint32 part, uint32 instrument); + void setupPart(uint32 part); + + byte getChannel(uint32 part); + bool isChannelAllocated(byte channel) const; + byte findFreeChannel(uint32 part); + void deallocateFreeChannel(); + void deallocateChannel(byte channel); + bool allChannelsAllocated() const; + + byte *readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize); + + Common::Array<MIDITrackInfo> _trackInfo; + Common::Queue<EventInfo> _queuedEvents; + + typedef Common::HashMap<uint, PartStatus> PartMap; + PartMap _partMap; + + typedef Common::HashMap<uint, byte> ChannelMap; + ChannelMap _channelMap; + + void initFromContainerTracks(); + void initCommon(); + uint32 readUint32(); +}; + +#endif diff --git a/audio/midiparser_smf.cpp b/audio/midiparser_smf.cpp index 4b0913cbfe..6c64d1e601 100644 --- a/audio/midiparser_smf.cpp +++ b/audio/midiparser_smf.cpp @@ -56,8 +56,10 @@ void MidiParser_SMF::property(int prop, int value) { switch (prop) { case mpMalformedPitchBends: _malformedPitchBends = (value > 0); + break; default: MidiParser::property(prop, value); + break; } } diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp index 11690b0214..fcb45fa5ad 100644 --- a/audio/midiparser_xmidi.cpp +++ b/audio/midiparser_xmidi.cpp @@ -47,8 +47,13 @@ protected: uint32 readVLQ2(byte * &data); void parseNextEvent(EventInfo &info); + virtual void resetTracking() { + MidiParser::resetTracking(); + _loopCount = -1; + } + public: - MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _callbackProc(proc), _callbackData(data) {} + MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _callbackProc(proc), _callbackData(data), _loopCount(-1) {} ~MidiParser_XMIDI() { } bool loadMusic(byte *data, uint32 size); diff --git a/audio/mixer.cpp b/audio/mixer.cpp index 965766170d..8ff364b98d 100644 --- a/audio/mixer.cpp +++ b/audio/mixer.cpp @@ -171,9 +171,9 @@ private: #pragma mark --- Mixer --- #pragma mark - - +// TODO: parameter "system" is unused MixerImpl::MixerImpl(OSystem *system, uint sampleRate) - : _syst(system), _mutex(), _sampleRate(sampleRate), _mixerReady(false), _handleSeed(0), _soundTypeSettings() { + : _mutex(), _sampleRate(sampleRate), _mixerReady(false), _handleSeed(0), _soundTypeSettings() { assert(sampleRate > 0); @@ -491,7 +491,7 @@ Channel::Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *stream, DisposeAfterUse::Flag autofreeStream, bool reverseStereo, int id, bool permanent) : _type(type), _mixer(mixer), _id(id), _permanent(permanent), _volume(Mixer::kMaxChannelVolume), _balance(0), _pauseLevel(0), _samplesConsumed(0), _samplesDecoded(0), _mixerTimeStamp(0), - _pauseStartTime(0), _pauseTime(0), _converter(0), + _pauseStartTime(0), _pauseTime(0), _converter(0), _volL(0), _volR(0), _stream(stream, autofreeStream) { assert(mixer); assert(stream); diff --git a/audio/mixer_intern.h b/audio/mixer_intern.h index c6dfa55ada..fce13a9812 100644 --- a/audio/mixer_intern.h +++ b/audio/mixer_intern.h @@ -54,7 +54,6 @@ private: NUM_CHANNELS = 16 }; - OSystem *_syst; Common::Mutex _mutex; const uint _sampleRate; diff --git a/audio/mods/maxtrax.cpp b/audio/mods/maxtrax.cpp index 8ed51ae5c3..a2d470cdbf 100644 --- a/audio/mods/maxtrax.cpp +++ b/audio/mods/maxtrax.cpp @@ -105,7 +105,7 @@ inline uint32 pow2Fixed(int32 val) { } #endif -} // End of namespace +} // End of anonymous namespace namespace Audio { @@ -211,7 +211,7 @@ void MaxTrax::interrupt() { goto endOfEventLoop; case 0xA0: // SPECIAL - switch (curEvent->stopTime >> 8){ + switch (curEvent->stopTime >> 8) { case 0x01: // SPECIAL_SYNC _playerCtx.syncCallBack(curEvent->stopTime & 0xFF); break; @@ -1032,6 +1032,6 @@ void MaxTrax::outPutEvent(const Event &ev, int num) {} void MaxTrax::outPutScore(const Score &sc, int num) {} #endif // #ifndef NDEBUG -} // End of namespace Audio +} // End of namespace Audio #endif // #if defined(AUDIO_MODS_MAXTRAX_H) diff --git a/audio/mods/maxtrax.h b/audio/mods/maxtrax.h index ffb176c241..8288aef186 100644 --- a/audio/mods/maxtrax.h +++ b/audio/mods/maxtrax.h @@ -214,6 +214,6 @@ private: static void outPutEvent(const Event &ev, int num = -1); static void outPutScore(const Score &sc, int num = -1); }; -} // End of namespace Audio +} // End of namespace Audio #endif // !defined(AUDIO_MODS_MAXTRAX_H) diff --git a/audio/mods/paula.cpp b/audio/mods/paula.cpp index 4b49d6e750..d655428ed0 100644 --- a/audio/mods/paula.cpp +++ b/audio/mods/paula.cpp @@ -132,7 +132,14 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) { Channel &ch = _voice[voice]; int16 *p = buffer; int neededSamples = nSamples; - assert(ch.offset.int_off < ch.length); + + // NOTE: A Protracker (or other module format) player might actually + // push the offset past the sample length in its interrupt(), in which + // case the first mixBuffer() call should not mix anything, and the loop + // should be triggered. + // Thus, doing an assert(ch.offset.int_off < ch.length) here is wrong. + // An example where this happens is a certain Protracker module played + // by the OS/2 version of Hopkins FBI. // Mix the generated samples into the output buffer neededSamples -= mixBuffer<stereo>(p, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning); diff --git a/audio/mods/protracker.cpp b/audio/mods/protracker.cpp index 1e18d5adf8..c947f256e0 100644 --- a/audio/mods/protracker.cpp +++ b/audio/mods/protracker.cpp @@ -90,6 +90,14 @@ private: public: ProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo); + Modules::Module *getModule() { + // Ordinarily, the Module is not meant to be seen outside of + // this class, but occasionally, it's useful to be able to + // manipulate it directly. The Hopkins engine uses this to + // repair a broken song. + return &_module; + } + private: void interrupt(); @@ -462,8 +470,12 @@ void ProtrackerStream::interrupt() { namespace Audio { -AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo) { - return new Modules::ProtrackerStream(stream, offs, rate, stereo); +AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs, int rate, bool stereo, Modules::Module **module) { + Modules::ProtrackerStream *protrackerStream = new Modules::ProtrackerStream(stream, offs, rate, stereo); + if (module) { + *module = protrackerStream->getModule(); + } + return (AudioStream *)protrackerStream; } } // End of namespace Audio diff --git a/audio/mods/protracker.h b/audio/mods/protracker.h index 5f47c4453b..50528fc599 100644 --- a/audio/mods/protracker.h +++ b/audio/mods/protracker.h @@ -26,6 +26,7 @@ * - agos * - parallaction * - gob + * - hopkins */ #ifndef AUDIO_MODS_PROTRACKER_H @@ -35,6 +36,10 @@ namespace Common { class SeekableReadStream; } +namespace Modules { +class Module; +} + namespace Audio { class AudioStream; @@ -48,9 +53,10 @@ class AudioStream; * @param stream the ReadStream from which to read the ProTracker data * @param rate TODO * @param stereo TODO + * @param module can be used to return the Module object (rarely useful) * @return a new AudioStream, or NULL, if an error occurred */ -AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs = 0, int rate = 44100, bool stereo = true); +AudioStream *makeProtrackerStream(Common::SeekableReadStream *stream, int offs = 0, int rate = 44100, bool stereo = true, Modules::Module **module = 0); } // End of namespace Audio diff --git a/audio/mods/tfmx.cpp b/audio/mods/tfmx.cpp index 2957529afc..5829ab5fda 100644 --- a/audio/mods/tfmx.cpp +++ b/audio/mods/tfmx.cpp @@ -1095,7 +1095,7 @@ int Tfmx::doSfx(uint16 sfxIndex, bool unlockChannel) { return -1; } -} // End of namespace Audio +} // End of namespace Audio // some debugging functions #if 0 diff --git a/audio/mods/tfmx.h b/audio/mods/tfmx.h index ebe1172278..a8852d7963 100644 --- a/audio/mods/tfmx.h +++ b/audio/mods/tfmx.h @@ -273,6 +273,6 @@ private: void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3); }; -} // End of namespace Audio +} // End of namespace Audio #endif // !defined(AUDIO_MODS_TFMX_H) diff --git a/audio/module.mk b/audio/module.mk index e3aa0aaa81..4e1c031c83 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -4,6 +4,7 @@ MODULE_OBJS := \ audiostream.o \ fmopl.o \ mididrv.o \ + midiparser_qt.o \ midiparser_smf.o \ midiparser_xmidi.o \ midiparser.o \ diff --git a/audio/softsynth/adlib.cpp b/audio/softsynth/adlib.cpp index 32a5f4a910..0cadea7f22 100644 --- a/audio/softsynth/adlib.cpp +++ b/audio/softsynth/adlib.cpp @@ -32,7 +32,13 @@ #include "common/translation.h" #ifdef DEBUG_ADLIB -static int tick; +static int g_tick; +#endif + +// Only include OPL3 when we actually have an AdLib emulator builtin, which +// supports OPL3. +#ifndef DISABLE_DOSBOX_OPL +#define ENABLE_OPL3 #endif class MidiDriver_ADLIB; @@ -52,21 +58,21 @@ struct InstrumentExtra { } PACKED_STRUCT; struct AdLibInstrument { - byte mod_characteristic; - byte mod_scalingOutputLevel; - byte mod_attackDecay; - byte mod_sustainRelease; - byte mod_waveformSelect; - byte car_characteristic; - byte car_scalingOutputLevel; - byte car_attackDecay; - byte car_sustainRelease; - byte car_waveformSelect; + byte modCharacteristic; + byte modScalingOutputLevel; + byte modAttackDecay; + byte modSustainRelease; + byte modWaveformSelect; + byte carCharacteristic; + byte carScalingOutputLevel; + byte carAttackDecay; + byte carSustainRelease; + byte carWaveformSelect; byte feedback; - byte flags_a; - InstrumentExtra extra_a; - byte flags_b; - InstrumentExtra extra_b; + byte flagsA; + InstrumentExtra extraA; + byte flagsB; + InstrumentExtra extraB; byte duration; } PACKED_STRUCT; #include "common/pack-end.h" @@ -77,16 +83,20 @@ class AdLibPart : public MidiChannel { protected: // AdLibPart *_prev, *_next; AdLibVoice *_voice; - int16 _pitchbend; - byte _pitchbend_factor; - int8 _transpose_eff; - byte _vol_eff; - int8 _detune_eff; - byte _modwheel; + int16 _pitchBend; + byte _pitchBendFactor; + //int8 _transposeEff; + byte _volEff; + int8 _detuneEff; + byte _modWheel; bool _pedal; byte _program; - byte _pri_eff; - AdLibInstrument _part_instr; + byte _priEff; + byte _pan; + AdLibInstrument _partInstr; +#ifdef ENABLE_OPL3 + AdLibInstrument _partInstrSecondary; +#endif protected: MidiDriver_ADLIB *_owner; @@ -99,21 +109,25 @@ protected: public: AdLibPart() { _voice = 0; - _pitchbend = 0; - _pitchbend_factor = 2; - _transpose_eff = 0; - _vol_eff = 0; - _detune_eff = 0; - _modwheel = 0; + _pitchBend = 0; + _pitchBendFactor = 2; + //_transposeEff = 0; + _volEff = 0; + _detuneEff = 0; + _modWheel = 0; _pedal = 0; _program = 0; - _pri_eff = 0; + _priEff = 0; + _pan = 64; _owner = 0; _allocated = false; _channel = 0; - memset(&_part_instr, 0, sizeof(_part_instr)); + memset(&_partInstr, 0, sizeof(_partInstr)); +#ifdef ENABLE_OPL3 + memset(&_partInstrSecondary, 0, sizeof(_partInstrSecondary)); +#endif } MidiDriver *device(); @@ -132,7 +146,7 @@ public: void controlChange(byte control, byte value); void modulationWheel(byte value); void volume(byte value); - void panPosition(byte value) { return; } // Not supported + void panPosition(byte value); void pitchBendFactor(byte value); void detune(byte value); void priority(byte value); @@ -162,7 +176,6 @@ public: void noteOff(byte note); void noteOn(byte note, byte velocity); void programChange(byte program) { } - void pitchBend(int16 bend) { } // Control Change messages void modulationWheel(byte value) { } @@ -181,26 +194,26 @@ private: struct Struct10 { byte active; - int16 cur_val; + int16 curVal; int16 count; - uint16 max_value; - int16 start_value; + uint16 maxValue; + int16 startValue; byte loop; - byte table_a[4]; - byte table_b[4]; + byte tableA[4]; + byte tableB[4]; int8 unk3; - int8 modwheel; - int8 modwheel_last; - uint16 speed_lo_max; - uint16 num_steps; - int16 speed_hi; + int8 modWheel; + int8 modWheelLast; + uint16 speedLoMax; + uint16 numSteps; + int16 speedHi; int8 direction; - uint16 speed_lo; - uint16 speed_lo_counter; + uint16 speedLo; + uint16 speedLoCounter; }; struct Struct11 { - int16 modify_val; + int16 modifyVal; byte param, flag0x40, flag0x10; Struct10 *s10; }; @@ -208,11 +221,11 @@ struct Struct11 { struct AdLibVoice { AdLibPart *_part; AdLibVoice *_next, *_prev; - byte _waitforpedal; + byte _waitForPedal; byte _note; byte _channel; - byte _twochan; - byte _vol_1, _vol_2; + byte _twoChan; + byte _vol1, _vol2; int16 _duration; Struct10 _s10a; @@ -220,26 +233,34 @@ struct AdLibVoice { Struct10 _s10b; Struct11 _s11b; +#ifdef ENABLE_OPL3 + byte _secTwoChan; + byte _secVol1, _secVol2; +#endif + AdLibVoice() { memset(this, 0, sizeof(AdLibVoice)); } }; struct AdLibSetParams { - byte a, b, c, d; + byte registerBase; + byte shift; + byte mask; + byte inversion; }; -static const byte channel_mappings[9] = { +static const byte g_operator1Offsets[9] = { 0, 1, 2, 8, 9, 10, 16, 17, 18 }; -static const byte channel_mappings_2[9] = { +static const byte g_operator2Offsets[9] = { 3, 4, 5, 11, 12, 13, 19, 20, 21 }; -static const AdLibSetParams adlib_setparam_table[] = { +static const AdLibSetParams g_setParamTable[] = { {0x40, 0, 63, 63}, // level {0xE0, 2, 0, 0}, // unused {0x40, 6, 192, 0}, // level key scaling @@ -257,21 +278,21 @@ static const AdLibSetParams adlib_setparam_table[] = { {0xC0, 1, 14, 0} // feedback }; -static const byte param_table_1[16] = { +static const byte g_paramTable1[16] = { 29, 28, 27, 0, 3, 4, 7, 8, 13, 16, 17, 20, 21, 30, 31, 0 }; -static const uint16 maxval_table[16] = { +static const uint16 g_maxValTable[16] = { 0x2FF, 0x1F, 0x7, 0x3F, 0x0F, 0x0F, 0x0F, 0x3, 0x3F, 0x0F, 0x0F, 0x0F, 0x3, 0x3E, 0x1F, 0 }; -static const uint16 num_steps_table[] = { +static const uint16 g_numStepsTable[] = { 1, 2, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, @@ -282,7 +303,7 @@ static const uint16 num_steps_table[] = { 600, 860, 1200, 1600 }; -static const byte note_to_f_num[] = { +static const byte g_noteFrequencies[] = { 90, 91, 92, 92, 93, 94, 94, 95, 96, 96, 97, 98, 98, 99, 100, 101, 101, 102, 103, 104, 104, 105, 106, 107, @@ -303,188 +324,530 @@ static const byte note_to_f_num[] = { 242, 243, 245, 247, 249, 251, 252, 254 }; -static const AdLibInstrument map_gm_to_fm[128] = { +static const AdLibInstrument g_gmInstruments[128] = { // 0x00 -{ 0xC2, 0xC5, 0x2B, 0x99, 0x58, 0xC2, 0x1F, 0x1E, 0xC8, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x23 }, -{ 0x22, 0x53, 0x0E, 0x8A, 0x30, 0x14, 0x06, 0x1D, 0x7A, 0x5C, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x06, 0x00, 0x1C, 0x79, 0x40, 0x02, 0x00, 0x4B, 0x79, 0x58, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC2, 0x89, 0x2A, 0x89, 0x49, 0xC2, 0x16, 0x1C, 0xB8, 0x7C, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x23 }, -{ 0xC2, 0x17, 0x3D, 0x6A, 0x00, 0xC4, 0x2E, 0x2D, 0xC9, 0x20, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x06, 0x1E, 0x1C, 0x99, 0x00, 0x02, 0x3A, 0x4C, 0x79, 0x00, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x84, 0x40, 0x3B, 0x5A, 0x6F, 0x81, 0x0E, 0x3B, 0x5A, 0x7F, 0x0B, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x84, 0x40, 0x3B, 0x5A, 0x63, 0x81, 0x00, 0x3B, 0x5A, 0x7F, 0x01, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x8C, 0x80, 0x05, 0xEA, 0x59, 0x82, 0x0A, 0x3C, 0xAA, 0x64, 0x07, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x85, 0x40, 0x0D, 0xEC, 0x71, 0x84, 0x58, 0x3E, 0xCB, 0x7C, 0x01, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x8A, 0xC0, 0x0C, 0xDC, 0x50, 0x88, 0x58, 0x3D, 0xDA, 0x7C, 0x01, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC9, 0x40, 0x2B, 0x78, 0x42, 0xC2, 0x04, 0x4C, 0x8A, 0x7C, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x1A }, -{ 0x2A, 0x0E, 0x17, 0x89, 0x28, 0x22, 0x0C, 0x1B, 0x09, 0x70, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE7, 0x9B, 0x08, 0x08, 0x26, 0xE2, 0x06, 0x0A, 0x08, 0x70, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC5, 0x05, 0x00, 0xFC, 0x40, 0x84, 0x00, 0x00, 0xDC, 0x50, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x86, 0x40, 0x5D, 0x5A, 0x41, 0x81, 0x00, 0x0B, 0x5A, 0x7F, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, + { 0xC2, 0xC5, 0x2B, 0x99, 0x58, 0xC2, 0x1F, 0x1E, 0xC8, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x23 }, + { 0x22, 0x53, 0x0E, 0x8A, 0x30, 0x14, 0x06, 0x1D, 0x7A, 0x5C, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x06, 0x00, 0x1C, 0x79, 0x40, 0x02, 0x00, 0x4B, 0x79, 0x58, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x89, 0x2A, 0x89, 0x49, 0xC2, 0x16, 0x1C, 0xB8, 0x7C, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x23 }, + { 0xC2, 0x17, 0x3D, 0x6A, 0x00, 0xC4, 0x2E, 0x2D, 0xC9, 0x20, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x06, 0x1E, 0x1C, 0x99, 0x00, 0x02, 0x3A, 0x4C, 0x79, 0x00, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x84, 0x40, 0x3B, 0x5A, 0x6F, 0x81, 0x0E, 0x3B, 0x5A, 0x7F, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x84, 0x40, 0x3B, 0x5A, 0x63, 0x81, 0x00, 0x3B, 0x5A, 0x7F, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x8C, 0x80, 0x05, 0xEA, 0x59, 0x82, 0x0A, 0x3C, 0xAA, 0x64, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x85, 0x40, 0x0D, 0xEC, 0x71, 0x84, 0x58, 0x3E, 0xCB, 0x7C, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x8A, 0xC0, 0x0C, 0xDC, 0x50, 0x88, 0x58, 0x3D, 0xDA, 0x7C, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC9, 0x40, 0x2B, 0x78, 0x42, 0xC2, 0x04, 0x4C, 0x8A, 0x7C, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x1A }, + { 0x2A, 0x0E, 0x17, 0x89, 0x28, 0x22, 0x0C, 0x1B, 0x09, 0x70, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE7, 0x9B, 0x08, 0x08, 0x26, 0xE2, 0x06, 0x0A, 0x08, 0x70, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC5, 0x05, 0x00, 0xFC, 0x40, 0x84, 0x00, 0x00, 0xDC, 0x50, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x86, 0x40, 0x5D, 0x5A, 0x41, 0x81, 0x00, 0x0B, 0x5A, 0x7F, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, // 0x10 -{ 0xED, 0x00, 0x7B, 0xC8, 0x40, 0xE1, 0x99, 0x4A, 0xE9, 0x7E, 0x07, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE8, 0x4F, 0x3A, 0xD7, 0x7C, 0xE2, 0x97, 0x49, 0xF9, 0x7D, 0x05, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE1, 0x10, 0x2F, 0xF7, 0x7D, 0xF3, 0x45, 0x8F, 0xC7, 0x62, 0x07, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x01, 0x8C, 0x9F, 0xDA, 0x70, 0xE4, 0x50, 0x9F, 0xDA, 0x6A, 0x09, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x08, 0xD5, 0x9D, 0xA5, 0x45, 0xE2, 0x3F, 0x9F, 0xD6, 0x49, 0x07, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE5, 0x0F, 0x7D, 0xB8, 0x2E, 0xA2, 0x0F, 0x7C, 0xC7, 0x61, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xF2, 0x2A, 0x9F, 0xDB, 0x01, 0xE1, 0x04, 0x8F, 0xD7, 0x62, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x88, 0x9C, 0x50, 0x64, 0xE2, 0x18, 0x70, 0xC4, 0x7C, 0x0B, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x02, 0xA3, 0x0D, 0xDA, 0x01, 0xC2, 0x35, 0x5D, 0x58, 0x00, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x18 }, -{ 0x42, 0x55, 0x3E, 0xEB, 0x24, 0xD4, 0x08, 0x0D, 0xA9, 0x71, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x18 }, -{ 0xC2, 0x00, 0x2B, 0x17, 0x51, 0xC2, 0x1E, 0x4D, 0x97, 0x7C, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x19 }, -{ 0xC6, 0x01, 0x2D, 0xA7, 0x44, 0xC2, 0x06, 0x0E, 0xA7, 0x79, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC2, 0x0C, 0x06, 0x06, 0x55, 0xC2, 0x3F, 0x09, 0x86, 0x7D, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x0A }, -{ 0xC2, 0x2E, 0x4F, 0x77, 0x00, 0xC4, 0x08, 0x0E, 0x98, 0x59, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC2, 0x30, 0x4F, 0xCA, 0x01, 0xC4, 0x0D, 0x0E, 0xB8, 0x7F, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC4, 0x29, 0x4F, 0xCA, 0x03, 0xC8, 0x0D, 0x0C, 0xB7, 0x7D, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x0B }, + { 0xED, 0x00, 0x7B, 0xC8, 0x40, 0xE1, 0x99, 0x4A, 0xE9, 0x7E, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE8, 0x4F, 0x3A, 0xD7, 0x7C, 0xE2, 0x97, 0x49, 0xF9, 0x7D, 0x05, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x10, 0x2F, 0xF7, 0x7D, 0xF3, 0x45, 0x8F, 0xC7, 0x62, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x01, 0x8C, 0x9F, 0xDA, 0x70, 0xE4, 0x50, 0x9F, 0xDA, 0x6A, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x08, 0xD5, 0x9D, 0xA5, 0x45, 0xE2, 0x3F, 0x9F, 0xD6, 0x49, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE5, 0x0F, 0x7D, 0xB8, 0x2E, 0xA2, 0x0F, 0x7C, 0xC7, 0x61, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x2A, 0x9F, 0xDB, 0x01, 0xE1, 0x04, 0x8F, 0xD7, 0x62, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x88, 0x9C, 0x50, 0x64, 0xE2, 0x18, 0x70, 0xC4, 0x7C, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x02, 0xA3, 0x0D, 0xDA, 0x01, 0xC2, 0x35, 0x5D, 0x58, 0x00, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x18 }, + { 0x42, 0x55, 0x3E, 0xEB, 0x24, 0xD4, 0x08, 0x0D, 0xA9, 0x71, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x18 }, + { 0xC2, 0x00, 0x2B, 0x17, 0x51, 0xC2, 0x1E, 0x4D, 0x97, 0x7C, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x19 }, + { 0xC6, 0x01, 0x2D, 0xA7, 0x44, 0xC2, 0x06, 0x0E, 0xA7, 0x79, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x0C, 0x06, 0x06, 0x55, 0xC2, 0x3F, 0x09, 0x86, 0x7D, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0A }, + { 0xC2, 0x2E, 0x4F, 0x77, 0x00, 0xC4, 0x08, 0x0E, 0x98, 0x59, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x30, 0x4F, 0xCA, 0x01, 0xC4, 0x0D, 0x0E, 0xB8, 0x7F, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC4, 0x29, 0x4F, 0xCA, 0x03, 0xC8, 0x0D, 0x0C, 0xB7, 0x7D, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0B }, // 0x20 -{ 0xC2, 0x40, 0x3C, 0x96, 0x58, 0xC4, 0xDE, 0x0E, 0xC7, 0x7C, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x20 }, -{ 0x31, 0x13, 0x2D, 0xD7, 0x3C, 0xE2, 0x18, 0x2E, 0xB8, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x22, 0x86, 0x0D, 0xD7, 0x50, 0xE4, 0x18, 0x5E, 0xB8, 0x7C, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x28 }, -{ 0xF2, 0x0A, 0x0D, 0xD7, 0x40, 0xE4, 0x1F, 0x5E, 0xB8, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xF2, 0x09, 0x4B, 0xD6, 0x48, 0xE4, 0x1F, 0x1C, 0xB8, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x28 }, -{ 0x62, 0x11, 0x0C, 0xE6, 0x3C, 0xE4, 0x1F, 0x0C, 0xC8, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x12, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x7D, 0xB8, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x13, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x5D, 0xB8, 0x7D, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xA2, 0x40, 0x5D, 0xBA, 0x3F, 0xE2, 0x00, 0x8F, 0xD8, 0x79, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x40, 0x3D, 0xDA, 0x3B, 0xE1, 0x00, 0x7E, 0xD8, 0x7A, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x62, 0x00, 0x6D, 0xFA, 0x5D, 0xE2, 0x00, 0x8F, 0xC8, 0x79, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE1, 0x00, 0x4E, 0xDB, 0x4A, 0xE3, 0x18, 0x6F, 0xE9, 0x7E, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE1, 0x00, 0x4E, 0xDB, 0x66, 0xE2, 0x00, 0x7F, 0xE9, 0x7E, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x02, 0x0F, 0x66, 0xAA, 0x51, 0x02, 0x64, 0x29, 0xF9, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x04 }, -{ 0x16, 0x4A, 0x04, 0xBA, 0x39, 0xC2, 0x58, 0x2D, 0xCA, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0x02, 0x00, 0x01, 0x7A, 0x79, 0x02, 0x3F, 0x28, 0xEA, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, + { 0xC2, 0x40, 0x3C, 0x96, 0x58, 0xC4, 0xDE, 0x0E, 0xC7, 0x7C, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x20 }, + { 0x31, 0x13, 0x2D, 0xD7, 0x3C, 0xE2, 0x18, 0x2E, 0xB8, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x22, 0x86, 0x0D, 0xD7, 0x50, 0xE4, 0x18, 0x5E, 0xB8, 0x7C, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x28 }, + { 0xF2, 0x0A, 0x0D, 0xD7, 0x40, 0xE4, 0x1F, 0x5E, 0xB8, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x09, 0x4B, 0xD6, 0x48, 0xE4, 0x1F, 0x1C, 0xB8, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x28 }, + { 0x62, 0x11, 0x0C, 0xE6, 0x3C, 0xE4, 0x1F, 0x0C, 0xC8, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x12, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x7D, 0xB8, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x13, 0x3D, 0xE6, 0x34, 0xE4, 0x1F, 0x5D, 0xB8, 0x7D, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xA2, 0x40, 0x5D, 0xBA, 0x3F, 0xE2, 0x00, 0x8F, 0xD8, 0x79, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x40, 0x3D, 0xDA, 0x3B, 0xE1, 0x00, 0x7E, 0xD8, 0x7A, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x00, 0x6D, 0xFA, 0x5D, 0xE2, 0x00, 0x8F, 0xC8, 0x79, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0x4E, 0xDB, 0x4A, 0xE3, 0x18, 0x6F, 0xE9, 0x7E, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0x4E, 0xDB, 0x66, 0xE2, 0x00, 0x7F, 0xE9, 0x7E, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x02, 0x0F, 0x66, 0xAA, 0x51, 0x02, 0x64, 0x29, 0xF9, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x16, 0x4A, 0x04, 0xBA, 0x39, 0xC2, 0x58, 0x2D, 0xCA, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x02, 0x00, 0x01, 0x7A, 0x79, 0x02, 0x3F, 0x28, 0xEA, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, // 0x30 -{ 0x62, 0x53, 0x9C, 0xBA, 0x31, 0x62, 0x5B, 0xAD, 0xC9, 0x55, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xF2, 0x40, 0x6E, 0xDA, 0x49, 0xE2, 0x13, 0x8F, 0xF9, 0x7D, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x40, 0x8F, 0xFA, 0x50, 0xF2, 0x04, 0x7F, 0xFA, 0x7D, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0xA0, 0xCE, 0x5B, 0x02, 0xE2, 0x32, 0x7F, 0xFB, 0x3D, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE6, 0x80, 0x9C, 0x99, 0x42, 0xE2, 0x04, 0x7D, 0x78, 0x60, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xEA, 0xA0, 0xAC, 0x67, 0x02, 0xE2, 0x00, 0x7C, 0x7A, 0x7C, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE7, 0x94, 0xAD, 0xB7, 0x03, 0xE2, 0x00, 0x7C, 0xBA, 0x7C, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC3, 0x3F, 0x4B, 0xE9, 0x7E, 0xC1, 0x3F, 0x9B, 0xF9, 0x7F, 0x0B, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x06 }, -{ 0xB2, 0x20, 0xAD, 0xE9, 0x00, 0x62, 0x05, 0x8F, 0xC8, 0x68, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xF2, 0x00, 0x8F, 0xFB, 0x50, 0xF6, 0x47, 0x8F, 0xE9, 0x68, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xF2, 0x00, 0xAF, 0x88, 0x58, 0xF2, 0x54, 0x6E, 0xC9, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xF2, 0x2A, 0x9F, 0x98, 0x01, 0xE2, 0x84, 0x4E, 0x78, 0x6C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x02, 0x9F, 0xB8, 0x48, 0x22, 0x89, 0x9F, 0xE8, 0x7C, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x2A, 0x7F, 0xB8, 0x01, 0xE4, 0x00, 0x0D, 0xC5, 0x7C, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x28, 0x8E, 0xE8, 0x01, 0xF2, 0x00, 0x4D, 0xD6, 0x7D, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x62, 0x23, 0x8F, 0xEA, 0x00, 0xF2, 0x00, 0x5E, 0xD9, 0x7C, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, + { 0x62, 0x53, 0x9C, 0xBA, 0x31, 0x62, 0x5B, 0xAD, 0xC9, 0x55, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x40, 0x6E, 0xDA, 0x49, 0xE2, 0x13, 0x8F, 0xF9, 0x7D, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x40, 0x8F, 0xFA, 0x50, 0xF2, 0x04, 0x7F, 0xFA, 0x7D, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0xA0, 0xCE, 0x5B, 0x02, 0xE2, 0x32, 0x7F, 0xFB, 0x3D, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE6, 0x80, 0x9C, 0x99, 0x42, 0xE2, 0x04, 0x7D, 0x78, 0x60, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEA, 0xA0, 0xAC, 0x67, 0x02, 0xE2, 0x00, 0x7C, 0x7A, 0x7C, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE7, 0x94, 0xAD, 0xB7, 0x03, 0xE2, 0x00, 0x7C, 0xBA, 0x7C, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC3, 0x3F, 0x4B, 0xE9, 0x7E, 0xC1, 0x3F, 0x9B, 0xF9, 0x7F, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 }, + { 0xB2, 0x20, 0xAD, 0xE9, 0x00, 0x62, 0x05, 0x8F, 0xC8, 0x68, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x00, 0x8F, 0xFB, 0x50, 0xF6, 0x47, 0x8F, 0xE9, 0x68, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x00, 0xAF, 0x88, 0x58, 0xF2, 0x54, 0x6E, 0xC9, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x2A, 0x9F, 0x98, 0x01, 0xE2, 0x84, 0x4E, 0x78, 0x6C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x02, 0x9F, 0xB8, 0x48, 0x22, 0x89, 0x9F, 0xE8, 0x7C, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x2A, 0x7F, 0xB8, 0x01, 0xE4, 0x00, 0x0D, 0xC5, 0x7C, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x28, 0x8E, 0xE8, 0x01, 0xF2, 0x00, 0x4D, 0xD6, 0x7D, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x23, 0x8F, 0xEA, 0x00, 0xF2, 0x00, 0x5E, 0xD9, 0x7C, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, // 0x40 -{ 0xB4, 0x26, 0x6E, 0x98, 0x01, 0x62, 0x00, 0x7D, 0xC8, 0x7D, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x2E, 0x20, 0xD9, 0x01, 0xF2, 0x0F, 0x90, 0xF8, 0x78, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x28, 0x7E, 0xF8, 0x01, 0xE2, 0x23, 0x8E, 0xE8, 0x7D, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xB8, 0x28, 0x9E, 0x98, 0x01, 0x62, 0x00, 0x3D, 0xC8, 0x7D, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x62, 0x00, 0x8E, 0xC9, 0x3D, 0xE6, 0x00, 0x7E, 0xD8, 0x68, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x00, 0x5F, 0xF9, 0x48, 0xE6, 0x98, 0x8F, 0xF8, 0x7D, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x62, 0x0C, 0x6E, 0xD8, 0x3D, 0x2A, 0x06, 0x7D, 0xD8, 0x58, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x00, 0x7E, 0x89, 0x38, 0xE6, 0x84, 0x80, 0xF8, 0x68, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x80, 0x6C, 0xD9, 0x30, 0xE2, 0x00, 0x8D, 0xC8, 0x7C, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x80, 0x88, 0x48, 0x40, 0xE2, 0x0A, 0x7D, 0xA8, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x00, 0x77, 0xC5, 0x54, 0xE2, 0x00, 0x9E, 0xD7, 0x70, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x80, 0x86, 0xB9, 0x64, 0xE2, 0x05, 0x9F, 0xD7, 0x78, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x00, 0x68, 0x68, 0x56, 0xE2, 0x08, 0x9B, 0xB3, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x00, 0xA6, 0x87, 0x41, 0xE2, 0x0A, 0x7E, 0xC9, 0x7C, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x80, 0x9A, 0xB8, 0x48, 0xE2, 0x00, 0x9E, 0xF9, 0x60, 0x09, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x80, 0x8E, 0x64, 0x68, 0xE2, 0x28, 0x6F, 0x73, 0x7C, 0x01, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, + { 0xB4, 0x26, 0x6E, 0x98, 0x01, 0x62, 0x00, 0x7D, 0xC8, 0x7D, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x2E, 0x20, 0xD9, 0x01, 0xF2, 0x0F, 0x90, 0xF8, 0x78, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x28, 0x7E, 0xF8, 0x01, 0xE2, 0x23, 0x8E, 0xE8, 0x7D, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xB8, 0x28, 0x9E, 0x98, 0x01, 0x62, 0x00, 0x3D, 0xC8, 0x7D, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x00, 0x8E, 0xC9, 0x3D, 0xE6, 0x00, 0x7E, 0xD8, 0x68, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x00, 0x5F, 0xF9, 0x48, 0xE6, 0x98, 0x8F, 0xF8, 0x7D, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x0C, 0x6E, 0xD8, 0x3D, 0x2A, 0x06, 0x7D, 0xD8, 0x58, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x00, 0x7E, 0x89, 0x38, 0xE6, 0x84, 0x80, 0xF8, 0x68, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x80, 0x6C, 0xD9, 0x30, 0xE2, 0x00, 0x8D, 0xC8, 0x7C, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x80, 0x88, 0x48, 0x40, 0xE2, 0x0A, 0x7D, 0xA8, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x00, 0x77, 0xC5, 0x54, 0xE2, 0x00, 0x9E, 0xD7, 0x70, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x80, 0x86, 0xB9, 0x64, 0xE2, 0x05, 0x9F, 0xD7, 0x78, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x00, 0x68, 0x68, 0x56, 0xE2, 0x08, 0x9B, 0xB3, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x00, 0xA6, 0x87, 0x41, 0xE2, 0x0A, 0x7E, 0xC9, 0x7C, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x80, 0x9A, 0xB8, 0x48, 0xE2, 0x00, 0x9E, 0xF9, 0x60, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x80, 0x8E, 0x64, 0x68, 0xE2, 0x28, 0x6F, 0x73, 0x7C, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, // 0x50 -{ 0xE8, 0x00, 0x7D, 0x99, 0x54, 0xE6, 0x80, 0x80, 0xF8, 0x7C, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE6, 0x00, 0x9F, 0xB9, 0x6D, 0xE1, 0x00, 0x8F, 0xC8, 0x7D, 0x02, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x00, 0x09, 0x68, 0x4A, 0xE2, 0x2B, 0x9E, 0xF3, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC4, 0x00, 0x99, 0xE8, 0x3B, 0xE2, 0x25, 0x6F, 0x93, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE6, 0x00, 0x6F, 0xDA, 0x69, 0xE2, 0x05, 0x2F, 0xD8, 0x6A, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xEC, 0x60, 0x9D, 0xC7, 0x00, 0xE2, 0x21, 0x7F, 0xC9, 0x7C, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE3, 0x00, 0x0F, 0xF7, 0x7D, 0xE1, 0x3F, 0x0F, 0xA7, 0x01, 0x0D, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0xA9, 0x0F, 0xA8, 0x02, 0xE2, 0x3C, 0x5F, 0xDA, 0x3C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE8, 0x40, 0x0D, 0x89, 0x7D, 0xE2, 0x17, 0x7E, 0xD9, 0x7C, 0x07, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE1, 0x00, 0xDF, 0x8A, 0x56, 0xE2, 0x5E, 0xCF, 0xBA, 0x7E, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE2, 0x00, 0x0B, 0x68, 0x60, 0xE2, 0x01, 0x9E, 0xB8, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xEA, 0x00, 0xAE, 0xAB, 0x49, 0xE2, 0x00, 0xAE, 0xBA, 0x6C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xEB, 0x80, 0x8C, 0xCB, 0x3A, 0xE2, 0x86, 0xAF, 0xCA, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE5, 0x40, 0xDB, 0x3B, 0x3C, 0xE2, 0x80, 0xBE, 0xCA, 0x71, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE4, 0x00, 0x9E, 0xAA, 0x3D, 0xE1, 0x43, 0x0F, 0xBA, 0x7E, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE7, 0x40, 0xEC, 0xCA, 0x44, 0xE2, 0x03, 0xBF, 0xBA, 0x66, 0x02, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, + { 0xE8, 0x00, 0x7D, 0x99, 0x54, 0xE6, 0x80, 0x80, 0xF8, 0x7C, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE6, 0x00, 0x9F, 0xB9, 0x6D, 0xE1, 0x00, 0x8F, 0xC8, 0x7D, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x00, 0x09, 0x68, 0x4A, 0xE2, 0x2B, 0x9E, 0xF3, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC4, 0x00, 0x99, 0xE8, 0x3B, 0xE2, 0x25, 0x6F, 0x93, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE6, 0x00, 0x6F, 0xDA, 0x69, 0xE2, 0x05, 0x2F, 0xD8, 0x6A, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEC, 0x60, 0x9D, 0xC7, 0x00, 0xE2, 0x21, 0x7F, 0xC9, 0x7C, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE3, 0x00, 0x0F, 0xF7, 0x7D, 0xE1, 0x3F, 0x0F, 0xA7, 0x01, 0x0D, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0xA9, 0x0F, 0xA8, 0x02, 0xE2, 0x3C, 0x5F, 0xDA, 0x3C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE8, 0x40, 0x0D, 0x89, 0x7D, 0xE2, 0x17, 0x7E, 0xD9, 0x7C, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0xDF, 0x8A, 0x56, 0xE2, 0x5E, 0xCF, 0xBA, 0x7E, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x00, 0x0B, 0x68, 0x60, 0xE2, 0x01, 0x9E, 0xB8, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEA, 0x00, 0xAE, 0xAB, 0x49, 0xE2, 0x00, 0xAE, 0xBA, 0x6C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEB, 0x80, 0x8C, 0xCB, 0x3A, 0xE2, 0x86, 0xAF, 0xCA, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE5, 0x40, 0xDB, 0x3B, 0x3C, 0xE2, 0x80, 0xBE, 0xCA, 0x71, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x00, 0x9E, 0xAA, 0x3D, 0xE1, 0x43, 0x0F, 0xBA, 0x7E, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE7, 0x40, 0xEC, 0xCA, 0x44, 0xE2, 0x03, 0xBF, 0xBA, 0x66, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, // 0x60 -{ 0xEA, 0x00, 0x68, 0xB8, 0x48, 0xE2, 0x0A, 0x8E, 0xB8, 0x7C, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x61, 0x00, 0xBE, 0x99, 0x7E, 0xE3, 0x40, 0xCF, 0xCA, 0x7D, 0x09, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xCD, 0x00, 0x0B, 0x00, 0x48, 0xC2, 0x58, 0x0C, 0x00, 0x7C, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x1C }, -{ 0xE2, 0x00, 0x0E, 0x00, 0x52, 0xE2, 0x58, 0x5F, 0xD0, 0x7D, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xCC, 0x00, 0x7D, 0xDA, 0x40, 0xC2, 0x00, 0x5E, 0x9B, 0x58, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE9, 0xC0, 0xEE, 0xD8, 0x43, 0xE2, 0x05, 0xDD, 0xAA, 0x70, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xDA, 0x00, 0x8F, 0xAC, 0x4A, 0x22, 0x05, 0x8D, 0x8A, 0x75, 0x02, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x62, 0x8A, 0xCB, 0x7A, 0x74, 0xE6, 0x56, 0xAF, 0xDB, 0x70, 0x02, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xC2, 0x41, 0xAC, 0x5B, 0x5B, 0xC2, 0x80, 0x0D, 0xCB, 0x7D, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x12 }, -{ 0x75, 0x00, 0x0E, 0xCB, 0x5A, 0xE2, 0x1E, 0x0A, 0xC9, 0x7D, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x10 }, -{ 0x41, 0x00, 0x0E, 0xEA, 0x53, 0xC2, 0x00, 0x08, 0xCA, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x07 }, -{ 0xC1, 0x40, 0x0C, 0x59, 0x6A, 0xC2, 0x80, 0x3C, 0xAB, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x0D }, -{ 0x4B, 0x00, 0x0A, 0xF5, 0x61, 0xC2, 0x19, 0x0C, 0xE9, 0x7C, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x07 }, -{ 0x62, 0x00, 0x7F, 0xD8, 0x54, 0xEA, 0x00, 0x8F, 0xD8, 0x7D, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, + { 0xEA, 0x00, 0x68, 0xB8, 0x48, 0xE2, 0x0A, 0x8E, 0xB8, 0x7C, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x61, 0x00, 0xBE, 0x99, 0x7E, 0xE3, 0x40, 0xCF, 0xCA, 0x7D, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xCD, 0x00, 0x0B, 0x00, 0x48, 0xC2, 0x58, 0x0C, 0x00, 0x7C, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x1C }, + { 0xE2, 0x00, 0x0E, 0x00, 0x52, 0xE2, 0x58, 0x5F, 0xD0, 0x7D, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xCC, 0x00, 0x7D, 0xDA, 0x40, 0xC2, 0x00, 0x5E, 0x9B, 0x58, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE9, 0xC0, 0xEE, 0xD8, 0x43, 0xE2, 0x05, 0xDD, 0xAA, 0x70, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xDA, 0x00, 0x8F, 0xAC, 0x4A, 0x22, 0x05, 0x8D, 0x8A, 0x75, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x8A, 0xCB, 0x7A, 0x74, 0xE6, 0x56, 0xAF, 0xDB, 0x70, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x41, 0xAC, 0x5B, 0x5B, 0xC2, 0x80, 0x0D, 0xCB, 0x7D, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x12 }, + { 0x75, 0x00, 0x0E, 0xCB, 0x5A, 0xE2, 0x1E, 0x0A, 0xC9, 0x7D, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 }, + { 0x41, 0x00, 0x0E, 0xEA, 0x53, 0xC2, 0x00, 0x08, 0xCA, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 }, + { 0xC1, 0x40, 0x0C, 0x59, 0x6A, 0xC2, 0x80, 0x3C, 0xAB, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0D }, + { 0x4B, 0x00, 0x0A, 0xF5, 0x61, 0xC2, 0x19, 0x0C, 0xE9, 0x7C, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 }, + { 0x62, 0x00, 0x7F, 0xD8, 0x54, 0xEA, 0x00, 0x8F, 0xD8, 0x7D, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0x7F, 0xD9, 0x56, 0xE1, 0x00, 0x8F, 0xD8, 0x7E, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, // 0x70 -{ 0xCF, 0x40, 0x09, 0xEA, 0x54, 0xC4, 0x00, 0x0C, 0xDB, 0x64, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0xCF, 0x40, 0x0C, 0xAA, 0x54, 0xC4, 0x00, 0x18, 0xF9, 0x64, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0xC9, 0x0E, 0x88, 0xD9, 0x3E, 0xC2, 0x08, 0x1A, 0xEA, 0x6C, 0x0C, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x05 }, -{ 0x03, 0x00, 0x15, 0x00, 0x64, 0x02, 0x00, 0x08, 0x00, 0x7C, 0x09, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x01, 0x00, 0x47, 0xD7, 0x6C, 0x01, 0x3F, 0x0C, 0xFB, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x04 }, -{ 0x00, 0x00, 0x36, 0x67, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x05 }, -{ 0x02, 0x00, 0x36, 0x68, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x05 }, -{ 0xCB, 0x00, 0xAF, 0x00, 0x7E, 0xC0, 0x00, 0xC0, 0x06, 0x7F, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x0F }, -{ 0x05, 0x0D, 0x80, 0xA6, 0x7F, 0x0B, 0x38, 0xA9, 0xD8, 0x00, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x04 }, -{ 0x0F, 0x00, 0x90, 0xFA, 0x68, 0x06, 0x00, 0xA7, 0x39, 0x54, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x06 }, -{ 0xC9, 0x15, 0xDD, 0xFF, 0x7C, 0x00, 0x00, 0xE7, 0xFC, 0x6C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x38 }, -{ 0x48, 0x3C, 0x30, 0xF6, 0x03, 0x0A, 0x38, 0x97, 0xE8, 0x00, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x04 }, -{ 0x07, 0x80, 0x0B, 0xC8, 0x65, 0x02, 0x3F, 0x0C, 0xEA, 0x7C, 0x0F, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x05 }, -{ 0x00, 0x21, 0x66, 0x40, 0x03, 0x00, 0x3F, 0x47, 0x00, 0x00, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x08, 0x00, 0x0B, 0x3C, 0x7C, 0x08, 0x3F, 0x06, 0xF3, 0x00, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x00, 0x3F, 0x4C, 0xFB, 0x00, 0x00, 0x3F, 0x0A, 0xE9, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x05 } + { 0xCF, 0x40, 0x09, 0xEA, 0x54, 0xC4, 0x00, 0x0C, 0xDB, 0x64, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x40, 0x0C, 0xAA, 0x54, 0xC4, 0x00, 0x18, 0xF9, 0x64, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xC9, 0x0E, 0x88, 0xD9, 0x3E, 0xC2, 0x08, 0x1A, 0xEA, 0x6C, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x03, 0x00, 0x15, 0x00, 0x64, 0x02, 0x00, 0x08, 0x00, 0x7C, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x01, 0x00, 0x47, 0xD7, 0x6C, 0x01, 0x3F, 0x0C, 0xFB, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x00, 0x00, 0x36, 0x67, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x02, 0x00, 0x36, 0x68, 0x7C, 0x01, 0x3F, 0x0E, 0xFA, 0x7C, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0xCB, 0x00, 0xAF, 0x00, 0x7E, 0xC0, 0x00, 0xC0, 0x06, 0x7F, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0F }, + { 0x05, 0x0D, 0x80, 0xA6, 0x7F, 0x0B, 0x38, 0xA9, 0xD8, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x0F, 0x00, 0x90, 0xFA, 0x68, 0x06, 0x00, 0xA7, 0x39, 0x54, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 }, + { 0xC9, 0x15, 0xDD, 0xFF, 0x7C, 0x00, 0x00, 0xE7, 0xFC, 0x6C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x38 }, + { 0x48, 0x3C, 0x30, 0xF6, 0x03, 0x0A, 0x38, 0x97, 0xE8, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x07, 0x80, 0x0B, 0xC8, 0x65, 0x02, 0x3F, 0x0C, 0xEA, 0x7C, 0x0F, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x00, 0x21, 0x66, 0x40, 0x03, 0x00, 0x3F, 0x47, 0x00, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x08, 0x00, 0x0B, 0x3C, 0x7C, 0x08, 0x3F, 0x06, 0xF3, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x00, 0x3F, 0x4C, 0xFB, 0x00, 0x00, 0x3F, 0x0A, 0xE9, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } }; -static AdLibInstrument gm_percussion_to_fm[39] = { -{ 0x1A, 0x3F, 0x15, 0x05, 0x7C, 0x02, 0x21, 0x2B, 0xE4, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x06 }, -{ 0x11, 0x12, 0x04, 0x07, 0x7C, 0x02, 0x23, 0x0B, 0xE5, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x05 }, -{ 0x0A, 0x3F, 0x0B, 0x01, 0x7C, 0x1F, 0x1C, 0x46, 0xD0, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x01 }, -{ 0x00, 0x3F, 0x0F, 0x00, 0x7C, 0x10, 0x12, 0x07, 0x00, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x0F, 0x3F, 0x0B, 0x00, 0x7C, 0x1F, 0x0F, 0x19, 0xD0, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x00, 0x3F, 0x1F, 0x00, 0x7E, 0x1F, 0x16, 0x07, 0x00, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x1F, 0x4A, 0xD9, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0xCF, 0x7F, 0x08, 0xFF, 0x7E, 0x00, 0xC7, 0x2D, 0xF7, 0x73, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x43, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0xCF, 0x7F, 0x08, 0xCF, 0x7E, 0x00, 0x45, 0x2A, 0xF8, 0x4B, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x0C }, -{ 0x12, 0x3F, 0x06, 0x17, 0x7C, 0x03, 0x27, 0x0B, 0xE9, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0xCF, 0x7F, 0x08, 0xCD, 0x7E, 0x00, 0x40, 0x1A, 0x69, 0x63, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x0C }, -{ 0x13, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x17, 0x0A, 0xD9, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0x15, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0xCF, 0x3F, 0x2B, 0xFB, 0x7E, 0xC0, 0x1E, 0x1A, 0xCA, 0x7F, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x10 }, -{ 0x17, 0x3F, 0x04, 0x09, 0x7C, 0x03, 0x22, 0x0D, 0xE9, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0xCF, 0x3F, 0x0F, 0x5E, 0x7C, 0xC6, 0x13, 0x00, 0xCA, 0x7F, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0xCF, 0x3F, 0x7E, 0x9D, 0x7C, 0xC8, 0xC0, 0x0A, 0xBA, 0x74, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x06 }, -{ 0xCF, 0x3F, 0x4D, 0x9F, 0x7C, 0xC6, 0x00, 0x08, 0xDA, 0x5B, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x04 }, -{ 0xCF, 0x3F, 0x5D, 0xAA, 0x7A, 0xC0, 0xA4, 0x67, 0x99, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0xCF, 0x3F, 0x4A, 0xFD, 0x7C, 0xCF, 0x00, 0x59, 0xEA, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x0F, 0x18, 0x0A, 0xFA, 0x57, 0x06, 0x07, 0x06, 0x39, 0x7C, 0x0A, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0xCF, 0x3F, 0x2B, 0xFC, 0x7C, 0xCC, 0xC6, 0x0B, 0xEA, 0x7F, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x10 }, -{ 0x05, 0x1A, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x0C, 0xEA, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x07 }, -{ 0x04, 0x19, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x2C, 0xEA, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x04 }, -{ 0x04, 0x0A, 0x04, 0x00, 0x6C, 0x01, 0x07, 0x0D, 0xFA, 0x74, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x07 }, -{ 0x15, 0x14, 0x05, 0x00, 0x7D, 0x01, 0x07, 0x5C, 0xE9, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x05 }, -{ 0x10, 0x10, 0x05, 0x08, 0x7C, 0x01, 0x08, 0x0D, 0xEA, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x05 }, -{ 0x11, 0x00, 0x06, 0x87, 0x7F, 0x02, 0x40, 0x09, 0x59, 0x68, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x08 }, -{ 0x13, 0x26, 0x04, 0x6A, 0x7F, 0x01, 0x00, 0x08, 0x5A, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x08 }, -{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC4, 0x00, 0x18, 0xF9, 0x54, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC3, 0x00, 0x18, 0xF8, 0x54, 0x04, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0xCB, 0x3F, 0x8F, 0x00, 0x7E, 0xC5, 0x00, 0x98, 0xD6, 0x5F, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x0D }, -{ 0x0C, 0x18, 0x87, 0xB3, 0x7F, 0x19, 0x10, 0x55, 0x75, 0x7C, 0x0E, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x05, 0x11, 0x15, 0x00, 0x64, 0x02, 0x08, 0x08, 0x00, 0x5C, 0x09, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0x04, 0x08, 0x15, 0x00, 0x48, 0x01, 0x08, 0x08, 0x00, 0x60, 0x08, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x02 }, -{ 0xDA, 0x00, 0x53, 0x30, 0x68, 0x07, 0x1E, 0x49, 0xC4, 0x7E, 0x03, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 }, -{ 0x1C, 0x00, 0x07, 0xBC, 0x6C, 0x0C, 0x14, 0x0B, 0x6A, 0x7E, 0x0B, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x03 }, -{ 0x0A, 0x0E, 0x7F, 0x00, 0x7D, 0x13, 0x20, 0x28, 0x03, 0x7C, 0x06, 0, { 0,0,0,0,0,0,0,0 }, 0, { 0,0,0,0,0,0,0,0 }, 0x00 } +static AdLibInstrument g_gmPercussionInstruments[39] = { + { 0x1A, 0x3F, 0x15, 0x05, 0x7C, 0x02, 0x21, 0x2B, 0xE4, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 }, + { 0x11, 0x12, 0x04, 0x07, 0x7C, 0x02, 0x23, 0x0B, 0xE5, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x0A, 0x3F, 0x0B, 0x01, 0x7C, 0x1F, 0x1C, 0x46, 0xD0, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x01 }, + { 0x00, 0x3F, 0x0F, 0x00, 0x7C, 0x10, 0x12, 0x07, 0x00, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x0F, 0x3F, 0x0B, 0x00, 0x7C, 0x1F, 0x0F, 0x19, 0xD0, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x00, 0x3F, 0x1F, 0x00, 0x7E, 0x1F, 0x16, 0x07, 0x00, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x1F, 0x4A, 0xD9, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0xCF, 0x7F, 0x08, 0xFF, 0x7E, 0x00, 0xC7, 0x2D, 0xF7, 0x73, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x12, 0x3F, 0x05, 0x06, 0x7C, 0x43, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0xCF, 0x7F, 0x08, 0xCF, 0x7E, 0x00, 0x45, 0x2A, 0xF8, 0x4B, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0C }, + { 0x12, 0x3F, 0x06, 0x17, 0x7C, 0x03, 0x27, 0x0B, 0xE9, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0xCF, 0x7F, 0x08, 0xCD, 0x7E, 0x00, 0x40, 0x1A, 0x69, 0x63, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0C }, + { 0x13, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x17, 0x0A, 0xD9, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x15, 0x3F, 0x05, 0x06, 0x7C, 0x03, 0x21, 0x0C, 0xE9, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0xCF, 0x3F, 0x2B, 0xFB, 0x7E, 0xC0, 0x1E, 0x1A, 0xCA, 0x7F, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 }, + { 0x17, 0x3F, 0x04, 0x09, 0x7C, 0x03, 0x22, 0x0D, 0xE9, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0xCF, 0x3F, 0x0F, 0x5E, 0x7C, 0xC6, 0x13, 0x00, 0xCA, 0x7F, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0xCF, 0x3F, 0x7E, 0x9D, 0x7C, 0xC8, 0xC0, 0x0A, 0xBA, 0x74, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 }, + { 0xCF, 0x3F, 0x4D, 0x9F, 0x7C, 0xC6, 0x00, 0x08, 0xDA, 0x5B, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0xCF, 0x3F, 0x5D, 0xAA, 0x7A, 0xC0, 0xA4, 0x67, 0x99, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x3F, 0x4A, 0xFD, 0x7C, 0xCF, 0x00, 0x59, 0xEA, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x0F, 0x18, 0x0A, 0xFA, 0x57, 0x06, 0x07, 0x06, 0x39, 0x7C, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x3F, 0x2B, 0xFC, 0x7C, 0xCC, 0xC6, 0x0B, 0xEA, 0x7F, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 }, + { 0x05, 0x1A, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x0C, 0xEA, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 }, + { 0x04, 0x19, 0x04, 0x00, 0x7C, 0x12, 0x10, 0x2C, 0xEA, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x04, 0x0A, 0x04, 0x00, 0x6C, 0x01, 0x07, 0x0D, 0xFA, 0x74, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 }, + { 0x15, 0x14, 0x05, 0x00, 0x7D, 0x01, 0x07, 0x5C, 0xE9, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x10, 0x10, 0x05, 0x08, 0x7C, 0x01, 0x08, 0x0D, 0xEA, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x11, 0x00, 0x06, 0x87, 0x7F, 0x02, 0x40, 0x09, 0x59, 0x68, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x08 }, + { 0x13, 0x26, 0x04, 0x6A, 0x7F, 0x01, 0x00, 0x08, 0x5A, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x08 }, + { 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC4, 0x00, 0x18, 0xF9, 0x54, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x4E, 0x0C, 0xAA, 0x50, 0xC3, 0x00, 0x18, 0xF8, 0x54, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCB, 0x3F, 0x8F, 0x00, 0x7E, 0xC5, 0x00, 0x98, 0xD6, 0x5F, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0D }, + { 0x0C, 0x18, 0x87, 0xB3, 0x7F, 0x19, 0x10, 0x55, 0x75, 0x7C, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x05, 0x11, 0x15, 0x00, 0x64, 0x02, 0x08, 0x08, 0x00, 0x5C, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x04, 0x08, 0x15, 0x00, 0x48, 0x01, 0x08, 0x08, 0x00, 0x60, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xDA, 0x00, 0x53, 0x30, 0x68, 0x07, 0x1E, 0x49, 0xC4, 0x7E, 0x03, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x1C, 0x00, 0x07, 0xBC, 0x6C, 0x0C, 0x14, 0x0B, 0x6A, 0x7E, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x0A, 0x0E, 0x7F, 0x00, 0x7D, 0x13, 0x20, 0x28, 0x03, 0x7C, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }; -static const byte gm_percussion_lookup[128] = { +#ifdef ENABLE_OPL3 +static const AdLibInstrument g_gmInstrumentsOPL3[128][2] = { + { { 0xC2, 0xC2, 0x0A, 0x6B, 0xA0, 0xC2, 0x08, 0x0D, 0x88, 0xC8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x23 }, + { 0x02, 0x00, 0x0C, 0x78, 0x61, 0x04, 0x4C, 0x0B, 0x9A, 0xC8, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x23 } }, + { { 0x22, 0x53, 0x0E, 0x8A, 0x60, 0x14, 0x06, 0x1D, 0x7A, 0xB8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x22, 0x5A, 0x0E, 0x8A, 0x40, 0x14, 0x2F, 0x0E, 0x7A, 0x88, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x06, 0x00, 0x1C, 0x79, 0x70, 0x02, 0x00, 0x4B, 0x79, 0xA8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x06, 0x00, 0x1A, 0x79, 0x60, 0x02, 0x00, 0x4C, 0xA9, 0xC8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC2, 0x80, 0x0B, 0x89, 0x90, 0xC2, 0x06, 0x1B, 0xA8, 0xB0, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x23 }, + { 0x04, 0x28, 0x5D, 0xB8, 0x01, 0x02, 0x00, 0x3C, 0x70, 0x88, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC2, 0x17, 0x3D, 0x6A, 0x00, 0xC4, 0x2E, 0x2D, 0xC9, 0x40, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x17, 0x3D, 0x6A, 0x00, 0xC4, 0x2E, 0x2D, 0xC9, 0x40, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x06, 0x1E, 0x1C, 0x99, 0x00, 0x02, 0x3A, 0x4C, 0x79, 0x00, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x06, 0x1E, 0x1C, 0x99, 0x00, 0x02, 0x3A, 0x4C, 0x79, 0x00, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x84, 0x40, 0x3B, 0x5A, 0x63, 0x81, 0x00, 0x3B, 0x5A, 0xD3, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x87, 0x40, 0x3A, 0x5A, 0x94, 0x82, 0x04, 0x3D, 0x59, 0xAC, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x84, 0x40, 0x3B, 0x5A, 0xC3, 0x81, 0x00, 0x3B, 0x5A, 0xFB, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x84, 0x40, 0x3B, 0x5A, 0xC3, 0x81, 0x00, 0x3B, 0x5A, 0xFB, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x8C, 0x80, 0x05, 0xEA, 0xA9, 0x82, 0x04, 0x3D, 0xAA, 0xB0, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x8C, 0x80, 0x06, 0x98, 0xA9, 0x86, 0x10, 0x36, 0x7A, 0xFD, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x85, 0x40, 0x0D, 0xEC, 0xE1, 0x84, 0x58, 0x3E, 0xCB, 0xF8, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x84, 0x40, 0x0D, 0xEB, 0xE0, 0x84, 0x48, 0x3E, 0xCA, 0xC0, 0x05, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x8A, 0xC0, 0x0C, 0xDC, 0xA0, 0x88, 0x58, 0x3D, 0xDA, 0xF8, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x8A, 0xC0, 0x0C, 0xDC, 0xA0, 0x88, 0x58, 0x3D, 0xDA, 0xF8, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC9, 0x40, 0x2B, 0x78, 0x8A, 0xC2, 0x0A, 0x4C, 0x8A, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x1A }, + { 0xCA, 0x40, 0x47, 0xCA, 0xB4, 0xC2, 0x00, 0x57, 0x8A, 0xB8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x1A } }, + { { 0x2A, 0x0E, 0x17, 0x89, 0x50, 0x22, 0x0C, 0x1B, 0x09, 0xE0, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x2A, 0x1A, 0x19, 0x8A, 0x00, 0x22, 0x38, 0x0B, 0x0A, 0x00, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE7, 0x9B, 0x08, 0x08, 0x4A, 0xE2, 0x06, 0x0A, 0x08, 0xE0, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE7, 0x9B, 0x08, 0x08, 0x4A, 0xE2, 0x2F, 0x0A, 0x08, 0x68, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC5, 0x0A, 0x05, 0xDC, 0xB8, 0x84, 0x06, 0x00, 0xEC, 0xC0, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x09, 0x10, 0x04, 0x5B, 0xA5, 0x02, 0x08, 0x00, 0xEC, 0x70, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x86, 0x40, 0x5D, 0x5A, 0x81, 0x81, 0x00, 0x0B, 0x5A, 0xFB, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x86, 0x40, 0x5D, 0x5A, 0x81, 0x81, 0x00, 0x0B, 0x5A, 0xFB, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xED, 0x0F, 0x5B, 0xC8, 0xC8, 0xE2, 0x9F, 0x4A, 0xE9, 0xF9, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE6, 0x40, 0x0A, 0xA7, 0x64, 0xE2, 0x8B, 0x6A, 0x79, 0xB1, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE8, 0x4F, 0x3A, 0xD7, 0xF8, 0xE2, 0x97, 0x49, 0xF9, 0xF9, 0x05, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC9, 0x02, 0x16, 0x9A, 0xAB, 0xC4, 0x15, 0x46, 0xBA, 0xF8, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE1, 0x08, 0x2F, 0xF7, 0xE1, 0xF3, 0x42, 0x8F, 0xC7, 0xC2, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE3, 0x00, 0x2D, 0xF7, 0xC1, 0xE4, 0x40, 0x7F, 0xC7, 0xD2, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x01, 0x8C, 0x9F, 0xDA, 0xE8, 0xE4, 0x50, 0x9F, 0xDA, 0xF2, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x02, 0x80, 0x9F, 0xDA, 0x00, 0xE3, 0x50, 0x9F, 0xD9, 0xFA, 0x03, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x08, 0xD5, 0x9D, 0xA5, 0x89, 0xE2, 0x3F, 0x9F, 0xD6, 0x91, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x08, 0xD5, 0x9D, 0xA5, 0x89, 0xE2, 0x3F, 0x9F, 0xD6, 0x91, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE5, 0x0F, 0x7D, 0xB8, 0x5A, 0xA2, 0x0C, 0x7C, 0xC7, 0xC1, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x06, 0x4C, 0xAC, 0x56, 0x31, 0x02, 0x08, 0x8D, 0x46, 0xDC, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xF2, 0x2A, 0x9F, 0xDB, 0x01, 0xE1, 0x04, 0x8F, 0xD7, 0xC2, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x00, 0x9F, 0xDB, 0xA9, 0xE1, 0x00, 0x8F, 0xD7, 0xBA, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x88, 0x9C, 0x50, 0xC8, 0xE2, 0x18, 0x70, 0xC4, 0xF8, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE6, 0x00, 0x9C, 0x50, 0xB0, 0xE4, 0x00, 0x70, 0xC4, 0xA0, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x02, 0xA3, 0x0D, 0xDA, 0x01, 0xC2, 0x35, 0x5D, 0x58, 0x00, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x18 }, + { 0x02, 0xA3, 0x0D, 0xDA, 0x01, 0xC2, 0x35, 0x5D, 0x58, 0x00, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x18 } }, + { { 0x42, 0x53, 0x3E, 0xEB, 0x48, 0xD4, 0x05, 0x1D, 0xA9, 0xC9, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x18 }, + { 0x42, 0x54, 0x6F, 0xEB, 0x61, 0xD4, 0x02, 0x2E, 0xA9, 0xC8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x18 } }, + { { 0xC2, 0x00, 0x59, 0x17, 0xB1, 0xC2, 0x1E, 0x6D, 0x98, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x19 }, + { 0xC2, 0x00, 0x08, 0xB3, 0x99, 0xC2, 0x06, 0x2B, 0x58, 0xFA, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x19 } }, + { { 0xC6, 0x01, 0x2D, 0xA7, 0x88, 0xC2, 0x08, 0x0E, 0xA7, 0xC1, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC4, 0x00, 0x2D, 0xA7, 0x91, 0xC2, 0x02, 0x0E, 0xA7, 0xD1, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC2, 0x0C, 0x06, 0x06, 0xA9, 0xC2, 0x3F, 0x08, 0xB8, 0xF9, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0A }, + { 0xC1, 0x00, 0x68, 0x50, 0xB8, 0xC2, 0x00, 0x48, 0x84, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0A } }, + { { 0xC2, 0x2E, 0x4F, 0x77, 0x00, 0xC4, 0x08, 0x0E, 0x98, 0xB1, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x2F, 0x6F, 0x79, 0x00, 0xC8, 0x0F, 0x5E, 0x98, 0xB9, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC2, 0x30, 0x4F, 0xCA, 0x01, 0xC4, 0x0D, 0x0E, 0xB8, 0xFB, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x30, 0x4F, 0xCA, 0x01, 0xC4, 0x0D, 0x0E, 0xB8, 0xFB, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC4, 0x29, 0x4F, 0xCA, 0x03, 0xC8, 0x0D, 0x0C, 0xB7, 0xF9, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0B }, + { 0xC4, 0x29, 0x4F, 0xCA, 0x03, 0xC8, 0x0D, 0x0C, 0xB7, 0xF9, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0B } }, + { { 0xC2, 0x41, 0x3D, 0x96, 0x88, 0xC4, 0xCA, 0x0E, 0xC7, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x20 }, + { 0xC2, 0x04, 0x58, 0xC9, 0x90, 0xC2, 0x94, 0x2C, 0xB9, 0xF0, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x20 } }, + { { 0x31, 0x13, 0x2D, 0xD7, 0x78, 0xE2, 0x18, 0x2E, 0xB8, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x31, 0x13, 0x2D, 0xD7, 0x78, 0xE2, 0x18, 0x2E, 0xB8, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x22, 0x86, 0x0D, 0xD7, 0xA0, 0xE4, 0x18, 0x5E, 0xB8, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x28 }, + { 0x22, 0x86, 0x0D, 0xD7, 0xA0, 0xE4, 0x18, 0x5E, 0xB8, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x28 } }, + { { 0xF2, 0x0A, 0x0D, 0xD7, 0x80, 0xE4, 0x1F, 0x5E, 0xB8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xD2, 0x06, 0x9A, 0xD7, 0xA0, 0xC2, 0x1F, 0x59, 0xB8, 0xF8, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xF2, 0x09, 0x4B, 0xD6, 0x90, 0xE4, 0x1F, 0x1C, 0xB8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x28 }, + { 0xF2, 0x09, 0x4B, 0xD6, 0x90, 0xE4, 0x1F, 0x1C, 0xB8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x28 } }, + { { 0x62, 0x11, 0x0C, 0xE6, 0x78, 0xE4, 0x1F, 0x0C, 0xC8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x11, 0x0C, 0xE6, 0x78, 0xE4, 0x1F, 0x0C, 0xC8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x12, 0x3D, 0xE6, 0x68, 0xE4, 0x1F, 0x7D, 0xB8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x12, 0x3D, 0xE6, 0x68, 0xE4, 0x1F, 0x7D, 0xB8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x13, 0x3D, 0xE6, 0x68, 0xE4, 0x1F, 0x5D, 0xB8, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x13, 0x3D, 0xE6, 0x68, 0xE4, 0x1F, 0x5D, 0xB8, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xA2, 0x40, 0x5D, 0xBA, 0x7B, 0xE2, 0x00, 0x8F, 0xD8, 0xF1, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xA2, 0x40, 0x5D, 0xBA, 0x7B, 0xE2, 0x00, 0x8F, 0xD8, 0xF1, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x40, 0x3D, 0xDA, 0x73, 0xE1, 0x00, 0x7E, 0xD8, 0xF2, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x40, 0x3D, 0xDA, 0x73, 0xE1, 0x00, 0x7E, 0xD8, 0xF2, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x62, 0x00, 0x6D, 0xFA, 0xB9, 0xE2, 0x00, 0x8F, 0xC8, 0xF1, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x00, 0x6D, 0xFA, 0xB9, 0xE2, 0x00, 0x8F, 0xC8, 0xF1, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE1, 0x00, 0x4E, 0xDB, 0x92, 0xE3, 0x18, 0x6F, 0xE9, 0xFA, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0x4E, 0xDB, 0xCA, 0xE2, 0x00, 0x6F, 0xE9, 0xFA, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE1, 0x00, 0x4E, 0xDB, 0xCA, 0xE2, 0x00, 0x7F, 0xE9, 0xFA, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0x4E, 0xDB, 0xCA, 0xE2, 0x00, 0x7F, 0xE9, 0xFA, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x02, 0x0F, 0x66, 0xAA, 0xA1, 0x02, 0x64, 0x29, 0xF9, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x02, 0x00, 0x65, 0xAA, 0xF1, 0x02, 0x4A, 0x28, 0xF9, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 } }, + { { 0x16, 0x4A, 0x04, 0xBA, 0x71, 0xC2, 0x48, 0x2E, 0xCA, 0xF0, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x14, 0xC0, 0x66, 0x08, 0x90, 0xC2, 0x48, 0x2C, 0x0A, 0xA0, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0x02, 0x0A, 0x01, 0x7A, 0xB1, 0x02, 0x12, 0x2A, 0xEA, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x02, 0x06, 0x75, 0x05, 0xB1, 0x01, 0x3F, 0x28, 0xEA, 0xF9, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x16 } }, + { { 0x62, 0x53, 0x9C, 0xBA, 0x61, 0x62, 0x5A, 0xAD, 0xCA, 0xC1, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x40, 0x9F, 0x8A, 0x98, 0xE2, 0x11, 0x7F, 0xB8, 0xFA, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xF2, 0x40, 0x6E, 0xDA, 0x91, 0xE2, 0x13, 0x8F, 0xF9, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x40, 0x6E, 0xDA, 0x91, 0xE2, 0x13, 0x8F, 0xF9, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x40, 0x8F, 0xFA, 0xA0, 0xF2, 0x04, 0x7F, 0xFA, 0xF9, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x40, 0x8F, 0xFA, 0xA0, 0xF2, 0x04, 0x7F, 0xFA, 0xF9, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0xA0, 0xCE, 0x5B, 0x02, 0xE2, 0x32, 0x7F, 0xFB, 0x79, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0xA0, 0xCE, 0x5B, 0x02, 0xE2, 0x32, 0x7F, 0xFB, 0x79, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE6, 0x80, 0x9C, 0x99, 0x82, 0xE2, 0x04, 0x8D, 0x78, 0xC0, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE0, 0x44, 0x8A, 0xA9, 0x5B, 0xE1, 0x06, 0x8D, 0x79, 0xBA, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE8, 0xA0, 0xAC, 0x67, 0x02, 0xE2, 0x06, 0x7C, 0x7A, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEA, 0xA0, 0xAC, 0x67, 0x02, 0xE2, 0x00, 0x7C, 0x7A, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE7, 0x94, 0xAD, 0xB7, 0x03, 0xE2, 0x00, 0x7C, 0xBA, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE7, 0x94, 0xAD, 0xB7, 0x03, 0xE2, 0x00, 0x7C, 0xBA, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC3, 0x3F, 0x4B, 0xE9, 0xFA, 0xC1, 0x3F, 0x9B, 0xF9, 0xFB, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 }, + { 0xC3, 0x3F, 0x4B, 0xE9, 0xFA, 0xC1, 0x3F, 0x9B, 0xF9, 0xFB, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 } }, + { { 0xB2, 0x20, 0xAD, 0xE9, 0x00, 0x62, 0x05, 0x8F, 0xC8, 0xD0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xB2, 0x25, 0xAD, 0xE9, 0x00, 0x62, 0x00, 0x8F, 0xC8, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xF2, 0x02, 0xAF, 0xFB, 0x90, 0xF6, 0x54, 0x8F, 0xE9, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x00, 0x9F, 0xFA, 0xB0, 0xF2, 0x58, 0x7F, 0xEA, 0xF8, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xF2, 0x00, 0xAF, 0x88, 0xA8, 0xF2, 0x46, 0x6E, 0xC9, 0xE0, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xD2, 0x00, 0x7B, 0x88, 0xA8, 0xD2, 0x4C, 0x69, 0xE9, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xF2, 0x2A, 0x9F, 0x98, 0x01, 0xE2, 0x8F, 0x4E, 0x78, 0xC0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xD2, 0x02, 0x85, 0x89, 0xC8, 0xD2, 0x94, 0x77, 0x49, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x02, 0x9F, 0xB8, 0x90, 0x22, 0x8A, 0x9F, 0xE8, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x00, 0x86, 0xB8, 0x98, 0x02, 0x8F, 0x89, 0xE8, 0xF9, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x2A, 0x7F, 0xB8, 0x01, 0xE4, 0x00, 0x0D, 0xC5, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x2A, 0x7F, 0xB8, 0x01, 0xE4, 0x00, 0x0D, 0xC5, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x28, 0x8E, 0xE8, 0x01, 0xF2, 0x00, 0x4D, 0xD6, 0xF9, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x28, 0x8E, 0xE8, 0x01, 0xF2, 0x00, 0x4D, 0xD6, 0xF9, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x62, 0x23, 0x8F, 0xEA, 0x00, 0xF2, 0x00, 0x5E, 0xD9, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x23, 0x8F, 0xEA, 0x00, 0xF2, 0x00, 0x5E, 0xD9, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xB4, 0x26, 0x6E, 0x98, 0x01, 0x62, 0x00, 0x7D, 0xC8, 0xF9, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xB4, 0x26, 0x6E, 0x98, 0x01, 0x62, 0x00, 0x7D, 0xC8, 0xF9, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x2E, 0x20, 0xD9, 0x01, 0xF2, 0x1A, 0x90, 0xF8, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xD2, 0x10, 0x69, 0x18, 0xCF, 0xD4, 0x14, 0x5B, 0x04, 0xFD, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x28, 0x7E, 0xF8, 0x01, 0xE2, 0x23, 0x8E, 0xE8, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x28, 0x7E, 0xF8, 0x01, 0xE2, 0x23, 0x8E, 0xE8, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xB8, 0x28, 0x9E, 0x98, 0x01, 0x62, 0x00, 0x3D, 0xC8, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xB8, 0x28, 0x9E, 0x98, 0x01, 0x62, 0x00, 0x3D, 0xC8, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x62, 0x00, 0x8E, 0xC9, 0x79, 0xE6, 0x00, 0x7E, 0xD8, 0xD0, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x00, 0x8E, 0xC9, 0x79, 0xE6, 0x00, 0x7E, 0xD8, 0xD0, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x00, 0x5F, 0xF9, 0x88, 0xE4, 0x9E, 0x8F, 0xF8, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x00, 0x97, 0xF9, 0x90, 0xC9, 0x80, 0x69, 0x98, 0xA0, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x62, 0x0C, 0x6E, 0xD8, 0x79, 0x2A, 0x09, 0x7D, 0xD8, 0xC0, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x02, 0x04, 0x8A, 0xD8, 0x80, 0x0C, 0x12, 0x85, 0xD8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x00, 0x7E, 0x89, 0x70, 0xE6, 0x8F, 0x80, 0xF8, 0xF0, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC4, 0x00, 0x67, 0x59, 0x70, 0xC6, 0x8A, 0x77, 0xA8, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x80, 0x6C, 0xD9, 0x60, 0xE2, 0x00, 0x8D, 0xC8, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x80, 0x6C, 0xD9, 0x60, 0xE2, 0x00, 0x8D, 0xC8, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x80, 0x88, 0x48, 0x98, 0xE2, 0x1E, 0x8E, 0xC9, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xF2, 0x40, 0xA8, 0xB9, 0x80, 0xE2, 0x0C, 0x89, 0x09, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x00, 0x77, 0xC5, 0xA8, 0xE2, 0x00, 0x9E, 0xD7, 0xE0, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x00, 0x77, 0xC5, 0xA8, 0xE2, 0x00, 0x9E, 0xD7, 0xE0, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x80, 0x86, 0xB9, 0xA8, 0xE2, 0x14, 0x9F, 0xD7, 0xB0, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x80, 0x94, 0x09, 0x78, 0xC2, 0x00, 0x97, 0x97, 0xF8, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x00, 0x68, 0x68, 0xAA, 0xE2, 0x0A, 0x9B, 0xB3, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC2, 0x00, 0x86, 0x68, 0xA0, 0xC2, 0x00, 0x77, 0x47, 0xE0, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x00, 0xA6, 0x87, 0x81, 0xE2, 0x0A, 0x7E, 0xC9, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x00, 0x89, 0x40, 0x79, 0xE2, 0x00, 0x7E, 0xC9, 0x90, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x80, 0xAA, 0xB8, 0x90, 0xE2, 0x00, 0x9E, 0xF9, 0xC0, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE6, 0x80, 0x9D, 0xB8, 0x51, 0xE2, 0x00, 0x9E, 0xF9, 0xA0, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x80, 0x8E, 0x64, 0xD0, 0xE2, 0x28, 0x6F, 0x73, 0xF8, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x80, 0x8E, 0x64, 0xD0, 0xE2, 0x28, 0x6F, 0x73, 0xF8, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE8, 0x00, 0x7D, 0x99, 0xA8, 0xE6, 0x80, 0x80, 0xF8, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE8, 0x00, 0x7D, 0x99, 0xA8, 0xE6, 0x80, 0x80, 0xF8, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE6, 0x00, 0x9F, 0xB9, 0xD9, 0xE1, 0x00, 0x8F, 0xC8, 0xF9, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE6, 0x00, 0x9F, 0xB9, 0xD9, 0xE1, 0x00, 0x8F, 0xC8, 0xF9, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x00, 0x09, 0x68, 0x92, 0xE2, 0x2B, 0x9E, 0xF3, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x00, 0x09, 0x68, 0x92, 0xE2, 0x2B, 0x9E, 0xF3, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC4, 0x00, 0x99, 0xE8, 0x73, 0xE2, 0x25, 0x6F, 0x93, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xC4, 0x00, 0x99, 0xE8, 0x73, 0xE2, 0x25, 0x6F, 0x93, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE6, 0x00, 0x6F, 0xDA, 0xC9, 0xE2, 0x05, 0x2F, 0xD8, 0xAA, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x00, 0x4F, 0xDA, 0xC8, 0xE2, 0x00, 0x0F, 0xD8, 0xD0, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xEC, 0x60, 0x9D, 0xC7, 0x00, 0xE2, 0x21, 0x7F, 0xC9, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEC, 0x60, 0x9D, 0xC7, 0x00, 0xE2, 0x21, 0x7F, 0xC9, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE3, 0x00, 0x0F, 0xF7, 0xF9, 0xE1, 0x3F, 0x0F, 0xA7, 0x01, 0x0D, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE3, 0x00, 0x0F, 0xF7, 0xF9, 0xE1, 0x3F, 0x0F, 0xA7, 0x01, 0x0D, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0xA9, 0x0F, 0xA8, 0x02, 0xE2, 0x3C, 0x5F, 0xDA, 0x78, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0xA9, 0x0F, 0xA8, 0x02, 0xE2, 0x3C, 0x5F, 0xDA, 0x78, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE8, 0x40, 0x0D, 0x89, 0xF9, 0xE2, 0x17, 0x7E, 0xD9, 0xF8, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE8, 0x40, 0x0D, 0x89, 0xF9, 0xE2, 0x17, 0x7E, 0xD9, 0xF8, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE1, 0x00, 0xDF, 0x8A, 0xAA, 0xE2, 0x5E, 0xCF, 0xBA, 0xFA, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0xDF, 0x8A, 0xAA, 0xE2, 0x5E, 0xCF, 0xBA, 0xFA, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE2, 0x00, 0x0B, 0x68, 0xC0, 0xE2, 0x01, 0x9E, 0xB8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x00, 0x0B, 0x68, 0xC0, 0xE2, 0x01, 0x9E, 0xB8, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xEA, 0x00, 0xAE, 0xAB, 0x91, 0xE2, 0x00, 0xAE, 0xBA, 0xD8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEA, 0x00, 0xAE, 0xAB, 0x91, 0xE2, 0x00, 0xAE, 0xBA, 0xD8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xEB, 0x80, 0x8C, 0xCB, 0x72, 0xE2, 0x86, 0xAF, 0xCA, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEB, 0xC3, 0x9C, 0xCB, 0xA2, 0xE2, 0x4C, 0xAE, 0xCA, 0xFA, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE5, 0x40, 0xDB, 0x3B, 0x78, 0xE2, 0x80, 0xBE, 0xCA, 0xE1, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x80, 0x8E, 0xCB, 0xC0, 0xE2, 0x90, 0xAE, 0xCA, 0xFB, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE4, 0x00, 0x9E, 0xAA, 0x79, 0xE1, 0x43, 0x0F, 0xBA, 0xFA, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE4, 0x00, 0x9E, 0xAA, 0x79, 0xE1, 0x43, 0x0F, 0xBA, 0xFA, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE7, 0x40, 0xEB, 0xCA, 0x80, 0xE2, 0x03, 0xBF, 0xBA, 0xC2, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE3, 0x80, 0xDB, 0xCA, 0x40, 0xE2, 0x08, 0xDF, 0xBA, 0xC1, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xEA, 0x00, 0x68, 0xB8, 0x90, 0xE2, 0x0A, 0x8E, 0xB8, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEA, 0x00, 0x68, 0xB8, 0x90, 0xE2, 0x0A, 0x8E, 0xB8, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x61, 0x00, 0xBE, 0x99, 0xFA, 0xE3, 0x40, 0xCF, 0xCA, 0xF9, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x00, 0xCE, 0x9A, 0xA8, 0xE2, 0x45, 0xCF, 0xCA, 0xA0, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xCD, 0x00, 0x0B, 0x00, 0x90, 0xC2, 0x58, 0x0C, 0x00, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x1C }, + { 0xCD, 0x00, 0x0B, 0x00, 0x90, 0xC2, 0x58, 0x0C, 0x00, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x1C } }, + { { 0xE2, 0x00, 0x0E, 0x00, 0xA2, 0xE2, 0x58, 0x5F, 0xD0, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE2, 0x00, 0x0E, 0x00, 0xA2, 0xE2, 0x58, 0x5F, 0xD0, 0xF9, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xEC, 0x00, 0x7D, 0xDA, 0x80, 0xE2, 0x00, 0x5E, 0x9B, 0xA8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE6, 0x0A, 0x4C, 0xC9, 0x60, 0xE2, 0x07, 0x0C, 0x7A, 0xB8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE9, 0xC0, 0xEE, 0xD8, 0x83, 0xE2, 0x05, 0xDD, 0xAA, 0xE0, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xED, 0x48, 0xDE, 0xD8, 0xB4, 0xE1, 0x00, 0xDD, 0xAA, 0xA9, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xDA, 0x00, 0x8F, 0xAC, 0x92, 0x22, 0x05, 0x8D, 0x8A, 0xE9, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xEF, 0x00, 0x8C, 0xAA, 0x67, 0x25, 0x00, 0x9D, 0xAB, 0xC1, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x62, 0x82, 0xCB, 0x7A, 0xD8, 0xE6, 0x56, 0xAF, 0xDB, 0xE0, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x84, 0xBB, 0xAA, 0xCA, 0xCF, 0x41, 0xAC, 0xDA, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xC2, 0x41, 0xAC, 0xBB, 0xBB, 0xC2, 0x85, 0x0E, 0xCB, 0xF9, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x12 }, + { 0xC2, 0x03, 0x6A, 0x5B, 0xA4, 0xC2, 0x0D, 0x2A, 0xBB, 0xFC, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x12 } }, + { { 0x75, 0x00, 0x0E, 0xBB, 0xB2, 0xE2, 0x1E, 0x0A, 0xA9, 0xF9, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 }, + { 0x62, 0x00, 0x04, 0x9A, 0xE8, 0xE2, 0x00, 0x0A, 0x48, 0xFD, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 } }, + { { 0x41, 0x00, 0x0E, 0xEA, 0xA3, 0xC2, 0x00, 0x08, 0xCA, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 }, + { 0x41, 0x00, 0x0E, 0xEA, 0xA3, 0xC2, 0x00, 0x08, 0xCA, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 } }, + { { 0xC1, 0x40, 0x0C, 0x59, 0xD2, 0xC2, 0x80, 0x3C, 0xAB, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0D }, + { 0xC1, 0x40, 0x0C, 0x59, 0xD2, 0xC2, 0x80, 0x3C, 0xAB, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0D } }, + { { 0x4B, 0x00, 0x0A, 0xF5, 0xC1, 0xC2, 0x19, 0x0C, 0xE9, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 }, + { 0x4B, 0x00, 0x0A, 0xF5, 0xC1, 0xC2, 0x19, 0x0C, 0xE9, 0xF8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 } }, + { { 0x62, 0x00, 0x7F, 0xD8, 0xA8, 0xEA, 0x00, 0x8F, 0xD8, 0xF9, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x62, 0x00, 0x7F, 0xD8, 0xA8, 0xEA, 0x00, 0x8F, 0xD8, 0xF9, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE1, 0x00, 0x7F, 0xD9, 0xAA, 0xE1, 0x00, 0x8F, 0xD8, 0xFA, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0x7F, 0xD9, 0xAA, 0xE1, 0x00, 0x8F, 0xD8, 0xFA, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xE1, 0x00, 0x7F, 0xD9, 0xAA, 0xE1, 0x00, 0x8F, 0xD8, 0xFA, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xE1, 0x00, 0x7F, 0xD9, 0xAA, 0xE1, 0x00, 0x8F, 0xD8, 0xFA, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0xCF, 0x40, 0x09, 0xEA, 0xA8, 0xC4, 0x00, 0x0C, 0xDB, 0xC8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x40, 0x09, 0xEA, 0xA8, 0xC4, 0x00, 0x0C, 0xDB, 0xC8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0xCF, 0x40, 0x0C, 0xAA, 0xA8, 0xC4, 0x00, 0x18, 0xF9, 0xC8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x40, 0x0C, 0xAA, 0xA8, 0xC4, 0x00, 0x18, 0xF9, 0xC8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0xC9, 0x0C, 0x88, 0xD9, 0x6A, 0xC2, 0x14, 0x3A, 0xEA, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0xC5, 0x00, 0x98, 0xD9, 0x92, 0xC1, 0x16, 0x6E, 0xF9, 0xE8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } }, + { { 0x03, 0x00, 0x15, 0x00, 0xC8, 0x02, 0x00, 0x08, 0x00, 0xF8, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x03, 0x00, 0x15, 0x00, 0xC8, 0x02, 0x00, 0x08, 0x00, 0xF8, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x01, 0x0C, 0x44, 0xE6, 0xE8, 0x01, 0x3F, 0x0C, 0xEA, 0xF8, 0x0C, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x02, 0x3F, 0x05, 0x08, 0xF8, 0x03, 0x3F, 0x3C, 0xF9, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 } }, + { { 0x00, 0x00, 0x36, 0x67, 0xF8, 0x01, 0x3F, 0x0E, 0xFA, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x00, 0x00, 0x36, 0x67, 0xF8, 0x01, 0x3F, 0x0E, 0xFA, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } }, + { { 0x02, 0x00, 0x36, 0x68, 0xF8, 0x01, 0x3F, 0x0E, 0xFA, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x02, 0x00, 0x36, 0x68, 0xF8, 0x01, 0x3F, 0x0E, 0xFA, 0xF8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } }, + { { 0xCB, 0x00, 0xAF, 0x00, 0xFA, 0xC0, 0x00, 0xC0, 0x06, 0xFB, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0F }, + { 0xCB, 0x00, 0xAF, 0x00, 0xFA, 0xC0, 0x00, 0xC0, 0x06, 0xFB, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0F } }, + { { 0x05, 0x0D, 0x80, 0xA6, 0xFB, 0x0B, 0x38, 0xA9, 0xD8, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x05, 0x0D, 0x80, 0xA6, 0xFB, 0x0B, 0x38, 0xA9, 0xD8, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 } }, + { { 0x0F, 0x00, 0x90, 0xFA, 0xD0, 0x06, 0x00, 0xA7, 0x39, 0xA8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 }, + { 0x0F, 0x00, 0x90, 0xFA, 0xD0, 0x06, 0x00, 0xA7, 0x39, 0xA8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 } }, + { { 0xC9, 0x15, 0xDD, 0xFF, 0xF8, 0x00, 0x00, 0xE7, 0xFC, 0xD8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x38 }, + { 0xC9, 0x15, 0xDD, 0xFF, 0xF8, 0x00, 0x00, 0xE7, 0xFC, 0xD8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x38 } }, + { { 0x48, 0x3C, 0x30, 0xF6, 0x03, 0x0A, 0x38, 0x97, 0xE8, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x48, 0x3C, 0x30, 0xF6, 0x03, 0x0A, 0x38, 0x97, 0xE8, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 } }, + { { 0x07, 0x80, 0x0B, 0xC8, 0xC9, 0x02, 0x3F, 0x0C, 0xEA, 0xF8, 0x0F, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x07, 0x80, 0x0B, 0xC8, 0xC9, 0x02, 0x3F, 0x0C, 0xEA, 0xF8, 0x0F, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } }, + { { 0x00, 0x21, 0x66, 0x40, 0x03, 0x00, 0x3F, 0x47, 0x00, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x00, 0x21, 0x66, 0x40, 0x03, 0x00, 0x3F, 0x47, 0x00, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x08, 0x00, 0x0B, 0x3C, 0xF8, 0x08, 0x3F, 0x06, 0xF3, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x08, 0x00, 0x0B, 0x3C, 0xF8, 0x08, 0x3F, 0x06, 0xF3, 0x00, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x00, 0x3F, 0x4C, 0xFB, 0x00, 0x00, 0x3F, 0x0A, 0xE9, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x00, 0x3F, 0x4C, 0xFB, 0x00, 0x00, 0x3F, 0x0A, 0xE9, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } } +}; + +static const AdLibInstrument g_gmPercussionInstrumentsOPL3[39][2] = { + { { 0x1A, 0x3F, 0x15, 0x05, 0xF8, 0x02, 0x21, 0x2B, 0xE4, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 }, + { 0x11, 0x18, 0x15, 0x00, 0xF8, 0x12, 0x00, 0x2B, 0x03, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 } }, + { { 0x11, 0x12, 0x04, 0x07, 0xF8, 0x02, 0x18, 0x0B, 0xE5, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x11, 0x28, 0x06, 0x04, 0xF8, 0x02, 0x1E, 0x1B, 0x02, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } }, + { { 0x0A, 0x3F, 0x0B, 0x01, 0xF8, 0x1F, 0x13, 0x46, 0xD0, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x01 }, + { 0x04, 0x18, 0x06, 0x01, 0xB0, 0x10, 0x00, 0x07, 0x00, 0x90, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x01 } }, + { { 0x00, 0x3F, 0x0F, 0x00, 0xF8, 0x10, 0x0A, 0x07, 0x00, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x02, 0x14, 0x04, 0x00, 0xC0, 0x11, 0x08, 0x07, 0x00, 0xC6, 0x02, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x0F, 0x3F, 0x0B, 0x00, 0xF8, 0x1F, 0x07, 0x19, 0xD0, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x0E, 0x32, 0x76, 0x03, 0xF8, 0x1F, 0x0F, 0x77, 0xD4, 0xFC, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x00, 0x3F, 0x1F, 0x00, 0xFA, 0x1F, 0x0C, 0x07, 0x00, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x07, 0x11, 0x13, 0x00, 0xA0, 0x13, 0x00, 0x07, 0x00, 0xC8, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0x12, 0x3F, 0x05, 0x06, 0xF8, 0x03, 0x16, 0x4A, 0xD9, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x02, 0x22, 0x05, 0xB6, 0xF8, 0x04, 0x0A, 0x59, 0x03, 0xF8, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0xCF, 0x7F, 0x08, 0xFF, 0xFA, 0x00, 0xC0, 0x2D, 0xF7, 0xE3, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xD2, 0x7F, 0x04, 0x0F, 0xFA, 0x10, 0xCD, 0x24, 0x07, 0xFB, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x12, 0x3F, 0x05, 0x06, 0xF8, 0x43, 0x17, 0x0C, 0xE9, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x12, 0x13, 0x09, 0x96, 0xF8, 0x44, 0x0A, 0x07, 0x03, 0xF8, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0xCF, 0x7F, 0x08, 0xCF, 0xFA, 0x00, 0x40, 0x2A, 0xF8, 0x8B, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0C }, + { 0xCF, 0x7F, 0x05, 0x07, 0xFA, 0x00, 0x40, 0x25, 0x08, 0xC3, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0C } }, + { { 0x12, 0x3F, 0x06, 0x17, 0xF8, 0x03, 0x1D, 0x0B, 0xE9, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x12, 0x1A, 0x08, 0x96, 0xF8, 0x44, 0x00, 0x08, 0x03, 0xF8, 0x05, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0xCF, 0x7F, 0x08, 0xCD, 0xFA, 0x00, 0x40, 0x1A, 0x69, 0xB3, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0C }, + { 0xCD, 0x3F, 0x36, 0x05, 0xFC, 0x0F, 0x47, 0x46, 0x06, 0xDF, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0C } }, + { { 0x13, 0x3F, 0x05, 0x06, 0xF8, 0x03, 0x0D, 0x0A, 0xD9, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x12, 0x14, 0x09, 0x96, 0xF8, 0x44, 0x02, 0x07, 0x03, 0xF8, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0x15, 0x3F, 0x05, 0x06, 0xF8, 0x03, 0x16, 0x0C, 0xE9, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x12, 0x00, 0x07, 0x96, 0xE8, 0x44, 0x02, 0x08, 0x03, 0xF8, 0x07, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0xCF, 0x3F, 0x2B, 0xFB, 0xFA, 0xC0, 0x16, 0x1A, 0xCA, 0xFB, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 }, + { 0xCF, 0x3F, 0x2B, 0xFB, 0xFA, 0xC0, 0x1E, 0x1A, 0xCA, 0xFB, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 } }, + { { 0x17, 0x3F, 0x04, 0x09, 0xF8, 0x03, 0x18, 0x0D, 0xE9, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x12, 0x00, 0x07, 0x96, 0xF8, 0x44, 0x02, 0x08, 0xF9, 0xF8, 0x01, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0xCF, 0x3F, 0x0F, 0x5E, 0xF8, 0xC6, 0x0C, 0x00, 0xCA, 0xFB, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0xCF, 0x3F, 0x04, 0x57, 0xF8, 0xC5, 0x13, 0x06, 0x05, 0xFF, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0xCF, 0x3F, 0x7E, 0x9D, 0xF8, 0xC8, 0xC0, 0x0A, 0xBA, 0xD0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 }, + { 0xCF, 0x3F, 0x77, 0x09, 0xF8, 0xC2, 0xC0, 0x08, 0xB5, 0xEA, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x06 } }, + { { 0xCF, 0x3F, 0x4D, 0x9F, 0xF8, 0xC6, 0x00, 0x08, 0xDA, 0xAB, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0xCF, 0x3F, 0x47, 0x06, 0xF8, 0xCD, 0x00, 0x07, 0x05, 0xB3, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 } }, + { { 0xCF, 0x3F, 0x5D, 0xAA, 0xF2, 0xC0, 0x8A, 0x67, 0x99, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x3F, 0x9A, 0x69, 0xF8, 0xCF, 0x88, 0x88, 0x48, 0xFA, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0xCF, 0x3F, 0x4A, 0xFD, 0xF8, 0xCF, 0x00, 0x59, 0xEA, 0xD8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x3F, 0x48, 0x06, 0xF8, 0xCF, 0x00, 0x54, 0x04, 0xF9, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x0F, 0x18, 0x0A, 0xFA, 0xAB, 0x06, 0x06, 0x06, 0x39, 0xF8, 0x0A, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x03, 0x18, 0x04, 0x09, 0xAC, 0x05, 0x07, 0x08, 0x07, 0xF8, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0xCF, 0x3F, 0x2B, 0xFC, 0xF8, 0xCC, 0xC4, 0x0B, 0xEA, 0xFB, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 }, + { 0xCF, 0x3F, 0x25, 0x06, 0xF8, 0xCC, 0xD7, 0x05, 0x02, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x10 } }, + { { 0x05, 0x1A, 0x04, 0x00, 0xF8, 0x12, 0x08, 0x0C, 0xEA, 0xE0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 }, + { 0x01, 0x00, 0x09, 0x08, 0x40, 0x13, 0x00, 0x2A, 0x0A, 0xD8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 } }, + { { 0x04, 0x19, 0x04, 0x00, 0xF8, 0x12, 0x08, 0x2C, 0xEA, 0xE0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 }, + { 0x04, 0x00, 0x07, 0x08, 0x40, 0x12, 0x00, 0x29, 0x08, 0xE0, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x04 } }, + { { 0x04, 0x0A, 0x04, 0x00, 0xD8, 0x01, 0x02, 0x0D, 0xFA, 0xE0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 }, + { 0x04, 0x00, 0x03, 0x09, 0x93, 0x02, 0x00, 0x28, 0x09, 0xE8, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x07 } }, + { { 0x15, 0x14, 0x05, 0x00, 0xF9, 0x01, 0x03, 0x5C, 0xE9, 0xD8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x05, 0x00, 0x03, 0x03, 0x49, 0x02, 0x00, 0x58, 0x08, 0xE0, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } }, + { { 0x10, 0x10, 0x05, 0x08, 0xF8, 0x01, 0x03, 0x0D, 0xEA, 0xE8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 }, + { 0x10, 0x00, 0x0C, 0x0C, 0x48, 0x02, 0x00, 0x08, 0xB9, 0xE0, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x05 } }, + { { 0x11, 0x00, 0x06, 0x87, 0xFB, 0x02, 0x40, 0x09, 0x59, 0xC0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x08 }, + { 0x15, 0x00, 0x04, 0x87, 0xFB, 0x02, 0x40, 0x09, 0x59, 0xD0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x08 } }, + { { 0x13, 0x26, 0x04, 0x6A, 0xFB, 0x01, 0x00, 0x08, 0x5A, 0xE0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x08 }, + { 0x12, 0x26, 0x03, 0x6A, 0xFB, 0x02, 0x00, 0x06, 0x5A, 0xC0, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x08 } }, + { { 0xCF, 0x4D, 0x0C, 0xAA, 0xA0, 0xC4, 0x00, 0x18, 0xF9, 0x90, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x4E, 0x05, 0xA6, 0xA0, 0xC6, 0x00, 0x16, 0xF8, 0x60, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0xCF, 0x4D, 0x0C, 0xAA, 0xA0, 0xC3, 0x00, 0x18, 0xF8, 0x98, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0xCF, 0x4E, 0x06, 0xAA, 0xA0, 0xC5, 0x00, 0x19, 0xF9, 0x90, 0x04, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0xCB, 0x3F, 0x8F, 0x00, 0xFA, 0xC5, 0x06, 0x98, 0xD6, 0xBB, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0D }, + { 0xC0, 0x00, 0xF0, 0x00, 0x00, 0xC0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x0D } }, + { { 0x0C, 0x18, 0x87, 0xB3, 0xFB, 0x19, 0x0B, 0x55, 0x75, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x0C, 0x18, 0x87, 0xB3, 0xFB, 0x1B, 0x10, 0x57, 0x75, 0xF8, 0x0E, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x05, 0x11, 0x15, 0x00, 0xC8, 0x02, 0x00, 0x08, 0x00, 0xA8, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x02, 0x11, 0x13, 0x00, 0xC8, 0x02, 0x00, 0x05, 0x00, 0x80, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0x04, 0x08, 0x15, 0x00, 0x90, 0x01, 0x00, 0x08, 0x00, 0xC0, 0x08, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 }, + { 0x03, 0x08, 0x14, 0x00, 0x90, 0x02, 0x00, 0x07, 0x00, 0xA8, 0x00, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x02 } }, + { { 0xDA, 0x00, 0x53, 0x30, 0xC0, 0x07, 0x10, 0x49, 0xC4, 0xDA, 0x03, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0xD2, 0x00, 0x56, 0x30, 0x90, 0x06, 0x00, 0x46, 0x56, 0x62, 0x09, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } }, + { { 0x1C, 0x00, 0x07, 0xBC, 0xC8, 0x0C, 0x0A, 0x0B, 0x6A, 0xF2, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 }, + { 0x18, 0x00, 0x07, 0xBC, 0x88, 0x09, 0x00, 0x0B, 0x6A, 0xBA, 0x0B, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x03 } }, + { { 0x0A, 0x0E, 0x7F, 0x00, 0xF9, 0x13, 0x16, 0x28, 0x03, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 }, + { 0x01, 0x0E, 0x54, 0x00, 0xF9, 0x15, 0x03, 0x27, 0x03, 0xF8, 0x06, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0x00 } } +}; +#endif + +static const byte g_gmPercussionInstrumentMap[128] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, @@ -495,9 +858,9 @@ static const byte gm_percussion_lookup[128] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; -static byte lookup_table[64][32]; +static byte g_volumeLookupTable[64][32]; -static const byte volume_table[] = { +static const byte g_volumeTable[] = { 0, 4, 7, 11, 13, 16, 18, 20, 22, 24, 26, 27, @@ -516,7 +879,7 @@ static const byte volume_table[] = { 62, 63, 63, 63 }; -static int lookup_volume(int a, int b) { +static int lookupVolume(int a, int b) { if (b == 0) return 0; @@ -529,32 +892,32 @@ static int lookup_volume(int a, int b) { if (b < 0) { if (a < 0) { - return lookup_table[-a][-b]; + return g_volumeLookupTable[-a][-b]; } else { - return -lookup_table[a][-b]; + return -g_volumeLookupTable[a][-b]; } } else { if (a < 0) { - return -lookup_table[-a][b]; + return -g_volumeLookupTable[-a][b]; } else { - return lookup_table[a][b]; + return g_volumeLookupTable[a][b]; } } } -static void create_lookup_table() { +static void createLookupTable() { int i, j; int sum; for (i = 0; i < 64; i++) { sum = i; for (j = 0; j < 32; j++) { - lookup_table[i][j] = sum >> 5; + g_volumeLookupTable[i][j] = sum >> 5; sum += i; } } for (i = 0; i < 64; i++) - lookup_table[i][0] = 0; + g_volumeLookupTable[i][0] = 0; } //////////////////////////////////////// @@ -584,58 +947,75 @@ public: // AudioStream API - bool isStereo() const { return false; } + bool isStereo() const { return _opl->isStereo(); } int getRate() const { return _mixer->getOutputRate(); } private: - bool _scummSmallHeader; // FIXME: This flag controls a special mode for SCUMM V3 games + bool _scummSmallHeader; // FIXME: This flag controls a special mode for SCUMM V3 games +#ifdef ENABLE_OPL3 + bool _opl3Mode; +#endif - FM_OPL *_opl; - byte *_adlib_reg_cache; + OPL::OPL *_opl; + byte *_regCache; +#ifdef ENABLE_OPL3 + byte *_regCacheSecondary; +#endif - int _adlib_timer_counter; + int _timerCounter; - uint16 channel_table_2[9]; - int _voice_index; - int _timer_p; - int _timer_q; - uint16 curnote_table[9]; + uint16 _channelTable2[9]; + int _voiceIndex; + int _timerIncrease; + int _timerThreshold; + uint16 _curNotTable[9]; AdLibVoice _voices[9]; AdLibPart _parts[32]; AdLibPercussionChannel _percussion; void generateSamples(int16 *buf, int len); void onTimer(); - void part_key_on(AdLibPart *part, AdLibInstrument *instr, byte note, byte velocity); - void part_key_off(AdLibPart *part, byte note); - - void adlib_key_off(int chan); - void adlib_note_on(int chan, byte note, int mod); - void adlib_note_on_ex(int chan, byte note, int mod); - int adlib_get_reg_value_param(int chan, byte data); - void adlib_setup_channel(int chan, AdLibInstrument *instr, byte vol_1, byte vol_2); - byte adlib_get_reg_value(byte reg) { - return _adlib_reg_cache[reg]; - } - void adlib_set_param(int channel, byte param, int value); - void adlib_key_onoff(int channel); - void adlib_write(byte reg, byte value); - void adlib_playnote(int channel, int note); - - AdLibVoice *allocate_voice(byte pri); - - void mc_off(AdLibVoice *voice); - - static void link_mc(AdLibPart *part, AdLibVoice *voice); - void mc_inc_stuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11); - void mc_init_stuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11, byte flags, - InstrumentExtra *ie); - - void struct10_init(Struct10 *s10, InstrumentExtra *ie); - static byte struct10_ontimer(Struct10 *s10, Struct11 *s11); - static void struct10_setup(Struct10 *s10); - static int random_nr(int a); - void mc_key_on(AdLibVoice *voice, AdLibInstrument *instr, byte note, byte velocity); + void partKeyOn(AdLibPart *part, const AdLibInstrument *instr, byte note, byte velocity, const AdLibInstrument *second, byte pan); + void partKeyOff(AdLibPart *part, byte note); + + void adlibKeyOff(int chan); + void adlibNoteOn(int chan, byte note, int mod); + void adlibNoteOnEx(int chan, byte note, int mod); + int adlibGetRegValueParam(int chan, byte data); + void adlibSetupChannel(int chan, const AdLibInstrument *instr, byte vol1, byte vol2); +#ifdef ENABLE_OPL3 + void adlibSetupChannelSecondary(int chan, const AdLibInstrument *instr, byte vol1, byte vol2, byte pan); +#endif + byte adlibGetRegValue(byte reg) { + return _regCache[reg]; + } +#ifdef ENABLE_OPL3 + byte adlibGetRegValueSecondary(byte reg) { + return _regCacheSecondary[reg]; + } +#endif + void adlibSetParam(int channel, byte param, int value, bool primary = true); + void adlibKeyOnOff(int channel); + void adlibWrite(byte reg, byte value); +#ifdef ENABLE_OPL3 + void adlibWriteSecondary(byte reg, byte value); +#endif + void adlibPlayNote(int channel, int note); + + AdLibVoice *allocateVoice(byte pri); + + void mcOff(AdLibVoice *voice); + + static void linkMc(AdLibPart *part, AdLibVoice *voice); + void mcIncStuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11); + void mcInitStuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11, byte flags, + const InstrumentExtra *ie); + + void struct10Init(Struct10 *s10, const InstrumentExtra *ie); + static byte struct10OnTimer(Struct10 *s10, Struct11 *s11); + static void struct10Setup(Struct10 *s10); + static int randomNr(int a); + void mcKeyOn(AdLibVoice *voice, const AdLibInstrument *instr, byte note, byte velocity, const AdLibInstrument *second, byte pan); }; // MidiChannel method implementations @@ -643,7 +1023,7 @@ private: void AdLibPart::init(MidiDriver_ADLIB *owner, byte channel) { _owner = owner; _channel = channel; - _pri_eff = 127; + _priEff = 127; programChange(0); } @@ -657,41 +1037,64 @@ void AdLibPart::send(uint32 b) { void AdLibPart::noteOff(byte note) { #ifdef DEBUG_ADLIB - debug(6, "%10d: noteOff(%d)", tick, note); + debug(6, "%10d: noteOff(%d)", g_tick, note); #endif - _owner->part_key_off(this, note); + _owner->partKeyOff(this, note); } void AdLibPart::noteOn(byte note, byte velocity) { #ifdef DEBUG_ADLIB - debug(6, "%10d: noteOn(%d,%d)", tick, note, velocity); + debug(6, "%10d: noteOn(%d,%d)", g_tick, note, velocity); +#endif + _owner->partKeyOn(this, &_partInstr, note, velocity, +#ifdef ENABLE_OPL3 + &_partInstrSecondary, +#else + NULL, #endif - _owner->part_key_on(this, &_part_instr, note, velocity); + _pan); } void AdLibPart::programChange(byte program) { if (program > 127) return; -/* + /* uint i; uint count = 0; - for (i = 0; i < ARRAYSIZE(map_gm_to_fm[0]); ++i) - count += map_gm_to_fm[program][i]; + for (i = 0; i < ARRAYSIZE(g_gmInstruments[0]); ++i) + count += g_gmInstruments[program][i]; if (!count) warning("No AdLib instrument defined for GM program %d", (int)program); -*/ + */ _program = program; - memcpy(&_part_instr, &map_gm_to_fm[program], sizeof(AdLibInstrument)); +#ifdef ENABLE_OPL3 + if (!_owner->_opl3Mode) { +#endif + memcpy(&_partInstr, &g_gmInstruments[program], sizeof(AdLibInstrument)); +#ifdef ENABLE_OPL3 + } else { + memcpy(&_partInstr, &g_gmInstrumentsOPL3[program][0], sizeof(AdLibInstrument)); + memcpy(&_partInstrSecondary, &g_gmInstrumentsOPL3[program][1], sizeof(AdLibInstrument)); + } +#endif } void AdLibPart::pitchBend(int16 bend) { AdLibVoice *voice; - _pitchbend = bend; + _pitchBend = bend; for (voice = _voice; voice; voice = voice->_next) { - _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, - (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); +#ifdef ENABLE_OPL3 + if (!_owner->_opl3Mode) { +#endif + _owner->adlibNoteOn(voice->_channel, voice->_note/* + _transposeEff*/, + (_pitchBend * _pitchBendFactor >> 6) + _detuneEff); +#ifdef ENABLE_OPL3 + } else { + _owner->adlibNoteOn(voice->_channel, voice->_note, _pitchBend >> 1); + } +#endif } } @@ -699,75 +1102,137 @@ void AdLibPart::controlChange(byte control, byte value) { switch (control) { case 0: case 32: - break; // Bank select. Not supported - case 1: modulationWheel(value); break; - case 7: volume(value); break; - case 10: break; // Pan position. Not supported. - case 16: pitchBendFactor(value); break; - case 17: detune(value); break; - case 18: priority(value); break; - case 64: sustain(value > 0); break; - case 91: break; // Effects level. Not supported. - case 93: break; // Chorus level. Not supported. - case 119: break; // Unknown, used in Simon the Sorcerer 2 - case 121: // reset all controllers + // Bank select. Not supported + break; + case 1: + modulationWheel(value); + break; + case 7: + volume(value); + break; + case 10: + panPosition(value); + break; + case 16: + pitchBendFactor(value); + break; + case 17: + detune(value); + break; + case 18: + priority(value); + break; + case 64: + sustain(value > 0); + break; + case 91: + // Effects level. Not supported. + break; + case 93: + // Chorus level. Not supported. + break; + case 119: + // Unknown, used in Simon the Sorcerer 2 + break; + case 121: + // reset all controllers modulationWheel(0); pitchBendFactor(0); detune(0); sustain(0); break; - case 123: allNotesOff(); break; + case 123: + allNotesOff(); + break; default: - warning("AdLib: Unknown control change message %d (%d)", (int) control, (int)value); + warning("AdLib: Unknown control change message %d (%d)", (int)control, (int)value); } } void AdLibPart::modulationWheel(byte value) { AdLibVoice *voice; - _modwheel = value; + _modWheel = value; for (voice = _voice; voice; voice = voice->_next) { if (voice->_s10a.active && voice->_s11a.flag0x40) - voice->_s10a.modwheel = _modwheel >> 2; + voice->_s10a.modWheel = _modWheel >> 2; if (voice->_s10b.active && voice->_s11b.flag0x40) - voice->_s10b.modwheel = _modwheel >> 2; + voice->_s10b.modWheel = _modWheel >> 2; } } void AdLibPart::volume(byte value) { AdLibVoice *voice; - _vol_eff = value; + _volEff = value; for (voice = _voice; voice; voice = voice->_next) { - _owner->adlib_set_param(voice->_channel, 0, volume_table[lookup_table[voice->_vol_2][_vol_eff >> 2]]); - if (voice->_twochan) { - _owner->adlib_set_param(voice->_channel, 13, volume_table[lookup_table[voice->_vol_1][_vol_eff >> 2]]); +#ifdef ENABLE_OPL3 + if (!_owner->_opl3Mode) { +#endif + _owner->adlibSetParam(voice->_channel, 0, g_volumeTable[g_volumeLookupTable[voice->_vol2][_volEff >> 2]]); + if (voice->_twoChan) { + _owner->adlibSetParam(voice->_channel, 13, g_volumeTable[g_volumeLookupTable[voice->_vol1][_volEff >> 2]]); + } +#ifdef ENABLE_OPL3 + } else { + _owner->adlibSetParam(voice->_channel, 0, g_volumeTable[((voice->_vol2 + 1) * _volEff) >> 7], true); + _owner->adlibSetParam(voice->_channel, 0, g_volumeTable[((voice->_secVol2 + 1) * _volEff) >> 7], false); + if (voice->_twoChan) { + _owner->adlibSetParam(voice->_channel, 13, g_volumeTable[((voice->_vol1 + 1) * _volEff) >> 7], true); + } + if (voice->_secTwoChan) { + _owner->adlibSetParam(voice->_channel, 13, g_volumeTable[((voice->_secVol1 + 1) * _volEff) >> 7], false); + } } +#endif } } +void AdLibPart::panPosition(byte value) { + _pan = value; +} + void AdLibPart::pitchBendFactor(byte value) { +#ifdef ENABLE_OPL3 + // Not supported in OPL3 mode. + if (_owner->_opl3Mode) { + return; + } +#endif + AdLibVoice *voice; - _pitchbend_factor = value; + _pitchBendFactor = value; for (voice = _voice; voice; voice = voice->_next) { - _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, - (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); + _owner->adlibNoteOn(voice->_channel, voice->_note/* + _transposeEff*/, + (_pitchBend * _pitchBendFactor >> 6) + _detuneEff); } } void AdLibPart::detune(byte value) { + // Sam&Max's OPL3 driver uses this for a completly different purpose. It + // is related to voice allocation. We ignore this for now. + // TODO: We probably need to look how the interpreter side of Sam&Max's + // iMuse version handles all this too. Implementing the driver side here + // would be not that hard. +#ifdef ENABLE_OPL3 + if (_owner->_opl3Mode) { + //_maxNotes = value; + return; + } +#endif + AdLibVoice *voice; - _detune_eff = value; + _detuneEff = value; for (voice = _voice; voice; voice = voice->_next) { - _owner->adlib_note_on(voice->_channel, voice->_note + _transpose_eff, - (_pitchbend * _pitchbend_factor >> 6) + _detune_eff); + _owner->adlibNoteOn(voice->_channel, voice->_note/* + _transposeEff*/, + (_pitchBend * _pitchBendFactor >> 6) + _detuneEff); } } void AdLibPart::priority(byte value) { - _pri_eff = value; + _priEff = value; } void AdLibPart::sustain(bool value) { @@ -776,20 +1241,29 @@ void AdLibPart::sustain(bool value) { _pedal = value; if (!value) { for (voice = _voice; voice; voice = voice->_next) { - if (voice->_waitforpedal) - _owner->mc_off(voice); + if (voice->_waitForPedal) + _owner->mcOff(voice); } } } void AdLibPart::allNotesOff() { while (_voice) - _owner->mc_off(_voice); + _owner->mcOff(_voice); } void AdLibPart::sysEx_customInstrument(uint32 type, const byte *instr) { + // Sam&Max allows for instrument overwrites, but we will not support it + // until we can find any track actually using it. +#ifdef ENABLE_OPL3 + if (_owner->_opl3Mode) { + warning("AdLibPart::sysEx_customInstrument: Used in OPL3 mode"); + return; + } +#endif + if (type == 'ADL ') { - memcpy(&_part_instr, instr, sizeof(AdLibInstrument)); + memcpy(&_partInstr, instr, sizeof(AdLibInstrument)); } } @@ -803,8 +1277,8 @@ AdLibPercussionChannel::~AdLibPercussionChannel() { void AdLibPercussionChannel::init(MidiDriver_ADLIB *owner, byte channel) { AdLibPart::init(owner, channel); - _pri_eff = 0; - _vol_eff = 127; + _priEff = 0; + _volEff = 127; // Initialize the custom instruments data memset(_notes, 0, sizeof(_notes)); @@ -812,33 +1286,49 @@ void AdLibPercussionChannel::init(MidiDriver_ADLIB *owner, byte channel) { } void AdLibPercussionChannel::noteOff(byte note) { - // Jamieson630: Unless I run into a specific instrument that - // may require a key off, I'm going to ignore this message. - // The rationale is that a percussion instrument should - // fade out of its own accord, and the AdLib instrument - // definitions used should follow this rule. Since - // percussion voices are allocated at the lowest priority - // anyway, we know that "hanging" percussion sounds will - // not prevent later musical instruments (or even other - // percussion sounds) from playing. -/* - _owner->part_key_off(this, note); -*/ + if (_customInstruments[note]) { + note = _notes[note]; + } + + // This used to ignore note off events, since the builtin percussion + // instrument data has a duration value, which causes the percussion notes + // to stop automatically. This is not the case for (Groovie's) custom + // percussion instruments though. Also the OPL3 driver of Sam&Max actually + // does not handle the duration value, so we need it there too. + _owner->partKeyOff(this, note); } void AdLibPercussionChannel::noteOn(byte note, byte velocity) { - AdLibInstrument *inst = NULL; + const AdLibInstrument *inst = NULL; + const AdLibInstrument *sec = NULL; // The custom instruments have priority over the default mapping - inst = _customInstruments[note]; - if (inst) - note = _notes[note]; + // We do not support custom instruments in OPL3 mode though. +#ifdef ENABLE_OPL3 + if (!_owner->_opl3Mode) { +#endif + inst = _customInstruments[note]; + if (inst) + note = _notes[note]; +#ifdef ENABLE_OPL3 + } +#endif if (!inst) { - // Use the default GM to FM mapping as a fallback as a fallback - byte key = gm_percussion_lookup[note]; - if (key != 0xFF) - inst = &gm_percussion_to_fm[key]; + // Use the default GM to FM mapping as a fallback + byte key = g_gmPercussionInstrumentMap[note]; + if (key != 0xFF) { +#ifdef ENABLE_OPL3 + if (!_owner->_opl3Mode) { +#endif + inst = &g_gmPercussionInstruments[key]; +#ifdef ENABLE_OPL3 + } else { + inst = &g_gmPercussionInstrumentsOPL3[key][0]; + sec = &g_gmPercussionInstrumentsOPL3[key][1]; + } +#endif + } } if (!inst) { @@ -846,10 +1336,18 @@ void AdLibPercussionChannel::noteOn(byte note, byte velocity) { return; } - _owner->part_key_on(this, inst, note, velocity); + _owner->partKeyOn(this, inst, note, velocity, sec, _pan); } void AdLibPercussionChannel::sysEx_customInstrument(uint32 type, const byte *instr) { + // We do not allow custom instruments in OPL3 mode right now. +#ifdef ENABLE_OPL3 + if (_owner->_opl3Mode) { + warning("AdLibPercussionChannel::sysEx_customInstrument: Used in OPL3 mode"); + return; + } +#endif + if (type == 'ADLP') { byte note = instr[0]; _notes[note] = instr[1]; @@ -861,16 +1359,16 @@ void AdLibPercussionChannel::sysEx_customInstrument(uint32 type, const byte *ins } // Save the new instrument data - _customInstruments[note]->mod_characteristic = instr[2]; - _customInstruments[note]->mod_scalingOutputLevel = instr[3]; - _customInstruments[note]->mod_attackDecay = instr[4]; - _customInstruments[note]->mod_sustainRelease = instr[5]; - _customInstruments[note]->mod_waveformSelect = instr[6]; - _customInstruments[note]->car_characteristic = instr[7]; - _customInstruments[note]->car_scalingOutputLevel = instr[8]; - _customInstruments[note]->car_attackDecay = instr[9]; - _customInstruments[note]->car_sustainRelease = instr[10]; - _customInstruments[note]->car_waveformSelect = instr[11]; + _customInstruments[note]->modCharacteristic = instr[2]; + _customInstruments[note]->modScalingOutputLevel = instr[3]; + _customInstruments[note]->modAttackDecay = instr[4]; + _customInstruments[note]->modSustainRelease = instr[5]; + _customInstruments[note]->modWaveformSelect = instr[6]; + _customInstruments[note]->carCharacteristic = instr[7]; + _customInstruments[note]->carScalingOutputLevel = instr[8]; + _customInstruments[note]->carAttackDecay = instr[9]; + _customInstruments[note]->carSustainRelease = instr[10]; + _customInstruments[note]->carWaveformSelect = instr[11]; _customInstruments[note]->feedback = instr[12]; } } @@ -882,21 +1380,28 @@ MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer) uint i; _scummSmallHeader = false; +#ifdef ENABLE_OPL3 + _opl3Mode = false; +#endif - _adlib_reg_cache = 0; + _regCache = 0; +#ifdef ENABLE_OPL3 + _regCacheSecondary = 0; +#endif - _adlib_timer_counter = 0; - _voice_index = 0; - for (i = 0; i < ARRAYSIZE(curnote_table); ++i) { - curnote_table[i] = 0; + _timerCounter = 0; + _voiceIndex = -1; + for (i = 0; i < ARRAYSIZE(_curNotTable); ++i) { + _curNotTable[i] = 0; } for (i = 0; i < ARRAYSIZE(_parts); ++i) { _parts[i].init(this, i + ((i >= 9) ? 1 : 0)); } _percussion.init(this, 9); - _timer_p = 0xD69; - _timer_q = 0x411B; + _timerIncrease = 0xD69; + _timerThreshold = 0x411B; + _opl = 0; } int MidiDriver_ADLIB::open() { @@ -914,14 +1419,37 @@ int MidiDriver_ADLIB::open() { voice->_s11b.s10 = &voice->_s10a; } - _adlib_reg_cache = (byte *)calloc(256, 1); + // Try to use OPL3 when requested. +#ifdef ENABLE_OPL3 + if (_opl3Mode) { + _opl = OPL::Config::create(OPL::Config::kOpl3); + } - _opl = makeAdLibOPL(getRate()); + // Initialize plain OPL2 when no OPL3 is intiailized already. + if (!_opl) { +#endif + _opl = OPL::Config::create(); +#ifdef ENABLE_OPL3 + _opl3Mode = false; + } +#endif + _opl->init(getRate()); - adlib_write(1, 0x20); - adlib_write(8, 0x40); - adlib_write(0xBD, 0x00); - create_lookup_table(); + _regCache = (byte *)calloc(256, 1); + + adlibWrite(8, 0x40); + adlibWrite(0xBD, 0x00); +#ifdef ENABLE_OPL3 + if (!_opl3Mode) { +#endif + adlibWrite(1, 0x20); + createLookupTable(); +#ifdef ENABLE_OPL3 + } else { + _regCacheSecondary = (byte *)calloc(256, 1); + adlibWriteSecondary(5, 1); + } +#endif _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); @@ -938,14 +1466,17 @@ void MidiDriver_ADLIB::close() { uint i; for (i = 0; i < ARRAYSIZE(_voices); ++i) { if (_voices[i]._part) - mc_off(&_voices[i]); + mcOff(&_voices[i]); } // Turn off the OPL emulation - OPLDestroy(_opl); -// YM3812Shutdown(); + delete _opl; + _opl = 0; - free(_adlib_reg_cache); + free(_regCache); +#ifdef ENABLE_OPL3 + free(_regCacheSecondary); +#endif } void MidiDriver_ADLIB::send(uint32 b) { @@ -954,9 +1485,9 @@ void MidiDriver_ADLIB::send(uint32 b) { void MidiDriver_ADLIB::send(byte chan, uint32 b) { //byte param3 = (byte) ((b >> 24) & 0xFF); - byte param2 = (byte) ((b >> 16) & 0xFF); - byte param1 = (byte) ((b >> 8) & 0xFF); - byte cmd = (byte) (b & 0xF0); + byte param2 = (byte)((b >> 16) & 0xFF); + byte param1 = (byte)((b >> 8) & 0xFF); + byte cmd = (byte)(b & 0xF0); AdLibPart *part; if (chan == 9) @@ -997,29 +1528,42 @@ void MidiDriver_ADLIB::send(byte chan, uint32 b) { uint32 MidiDriver_ADLIB::property(int prop, uint32 param) { switch (prop) { - case PROP_OLD_ADLIB: // Older games used a different operator volume algorithm - _scummSmallHeader = (param > 0); - if (_scummSmallHeader) { - _timer_p = 473; - _timer_q = 1000; - } else { - _timer_p = 0xD69; - _timer_q = 0x411B; - } - return 1; + case PROP_OLD_ADLIB: // Older games used a different operator volume algorithm + _scummSmallHeader = (param > 0); + if (_scummSmallHeader) { + _timerIncrease = 473; + _timerThreshold = 1000; + } else { + _timerIncrease = 0xD69; + _timerThreshold = 0x411B; + } + return 1; + + case PROP_SCUMM_OPL3: // Sam&Max OPL3 support. +#ifdef ENABLE_OPL3 + _opl3Mode = (param > 0); +#endif + return 1; } return 0; } void MidiDriver_ADLIB::setPitchBendRange(byte channel, uint range) { +#ifdef ENABLE_OPL3 + // Not supported in OPL3 mode. + if (_opl3Mode) { + return; + } +#endif + AdLibVoice *voice; AdLibPart *part = &_parts[channel]; - part->_pitchbend_factor = range; + part->_pitchBendFactor = range; for (voice = part->_voice; voice; voice = voice->_next) { - adlib_note_on(voice->_channel, voice->_note + part->_transpose_eff, - (part->_pitchbend * part->_pitchbend_factor >> 6) + part->_detune_eff); + adlibNoteOn(voice->_channel, voice->_note/* + part->_transposeEff*/, + (part->_pitchBend * part->_pitchBendFactor >> 6) + part->_detuneEff); } } @@ -1043,54 +1587,77 @@ MidiChannel *MidiDriver_ADLIB::allocateChannel() { // All the code brought over from IMuseAdLib -void MidiDriver_ADLIB::adlib_write(byte reg, byte value) { - if (_adlib_reg_cache[reg] == value) +void MidiDriver_ADLIB::adlibWrite(byte reg, byte value) { + if (_regCache[reg] == value) { return; + } #ifdef DEBUG_ADLIB - debug(6, "%10d: adlib_write[%x] = %x", tick, reg, value); + debug(6, "%10d: adlibWrite[%x] = %x", g_tick, reg, value); #endif - _adlib_reg_cache[reg] = value; + _regCache[reg] = value; - OPLWriteReg(_opl, reg, value); + _opl->writeReg(reg, value); } +#ifdef ENABLE_OPL3 +void MidiDriver_ADLIB::adlibWriteSecondary(byte reg, byte value) { + assert(_opl3Mode); + + if (_regCacheSecondary[reg] == value) { + return; + } +#ifdef DEBUG_ADLIB + debug(6, "%10d: adlibWriteSecondary[%x] = %x", g_tick, reg, value); +#endif + _regCacheSecondary[reg] = value; + + _opl->writeReg(reg | 0x100, value); +} +#endif + void MidiDriver_ADLIB::generateSamples(int16 *data, int len) { - memset(data, 0, sizeof(int16) * len); - YM3812UpdateOne(_opl, data, len); + if (_opl->isStereo()) { + len *= 2; + } + _opl->readBuffer(data, len); } void MidiDriver_ADLIB::onTimer() { - AdLibVoice *voice; - int i; - - _adlib_timer_counter += _timer_p; - while (_adlib_timer_counter >= _timer_q) { - _adlib_timer_counter -= _timer_q; + _timerCounter += _timerIncrease; + while (_timerCounter >= _timerThreshold) { + _timerCounter -= _timerThreshold; #ifdef DEBUG_ADLIB - tick++; + g_tick++; #endif - voice = _voices; - for (i = 0; i != ARRAYSIZE(_voices); i++, voice++) { - if (!voice->_part) - continue; - if (voice->_duration && (voice->_duration -= 0x11) <= 0) { - mc_off(voice); - return; - } - if (voice->_s10a.active) { - mc_inc_stuff(voice, &voice->_s10a, &voice->_s11a); - } - if (voice->_s10b.active) { - mc_inc_stuff(voice, &voice->_s10b, &voice->_s11b); + // Sam&Max's OPL3 driver does not have any timer handling like this. +#ifdef ENABLE_OPL3 + if (!_opl3Mode) { +#endif + AdLibVoice *voice = _voices; + for (int i = 0; i != ARRAYSIZE(_voices); i++, voice++) { + if (!voice->_part) + continue; + if (voice->_duration && (voice->_duration -= 0x11) <= 0) { + mcOff(voice); + return; + } + if (voice->_s10a.active) { + mcIncStuff(voice, &voice->_s10a, &voice->_s11a); + } + if (voice->_s10b.active) { + mcIncStuff(voice, &voice->_s10b, &voice->_s11b); + } } +#ifdef ENABLE_OPL3 } +#endif } } -void MidiDriver_ADLIB::mc_off(AdLibVoice *voice) { +void MidiDriver_ADLIB::mcOff(AdLibVoice *voice) { AdLibVoice *tmp; - adlib_key_off(voice->_channel); + adlibKeyOff(voice->_channel); tmp = voice->_prev; @@ -1103,57 +1670,62 @@ void MidiDriver_ADLIB::mc_off(AdLibVoice *voice) { voice->_part = NULL; } -void MidiDriver_ADLIB::mc_inc_stuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11) { +void MidiDriver_ADLIB::mcIncStuff(AdLibVoice *voice, Struct10 *s10, Struct11 *s11) { byte code; AdLibPart *part = voice->_part; - code = struct10_ontimer(s10, s11); + code = struct10OnTimer(s10, s11); if (code & 1) { switch (s11->param) { case 0: - voice->_vol_2 = s10->start_value + s11->modify_val; + voice->_vol2 = s10->startValue + s11->modifyVal; if (!_scummSmallHeader) { - adlib_set_param(voice->_channel, 0, - volume_table[lookup_table[voice->_vol_2] - [part->_vol_eff >> 2]]); + adlibSetParam(voice->_channel, 0, + g_volumeTable[g_volumeLookupTable[voice->_vol2] + [part->_volEff >> 2]]); } else { - adlib_set_param(voice->_channel, 0, voice->_vol_2); + adlibSetParam(voice->_channel, 0, voice->_vol2); } break; case 13: - voice->_vol_1 = s10->start_value + s11->modify_val; - if (voice->_twochan && !_scummSmallHeader) { - adlib_set_param(voice->_channel, 13, - volume_table[lookup_table[voice->_vol_1] - [part->_vol_eff >> 2]]); + voice->_vol1 = s10->startValue + s11->modifyVal; + if (voice->_twoChan && !_scummSmallHeader) { + adlibSetParam(voice->_channel, 13, + g_volumeTable[g_volumeLookupTable[voice->_vol1] + [part->_volEff >> 2]]); } else { - adlib_set_param(voice->_channel, 13, voice->_vol_1); + adlibSetParam(voice->_channel, 13, voice->_vol1); } break; case 30: - s11->s10->modwheel = (char)s11->modify_val; + s11->s10->modWheel = (char)s11->modifyVal; break; case 31: - s11->s10->unk3 = (char)s11->modify_val; + s11->s10->unk3 = (char)s11->modifyVal; break; default: - adlib_set_param(voice->_channel, s11->param, - s10->start_value + s11->modify_val); + adlibSetParam(voice->_channel, s11->param, + s10->startValue + s11->modifyVal); break; } } if (code & 2 && s11->flag0x10) - adlib_key_onoff(voice->_channel); + adlibKeyOnOff(voice->_channel); } -void MidiDriver_ADLIB::adlib_key_off(int chan){ +void MidiDriver_ADLIB::adlibKeyOff(int chan) { byte reg = chan + 0xB0; - adlib_write(reg, adlib_get_reg_value(reg) & ~0x20); + adlibWrite(reg, adlibGetRegValue(reg) & ~0x20); +#ifdef ENABLE_OPL3 + if (_opl3Mode) { + adlibWriteSecondary(reg, adlibGetRegValueSecondary(reg) & ~0x20); + } +#endif } -byte MidiDriver_ADLIB::struct10_ontimer(Struct10 *s10, Struct11 *s11) { +byte MidiDriver_ADLIB::struct10OnTimer(Struct10 *s10, Struct11 *s11) { byte result = 0; int i; @@ -1162,51 +1734,54 @@ byte MidiDriver_ADLIB::struct10_ontimer(Struct10 *s10, Struct11 *s11) { return 0; } - i = s10->cur_val + s10->speed_hi; - s10->speed_lo_counter += s10->speed_lo; - if (s10->speed_lo_counter >= s10->speed_lo_max) { - s10->speed_lo_counter -= s10->speed_lo_max; + i = s10->curVal + s10->speedHi; + s10->speedLoCounter += s10->speedLo; + if (s10->speedLoCounter >= s10->speedLoMax) { + s10->speedLoCounter -= s10->speedLoMax; i += s10->direction; } - if (s10->cur_val != i || s10->modwheel != s10->modwheel_last) { - s10->cur_val = i; - s10->modwheel_last = s10->modwheel; - i = lookup_volume(i, s10->modwheel_last); - if (i != s11->modify_val) { - s11->modify_val = i; + if (s10->curVal != i || s10->modWheel != s10->modWheelLast) { + s10->curVal = i; + s10->modWheelLast = s10->modWheel; + i = lookupVolume(i, s10->modWheelLast); + if (i != s11->modifyVal) { + s11->modifyVal = i; result = 1; } } - if (!--s10->num_steps) { + if (!--s10->numSteps) { s10->active++; if (s10->active > 4) { if (s10->loop) { s10->active = 1; result |= 2; - struct10_setup(s10); + struct10Setup(s10); } else { s10->active = 0; } } else { - struct10_setup(s10); + struct10Setup(s10); } } return result; } -void MidiDriver_ADLIB::adlib_set_param(int channel, byte param, int value) { +void MidiDriver_ADLIB::adlibSetParam(int channel, byte param, int value, bool primary) { const AdLibSetParams *as; byte reg; assert(channel >= 0 && channel < 9); +#ifdef ENABLE_OPL3 + assert(!_opl3Mode || (param == 0 || param == 13)); +#endif if (param <= 12) { - reg = channel_mappings_2[channel]; + reg = g_operator2Offsets[channel]; } else if (param <= 25) { param -= 13; - reg = channel_mappings[channel]; + reg = g_operator1Offsets[channel]; } else if (param <= 27) { param -= 13; reg = channel; @@ -1216,54 +1791,66 @@ void MidiDriver_ADLIB::adlib_set_param(int channel, byte param, int value) { else value -= 383; value <<= 4; - channel_table_2[channel] = value; - adlib_playnote(channel, curnote_table[channel] + value); + _channelTable2[channel] = value; + adlibPlayNote(channel, _curNotTable[channel] + value); return; } else { return; } - as = &adlib_setparam_table[param]; - if (as->d) - value = as->d - value; - reg += as->a; - adlib_write(reg, (adlib_get_reg_value(reg) & ~as->c) | (((byte)value) << as->b)); + as = &g_setParamTable[param]; + if (as->inversion) + value = as->inversion - value; + reg += as->registerBase; +#ifdef ENABLE_OPL3 + if (primary) { +#endif + adlibWrite(reg, (adlibGetRegValue(reg) & ~as->mask) | (((byte)value) << as->shift)); +#ifdef ENABLE_OPL3 + } else { + adlibWriteSecondary(reg, (adlibGetRegValueSecondary(reg) & ~as->mask) | (((byte)value) << as->shift)); + } +#endif } -void MidiDriver_ADLIB::adlib_key_onoff(int channel) { +void MidiDriver_ADLIB::adlibKeyOnOff(int channel) { +#ifdef ENABLE_OPL3 + assert(!_opl3Mode); +#endif + byte val; byte reg = channel + 0xB0; assert(channel >= 0 && channel < 9); - val = adlib_get_reg_value(reg); - adlib_write(reg, val & ~0x20); - adlib_write(reg, val | 0x20); + val = adlibGetRegValue(reg); + adlibWrite(reg, val & ~0x20); + adlibWrite(reg, val | 0x20); } -void MidiDriver_ADLIB::struct10_setup(Struct10 *s10) { +void MidiDriver_ADLIB::struct10Setup(Struct10 *s10) { int b, c, d, e, f, g, h; byte t; b = s10->unk3; f = s10->active - 1; - t = s10->table_a[f]; - e = num_steps_table[lookup_table[t & 0x7F][b]]; + t = s10->tableA[f]; + e = g_numStepsTable[g_volumeLookupTable[t & 0x7F][b]]; if (t & 0x80) { - e = random_nr(e); + e = randomNr(e); } if (e == 0) e++; - s10->num_steps = s10->speed_lo_max = e; + s10->numSteps = s10->speedLoMax = e; if (f != 2) { - c = s10->max_value; - g = s10->start_value; - t = s10->table_b[f]; - d = lookup_volume(c, (t & 0x7F) - 31); + c = s10->maxValue; + g = s10->startValue; + t = s10->tableB[f]; + d = lookupVolume(c, (t & 0x7F) - 31); if (t & 0x80) { - d = random_nr(d); + d = randomNr(d); } if (d + g > c) { h = c - g; @@ -1272,12 +1859,12 @@ void MidiDriver_ADLIB::struct10_setup(Struct10 *s10) { if (d + g < 0) h = -g; } - h -= s10->cur_val; + h -= s10->curVal; } else { h = 0; } - s10->speed_hi = h / e; + s10->speedHi = h / e; if (h < 0) { h = -h; s10->direction = -1; @@ -1285,11 +1872,11 @@ void MidiDriver_ADLIB::struct10_setup(Struct10 *s10) { s10->direction = 1; } - s10->speed_lo = h % e; - s10->speed_lo_counter = 0; + s10->speedLo = h % e; + s10->speedLoCounter = 0; } -void MidiDriver_ADLIB::adlib_playnote(int channel, int note) { +void MidiDriver_ADLIB::adlibPlayNote(int channel, int note) { byte old, oct, notex; int note2; int i; @@ -1304,7 +1891,7 @@ void MidiDriver_ADLIB::adlib_playnote(int channel, int note) { oct <<= 2; notex = note2 % 12 + 3; - old = adlib_get_reg_value(channel + 0xB0); + old = adlibGetRegValue(channel + 0xB0); if (old & 0x20) { old &= ~0x20; if (oct > old) { @@ -1321,58 +1908,58 @@ void MidiDriver_ADLIB::adlib_playnote(int channel, int note) { } i = (notex << 3) + ((note >> 4) & 0x7); - adlib_write(channel + 0xA0, note_to_f_num[i]); - adlib_write(channel + 0xB0, oct | 0x20); + adlibWrite(channel + 0xA0, g_noteFrequencies[i]); + adlibWrite(channel + 0xB0, oct | 0x20); } -int MidiDriver_ADLIB::random_nr(int a) { - static byte _rand_seed = 1; - if (_rand_seed & 1) { - _rand_seed >>= 1; - _rand_seed ^= 0xB8; +int MidiDriver_ADLIB::randomNr(int a) { + static byte _randSeed = 1; + if (_randSeed & 1) { + _randSeed >>= 1; + _randSeed ^= 0xB8; } else { - _rand_seed >>= 1; + _randSeed >>= 1; } - return _rand_seed * a >> 8; + return _randSeed * a >> 8; } -void MidiDriver_ADLIB::part_key_off(AdLibPart *part, byte note) { +void MidiDriver_ADLIB::partKeyOff(AdLibPart *part, byte note) { AdLibVoice *voice; for (voice = part->_voice; voice; voice = voice->_next) { if (voice->_note == note) { if (part->_pedal) - voice->_waitforpedal = true; + voice->_waitForPedal = true; else - mc_off(voice); + mcOff(voice); } } } -void MidiDriver_ADLIB::part_key_on(AdLibPart *part, AdLibInstrument *instr, byte note, byte velocity) { +void MidiDriver_ADLIB::partKeyOn(AdLibPart *part, const AdLibInstrument *instr, byte note, byte velocity, const AdLibInstrument *second, byte pan) { AdLibVoice *voice; - voice = allocate_voice(part->_pri_eff); + voice = allocateVoice(part->_priEff); if (!voice) return; - link_mc(part, voice); - mc_key_on(voice, instr, note, velocity); + linkMc(part, voice); + mcKeyOn(voice, instr, note, velocity, second, pan); } -AdLibVoice *MidiDriver_ADLIB::allocate_voice(byte pri) { +AdLibVoice *MidiDriver_ADLIB::allocateVoice(byte pri) { AdLibVoice *ac, *best = NULL; int i; for (i = 0; i < 9; i++) { - if (++_voice_index >= 9) - _voice_index = 0; - ac = &_voices[_voice_index]; + if (++_voiceIndex >= 9) + _voiceIndex = 0; + ac = &_voices[_voiceIndex]; if (!ac->_part) return ac; if (!ac->_next) { - if (ac->_part->_pri_eff <= pri) { - pri = ac->_part->_pri_eff; + if (ac->_part->_priEff <= pri) { + pri = ac->_part->_priEff; best = ac; } } @@ -1383,11 +1970,11 @@ AdLibVoice *MidiDriver_ADLIB::allocate_voice(byte pri) { return NULL; if (best) - mc_off(best); + mcOff(best); return best; } -void MidiDriver_ADLIB::link_mc(AdLibPart *part, AdLibVoice *voice) { +void MidiDriver_ADLIB::linkMc(AdLibPart *part, AdLibVoice *voice) { voice->_part = part; voice->_next = (AdLibVoice *)part->_voice; part->_voice = voice; @@ -1397,153 +1984,229 @@ void MidiDriver_ADLIB::link_mc(AdLibPart *part, AdLibVoice *voice) { voice->_next->_prev = voice; } -void MidiDriver_ADLIB::mc_key_on(AdLibVoice *voice, AdLibInstrument *instr, byte note, byte velocity) { +void MidiDriver_ADLIB::mcKeyOn(AdLibVoice *voice, const AdLibInstrument *instr, byte note, byte velocity, const AdLibInstrument *second, byte pan) { AdLibPart *part = voice->_part; - int c; - byte vol_1, vol_2; + byte vol1, vol2; +#ifdef ENABLE_OPL3 + byte secVol1 = 0, secVol2 = 0; +#endif - voice->_twochan = instr->feedback & 1; + voice->_twoChan = instr->feedback & 1; voice->_note = note; - voice->_waitforpedal = false; + voice->_waitForPedal = false; voice->_duration = instr->duration; if (voice->_duration != 0) voice->_duration *= 63; - if (!_scummSmallHeader) - vol_1 = (instr->mod_scalingOutputLevel & 0x3F) + lookup_table[velocity >> 1][instr->mod_waveformSelect >> 2]; - else - vol_1 = 0x3f - (instr->mod_scalingOutputLevel & 0x3F); - if (vol_1 > 0x3F) - vol_1 = 0x3F; - voice->_vol_1 = vol_1; - - if (!_scummSmallHeader) - vol_2 = (instr->car_scalingOutputLevel & 0x3F) + lookup_table[velocity >> 1][instr->car_waveformSelect >> 2]; - else - vol_2 = 0x3f - (instr->car_scalingOutputLevel & 0x3F); - if (vol_2 > 0x3F) - vol_2 = 0x3F; - voice->_vol_2 = vol_2; + if (!_scummSmallHeader) { +#ifdef ENABLE_OPL3 + if (_opl3Mode) + vol1 = (instr->modScalingOutputLevel & 0x3F) + (velocity * ((instr->modWaveformSelect >> 3) + 1)) / 64; + else +#endif + vol1 = (instr->modScalingOutputLevel & 0x3F) + g_volumeLookupTable[velocity >> 1][instr->modWaveformSelect >> 2]; + } else { + vol1 = 0x3f - (instr->modScalingOutputLevel & 0x3F); + } + if (vol1 > 0x3F) + vol1 = 0x3F; + voice->_vol1 = vol1; - c = part->_vol_eff >> 2; + if (!_scummSmallHeader) { +#ifdef ENABLE_OPL3 + if (_opl3Mode) + vol2 = (instr->carScalingOutputLevel & 0x3F) + (velocity * ((instr->carWaveformSelect >> 3) + 1)) / 64; + else +#endif + vol2 = (instr->carScalingOutputLevel & 0x3F) + g_volumeLookupTable[velocity >> 1][instr->carWaveformSelect >> 2]; + } else { + vol2 = 0x3f - (instr->carScalingOutputLevel & 0x3F); + } + if (vol2 > 0x3F) + vol2 = 0x3F; + voice->_vol2 = vol2; + +#ifdef ENABLE_OPL3 + if (_opl3Mode) { + voice->_secTwoChan = second->feedback & 1; + secVol1 = (second->modScalingOutputLevel & 0x3F) + (velocity * ((second->modWaveformSelect >> 3) + 1)) / 64; + if (secVol1 > 0x3F) { + secVol1 = 0x3F; + } + voice->_secVol1 = secVol1; + secVol2 = (second->carScalingOutputLevel & 0x3F) + (velocity * ((second->carWaveformSelect >> 3) + 1)) / 64; + if (secVol2 > 0x3F) { + secVol2 = 0x3F; + } + voice->_secVol2 = secVol2; + } +#endif if (!_scummSmallHeader) { - vol_2 = volume_table[lookup_table[vol_2][c]]; - if (voice->_twochan) - vol_1 = volume_table[lookup_table[vol_1][c]]; +#ifdef ENABLE_OPL3 + if (!_opl3Mode) { +#endif + int c = part->_volEff >> 2; + vol2 = g_volumeTable[g_volumeLookupTable[vol2][c]]; + if (voice->_twoChan) + vol1 = g_volumeTable[g_volumeLookupTable[vol1][c]]; +#ifdef ENABLE_OPL3 + } else { + vol2 = g_volumeTable[((vol2 + 1) * part->_volEff) >> 7]; + secVol2 = g_volumeTable[((secVol2 + 1) * part->_volEff) >> 7]; + if (voice->_twoChan) + vol1 = g_volumeTable[((vol1 + 1) * part->_volEff) >> 7]; + if (voice->_secTwoChan) + secVol1 = g_volumeTable[((secVol1 + 1) * part->_volEff) >> 7]; + } +#endif } - adlib_setup_channel(voice->_channel, instr, vol_1, vol_2); - adlib_note_on_ex(voice->_channel, part->_transpose_eff + note, part->_detune_eff + (part->_pitchbend * part->_pitchbend_factor >> 6)); + adlibSetupChannel(voice->_channel, instr, vol1, vol2); +#ifdef ENABLE_OPL3 + if (!_opl3Mode) { +#endif + adlibNoteOnEx(voice->_channel, /*part->_transposeEff + */note, part->_detuneEff + (part->_pitchBend * part->_pitchBendFactor >> 6)); - if (instr->flags_a & 0x80) { - mc_init_stuff(voice, &voice->_s10a, &voice->_s11a, instr->flags_a, &instr->extra_a); - } else { - voice->_s10a.active = 0; - } + if (instr->flagsA & 0x80) { + mcInitStuff(voice, &voice->_s10a, &voice->_s11a, instr->flagsA, &instr->extraA); + } else { + voice->_s10a.active = 0; + } - if (instr->flags_b & 0x80) { - mc_init_stuff(voice, &voice->_s10b, &voice->_s11b, instr->flags_b, &instr->extra_b); + if (instr->flagsB & 0x80) { + mcInitStuff(voice, &voice->_s10b, &voice->_s11b, instr->flagsB, &instr->extraB); + } else { + voice->_s10b.active = 0; + } +#ifdef ENABLE_OPL3 } else { - voice->_s10b.active = 0; + adlibSetupChannelSecondary(voice->_channel, second, secVol1, secVol2, pan); + adlibNoteOnEx(voice->_channel, note, part->_pitchBend >> 1); } +#endif } -void MidiDriver_ADLIB::adlib_setup_channel(int chan, AdLibInstrument *instr, byte vol_1, byte vol_2) { - byte channel; - +void MidiDriver_ADLIB::adlibSetupChannel(int chan, const AdLibInstrument *instr, byte vol1, byte vol2) { assert(chan >= 0 && chan < 9); - channel = channel_mappings[chan]; - adlib_write(channel + 0x20, instr->mod_characteristic); - adlib_write(channel + 0x40, (instr->mod_scalingOutputLevel | 0x3F) - vol_1 ); - adlib_write(channel + 0x60, 0xff & (~instr->mod_attackDecay)); - adlib_write(channel + 0x80, 0xff & (~instr->mod_sustainRelease)); - adlib_write(channel + 0xE0, instr->mod_waveformSelect); - - channel = channel_mappings_2[chan]; - adlib_write(channel + 0x20, instr->car_characteristic); - adlib_write(channel + 0x40, (instr->car_scalingOutputLevel | 0x3F) - vol_2 ); - adlib_write(channel + 0x60, 0xff & (~instr->car_attackDecay)); - adlib_write(channel + 0x80, 0xff & (~instr->car_sustainRelease)); - adlib_write(channel + 0xE0, instr->car_waveformSelect); - - adlib_write((byte)chan + 0xC0, instr->feedback); + byte channel = g_operator1Offsets[chan]; + adlibWrite(channel + 0x20, instr->modCharacteristic); + adlibWrite(channel + 0x40, (instr->modScalingOutputLevel | 0x3F) - vol1); + adlibWrite(channel + 0x60, 0xff & (~instr->modAttackDecay)); + adlibWrite(channel + 0x80, 0xff & (~instr->modSustainRelease)); + adlibWrite(channel + 0xE0, instr->modWaveformSelect); + + channel = g_operator2Offsets[chan]; + adlibWrite(channel + 0x20, instr->carCharacteristic); + adlibWrite(channel + 0x40, (instr->carScalingOutputLevel | 0x3F) - vol2); + adlibWrite(channel + 0x60, 0xff & (~instr->carAttackDecay)); + adlibWrite(channel + 0x80, 0xff & (~instr->carSustainRelease)); + adlibWrite(channel + 0xE0, instr->carWaveformSelect); + + adlibWrite((byte)chan + 0xC0, instr->feedback +#ifdef ENABLE_OPL3 + | (_opl3Mode ? 0x30 : 0) +#endif + ); } -void MidiDriver_ADLIB::adlib_note_on_ex(int chan, byte note, int mod) { - int code; +#ifdef ENABLE_OPL3 +void MidiDriver_ADLIB::adlibSetupChannelSecondary(int chan, const AdLibInstrument *instr, byte vol1, byte vol2, byte pan) { assert(chan >= 0 && chan < 9); - code = (note << 7) + mod; - curnote_table[chan] = code; - channel_table_2[chan] = 0; - adlib_playnote(chan, code); + assert(_opl3Mode); + + byte channel = g_operator1Offsets[chan]; + adlibWriteSecondary(channel + 0x20, instr->modCharacteristic); + adlibWriteSecondary(channel + 0x40, (instr->modScalingOutputLevel | 0x3F) - vol1); + adlibWriteSecondary(channel + 0x60, 0xff & (~instr->modAttackDecay)); + adlibWriteSecondary(channel + 0x80, 0xff & (~instr->modSustainRelease)); + adlibWriteSecondary(channel + 0xE0, instr->modWaveformSelect); + + channel = g_operator2Offsets[chan]; + adlibWriteSecondary(channel + 0x20, instr->carCharacteristic); + adlibWriteSecondary(channel + 0x40, (instr->carScalingOutputLevel | 0x3F) - vol2); + adlibWriteSecondary(channel + 0x60, 0xff & (~instr->carAttackDecay)); + adlibWriteSecondary(channel + 0x80, 0xff & (~instr->carSustainRelease)); + adlibWriteSecondary(channel + 0xE0, instr->carWaveformSelect); + + // The original uses the following (strange) behavior: +#if 0 + if (instr->feedback | (pan > 64)) { + adlibWriteSecondary((byte)chan + 0xC0, 0x20); + } else { + adlibWriteSecondary((byte)chan + 0xC0, 0x10); + } +#else + adlibWriteSecondary((byte)chan + 0xC0, instr->feedback | ((pan > 64) ? 0x20 : 0x10)); +#endif } +#endif -void MidiDriver_ADLIB::mc_init_stuff(AdLibVoice *voice, Struct10 *s10, - Struct11 *s11, byte flags, InstrumentExtra *ie) { +void MidiDriver_ADLIB::mcInitStuff(AdLibVoice *voice, Struct10 *s10, + Struct11 *s11, byte flags, const InstrumentExtra *ie) { AdLibPart *part = voice->_part; - s11->modify_val = 0; + s11->modifyVal = 0; s11->flag0x40 = flags & 0x40; s10->loop = flags & 0x20; s11->flag0x10 = flags & 0x10; - s11->param = param_table_1[flags & 0xF]; - s10->max_value = maxval_table[flags & 0xF]; + s11->param = g_paramTable1[flags & 0xF]; + s10->maxValue = g_maxValTable[flags & 0xF]; s10->unk3 = 31; if (s11->flag0x40) { - s10->modwheel = part->_modwheel >> 2; + s10->modWheel = part->_modWheel >> 2; } else { - s10->modwheel = 31; + s10->modWheel = 31; } switch (s11->param) { case 0: - s10->start_value = voice->_vol_2; + s10->startValue = voice->_vol2; break; case 13: - s10->start_value = voice->_vol_1; + s10->startValue = voice->_vol1; break; case 30: - s10->start_value = 31; - s11->s10->modwheel = 0; + s10->startValue = 31; + s11->s10->modWheel = 0; break; case 31: - s10->start_value = 0; + s10->startValue = 0; s11->s10->unk3 = 0; break; default: - s10->start_value = adlib_get_reg_value_param(voice->_channel, s11->param); + s10->startValue = adlibGetRegValueParam(voice->_channel, s11->param); } - struct10_init(s10, ie); + struct10Init(s10, ie); } -void MidiDriver_ADLIB::struct10_init(Struct10 *s10, InstrumentExtra *ie) { +void MidiDriver_ADLIB::struct10Init(Struct10 *s10, const InstrumentExtra *ie) { s10->active = 1; if (!_scummSmallHeader) { - s10->cur_val = 0; + s10->curVal = 0; } else { - s10->cur_val = s10->start_value; - s10->start_value = 0; + s10->curVal = s10->startValue; + s10->startValue = 0; } - s10->modwheel_last = 31; + s10->modWheelLast = 31; s10->count = ie->a; if (s10->count) s10->count *= 63; - s10->table_a[0] = ie->b; - s10->table_a[1] = ie->d; - s10->table_a[2] = ie->f; - s10->table_a[3] = ie->g; + s10->tableA[0] = ie->b; + s10->tableA[1] = ie->d; + s10->tableA[2] = ie->f; + s10->tableA[3] = ie->g; - s10->table_b[0] = ie->c; - s10->table_b[1] = ie->e; - s10->table_b[2] = 0; - s10->table_b[3] = ie->h; + s10->tableB[0] = ie->c; + s10->tableB[1] = ie->e; + s10->tableB[2] = 0; + s10->tableB[3] = ie->h; - struct10_setup(s10); + struct10Setup(s10); } -int MidiDriver_ADLIB::adlib_get_reg_value_param(int chan, byte param) { +int MidiDriver_ADLIB::adlibGetRegValueParam(int chan, byte param) { const AdLibSetParams *as; byte val; byte channel; @@ -1551,10 +2214,10 @@ int MidiDriver_ADLIB::adlib_get_reg_value_param(int chan, byte param) { assert(chan >= 0 && chan < 9); if (param <= 12) { - channel = channel_mappings_2[chan]; + channel = g_operator2Offsets[chan]; } else if (param <= 25) { param -= 13; - channel = channel_mappings[chan]; + channel = g_operator1Offsets[chan]; } else if (param <= 27) { param -= 13; channel = chan; @@ -1566,24 +2229,52 @@ int MidiDriver_ADLIB::adlib_get_reg_value_param(int chan, byte param) { return 0; } - as = &adlib_setparam_table[param]; - val = adlib_get_reg_value(channel + as->a); - val &= as->c; - val >>= as->b; - if (as->d) - val = as->d - val; + as = &g_setParamTable[param]; + val = adlibGetRegValue(channel + as->registerBase); + val &= as->mask; + val >>= as->shift; + if (as->inversion) + val = as->inversion - val; return val; } -void MidiDriver_ADLIB::adlib_note_on(int chan, byte note, int mod) { - int code; +void MidiDriver_ADLIB::adlibNoteOn(int chan, byte note, int mod) { +#ifdef ENABLE_OPL3 + if (_opl3Mode) { + adlibNoteOnEx(chan, note, mod); + return; + } +#endif + assert(chan >= 0 && chan < 9); - code = (note << 7) + mod; - curnote_table[chan] = code; - adlib_playnote(chan, (int16) channel_table_2[chan] + code); + int code = (note << 7) + mod; + _curNotTable[chan] = code; + adlibPlayNote(chan, (int16)_channelTable2[chan] + code); } +void MidiDriver_ADLIB::adlibNoteOnEx(int chan, byte note, int mod) { + assert(chan >= 0 && chan < 9); + +#ifdef ENABLE_OPL3 + if (_opl3Mode) { + const int noteAdjusted = note + (mod >> 8) - 7; + const int pitchAdjust = (mod >> 5) & 7; + + adlibWrite(0xA0 + chan, g_noteFrequencies[(noteAdjusted % 12) * 8 + pitchAdjust + 6 * 8]); + adlibWriteSecondary(0xA0 + chan, g_noteFrequencies[(noteAdjusted % 12) * 8 + pitchAdjust + 6 * 8]); + adlibWrite(0xB0 + chan, (CLIP(noteAdjusted / 12, 0, 7) << 2) | 0x20); + adlibWriteSecondary(0xB0 + chan, (CLIP(noteAdjusted / 12, 0, 7) << 2) | 0x20); + } else { +#endif + int code = (note << 7) + mod; + _curNotTable[chan] = code; + _channelTable2[chan] = 0; + adlibPlayNote(chan, code); +#ifdef ENABLE_OPL3 + } +#endif +} // Plugin interface diff --git a/audio/softsynth/fluidsynth.cpp b/audio/softsynth/fluidsynth.cpp index 2451336784..518e260175 100644 --- a/audio/softsynth/fluidsynth.cpp +++ b/audio/softsynth/fluidsynth.cpp @@ -127,12 +127,54 @@ int MidiDriver_FluidSynth::open() { _synth = new_fluid_synth(_settings); - // In theory, this ought to reduce CPU load... but it doesn't make any - // noticeable difference for me, so disable it for now. + if (ConfMan.getBool("fluidsynth_chorus_activate")) { + fluid_synth_set_chorus_on(_synth, 1); + + int chorusNr = ConfMan.getInt("fluidsynth_chorus_nr"); + double chorusLevel = (double)ConfMan.getInt("fluidsynth_chorus_level") / 100.0; + double chorusSpeed = (double)ConfMan.getInt("fluidsynth_chorus_speed") / 100.0; + double chorusDepthMs = (double)ConfMan.getInt("fluidsynth_chorus_depth") / 10.0; + + Common::String chorusWaveForm = ConfMan.get("fluidsynth_chorus_waveform"); + int chorusType = FLUID_CHORUS_MOD_SINE; + if (chorusWaveForm == "sine") { + chorusType = FLUID_CHORUS_MOD_SINE; + } else { + chorusType = FLUID_CHORUS_MOD_TRIANGLE; + } + + fluid_synth_set_chorus(_synth, chorusNr, chorusLevel, chorusSpeed, chorusDepthMs, chorusType); + } else { + fluid_synth_set_chorus_on(_synth, 0); + } + + if (ConfMan.getBool("fluidsynth_reverb_activate")) { + fluid_synth_set_reverb_on(_synth, 1); + + double reverbRoomSize = (double)ConfMan.getInt("fluidsynth_reverb_roomsize") / 100.0; + double reverbDamping = (double)ConfMan.getInt("fluidsynth_reverb_damping") / 100.0; + int reverbWidth = ConfMan.getInt("fluidsynth_reverb_width"); + double reverbLevel = (double)ConfMan.getInt("fluidsynth_reverb_level") / 100.0; + + fluid_synth_set_reverb(_synth, reverbRoomSize, reverbDamping, reverbWidth, reverbLevel); + } else { + fluid_synth_set_reverb_on(_synth, 0); + } + + Common::String interpolation = ConfMan.get("fluidsynth_misc_interpolation"); + int interpMethod = FLUID_INTERP_4THORDER; + + if (interpolation == "none") { + interpMethod = FLUID_INTERP_NONE; + } else if (interpolation == "linear") { + interpMethod = FLUID_INTERP_LINEAR; + } else if (interpolation == "4th") { + interpMethod = FLUID_INTERP_4THORDER; + } else if (interpolation == "7th") { + interpMethod = FLUID_INTERP_7THORDER; + } - // fluid_synth_set_interp_method(_synth, -1, FLUID_INTERP_LINEAR); - // fluid_synth_set_reverb_on(_synth, 0); - // fluid_synth_set_chorus_on(_synth, 0); + fluid_synth_set_interp_method(_synth, -1, interpMethod); const char *soundfont = ConfMan.get("soundfont").c_str(); diff --git a/audio/softsynth/fmtowns_pc98/towns_midi.cpp b/audio/softsynth/fmtowns_pc98/towns_midi.cpp index b8203944c0..46b1a6406d 100644 --- a/audio/softsynth/fmtowns_pc98/towns_midi.cpp +++ b/audio/softsynth/fmtowns_pc98/towns_midi.cpp @@ -147,17 +147,10 @@ private: TownsMidiOutputChannel *_out; uint8 *_instrument; - uint8 _prg; uint8 _chanIndex; - uint8 _effectLevel; uint8 _priority; - uint8 _ctrlVolume; uint8 _tl; - uint8 _pan; - uint8 _panEff; - uint8 _percS; int8 _transpose; - uint8 _fld_1f; int8 _detune; int8 _modWheel; uint8 _sustain; @@ -659,9 +652,8 @@ const uint16 TownsMidiOutputChannel::_freqLSB[] = { 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B }; -TownsMidiInputChannel::TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex) : MidiChannel(), _driver(driver), _out(0), _prg(0), _chanIndex(chanIndex), - _effectLevel(0), _priority(0), _ctrlVolume(0), _tl(0), _pan(0), _panEff(0), _transpose(0), _percS(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0), - _fld_1f(0), _detune(0), _modWheel(0), _allocated(false) { +TownsMidiInputChannel::TownsMidiInputChannel(MidiDriver_TOWNS *driver, int chanIndex) : MidiChannel(), _driver(driver), _out(0), _chanIndex(chanIndex), + _priority(0), _tl(0), _transpose(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0), _detune(0), _modWheel(0), _allocated(false) { _instrument = new uint8[30]; memset(_instrument, 0, 30); } diff --git a/audio/softsynth/mt32.cpp b/audio/softsynth/mt32.cpp index 186118262f..00d0469356 100644 --- a/audio/softsynth/mt32.cpp +++ b/audio/softsynth/mt32.cpp @@ -25,6 +25,7 @@ #ifdef USE_MT32EMU #include "audio/softsynth/mt32/mt32emu.h" +#include "audio/softsynth/mt32/ROMInfo.h" #include "audio/softsynth/emumidi.h" #include "audio/musicplugin.h" @@ -47,6 +48,50 @@ #include "graphics/palette.h" #include "graphics/font.h" +#include "gui/message.h" + +namespace MT32Emu { + +class ReportHandlerScummVM : public ReportHandler { +friend class Synth; + +public: + virtual ~ReportHandlerScummVM() {} + +protected: + + // Callback for debug messages, in vprintf() format + void printDebug(const char *fmt, va_list list) { + debug(4, fmt, list); + } + + // Callbacks for reporting various errors and information + void onErrorControlROM() { + GUI::MessageDialog dialog("MT32emu: Init Error - Missing or invalid Control ROM image", "OK"); + dialog.runModal(); + error("MT32emu: Init Error - Missing or invalid Control ROM image"); + } + void onErrorPCMROM() { + GUI::MessageDialog dialog("MT32emu: Init Error - Missing PCM ROM image", "OK"); + dialog.runModal(); + error("MT32emu: Init Error - Missing PCM ROM image"); + } + void showLCDMessage(const char *message) { + g_system->displayMessageOnOSD(message); + } + void onDeviceReset() {} + void onDeviceReconfig() {} + void onNewReverbMode(Bit8u /* mode */) {} + void onNewReverbTime(Bit8u /* time */) {} + void onNewReverbLevel(Bit8u /* level */) {} + void onPartStateChanged(int /* partNum */, bool /* isActive */) {} + void onPolyStateChanged(int /* partNum */) {} + void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {} + void onProgramChanged(int /* partNum */, char * /* patchName */) {} +}; + +} // end of namespace MT32Emu + class MidiChannel_MT32 : public MidiChannel_MPU401 { void effectLevel(byte value) { } void chorusLevel(byte value) { } @@ -57,6 +102,10 @@ private: MidiChannel_MT32 _midiChannels[16]; uint16 _channelMask; MT32Emu::Synth *_synth; + MT32Emu::ReportHandlerScummVM *_reportHandler; + const MT32Emu::ROMImage *_controlROM, *_pcmROM; + Common::File *_controlFile, *_pcmFile; + void deleteMuntStructures(); int _outputRate; @@ -84,149 +133,6 @@ public: int getRate() const { return _outputRate; } }; -static int eatSystemEvents() { - Common::Event event; - Common::EventManager *eventMan = g_system->getEventManager(); - while (eventMan->pollEvent(event)) { - switch (event.type) { - case Common::EVENT_QUIT: - return 1; - default: - break; - } - } - return 0; -} - -static void drawProgress(float progress) { - const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kGUIFont)); - Graphics::Surface *screen = g_system->lockScreen(); - - assert(screen); - assert(screen->pixels); - - Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); - - int16 w = g_system->getWidth() / 7 * 5; - int16 h = font.getFontHeight(); - int16 x = g_system->getWidth() / 7; - int16 y = g_system->getHeight() / 2 - h / 2; - - Common::Rect r(x, y, x + w, y + h); - - uint32 col; - - if (screenFormat.bytesPerPixel > 1) - col = screenFormat.RGBToColor(0, 171, 0); - else - col = 1; - - screen->frameRect(r, col); - - r.grow(-1); - r.setWidth(uint16(progress * w)); - - if (screenFormat.bytesPerPixel > 1) - col = screenFormat.RGBToColor(171, 0, 0); - else - col = 2; - - screen->fillRect(r, col); - - g_system->unlockScreen(); - g_system->updateScreen(); -} - -static void drawMessage(int offset, const Common::String &text) { - const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kGUIFont)); - Graphics::Surface *screen = g_system->lockScreen(); - - assert(screen); - assert(screen->pixels); - - Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); - - uint16 h = font.getFontHeight(); - uint16 y = g_system->getHeight() / 2 - h / 2 + offset * (h + 1); - - uint32 col; - - if (screenFormat.bytesPerPixel > 1) - col = screenFormat.RGBToColor(0, 0, 0); - else - col = 0; - - Common::Rect r(0, y, screen->w, y + h); - screen->fillRect(r, col); - - if (screenFormat.bytesPerPixel > 1) - col = screenFormat.RGBToColor(0, 171, 0); - else - col = 1; - - font.drawString(screen, text, 0, y, screen->w, col, Graphics::kTextAlignCenter); - - g_system->unlockScreen(); - g_system->updateScreen(); -} - -static Common::File *MT32_OpenFile(void *userData, const char *filename) { - Common::File *file = new Common::File(); - if (!file->open(filename)) { - delete file; - return NULL; - } - return file; -} - -static void MT32_PrintDebug(void *userData, const char *fmt, va_list list) { - if (((MidiDriver_MT32 *)userData)->_initializing) { - char buf[512]; - - vsnprintf(buf, 512, fmt, list); - buf[70] = 0; // Truncate to a reasonable length - - drawMessage(1, buf); - } - - //vdebug(0, fmt, list); // FIXME: Use a higher debug level -} - -static int MT32_Report(void *userData, MT32Emu::ReportType type, const void *reportData) { - switch (type) { - case MT32Emu::ReportType_lcdMessage: - g_system->displayMessageOnOSD((const char *)reportData); - break; - case MT32Emu::ReportType_errorControlROM: - error("Failed to load MT32_CONTROL.ROM"); - break; - case MT32Emu::ReportType_errorPCMROM: - error("Failed to load MT32_PCM.ROM"); - break; - case MT32Emu::ReportType_progressInit: - if (((MidiDriver_MT32 *)userData)->_initializing) { - drawProgress(*((const float *)reportData)); - return eatSystemEvents(); - } - break; - case MT32Emu::ReportType_availableSSE: - debug(1, "MT32emu: SSE is available"); - break; - case MT32Emu::ReportType_usingSSE: - debug(1, "MT32emu: using SSE"); - break; - case MT32Emu::ReportType_available3DNow: - debug(1, "MT32emu: 3DNow! is available"); - break; - case MT32Emu::ReportType_using3DNow: - debug(1, "MT32emu: using 3DNow!"); - break; - default: - break; - } - return 0; -} - //////////////////////////////////////// // // MidiDriver_MT32 @@ -239,43 +145,51 @@ MidiDriver_MT32::MidiDriver_MT32(Audio::Mixer *mixer) : MidiDriver_Emulated(mixe for (i = 0; i < ARRAYSIZE(_midiChannels); ++i) { _midiChannels[i].init(this, i); } + _reportHandler = NULL; _synth = NULL; - // A higher baseFreq reduces the length used in generateSamples(), - // and means that the timer callback will be called more often. - // That results in more accurate timing. - _baseFreq = 10000; // Unfortunately bugs in the emulator cause inaccurate tuning // at rates other than 32KHz, thus we produce data at 32KHz and // rely on Mixer to convert. _outputRate = 32000; //_mixer->getOutputRate(); _initializing = false; + + // Initialized in open() + _controlROM = NULL; + _pcmROM = NULL; + _controlFile = NULL; + _pcmFile = NULL; } MidiDriver_MT32::~MidiDriver_MT32() { + deleteMuntStructures(); +} + +void MidiDriver_MT32::deleteMuntStructures() { delete _synth; + _synth = NULL; + delete _reportHandler; + _reportHandler = NULL; + + if (_controlROM) + MT32Emu::ROMImage::freeROMImage(_controlROM); + _controlROM = NULL; + if (_pcmROM) + MT32Emu::ROMImage::freeROMImage(_pcmROM); + _pcmROM = NULL; + + delete _controlFile; + _controlFile = NULL; + delete _pcmFile; + _pcmFile = NULL; } int MidiDriver_MT32::open() { - MT32Emu::SynthProperties prop; - if (_isOpen) return MERR_ALREADY_OPEN; MidiDriver_Emulated::open(); - - memset(&prop, 0, sizeof(prop)); - prop.sampleRate = getRate(); - prop.useReverb = true; - prop.useDefaultReverb = false; - prop.reverbType = 0; - prop.reverbTime = 5; - prop.reverbLevel = 3; - prop.userData = this; - prop.printDebug = MT32_PrintDebug; - prop.report = MT32_Report; - prop.openFile = MT32_OpenFile; - - _synth = new MT32Emu::Synth(); + _reportHandler = new MT32Emu::ReportHandlerScummVM(); + _synth = new MT32Emu::Synth(_reportHandler); Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); @@ -290,8 +204,16 @@ int MidiDriver_MT32::open() { } _initializing = true; - drawMessage(-1, _s("Initializing MT-32 Emulator")); - if (!_synth->open(prop)) + debug(4, _s("Initializing MT-32 Emulator")); + _controlFile = new Common::File(); + if (!_controlFile->open("MT32_CONTROL.ROM") && !_controlFile->open("CM32L_CONTROL.ROM")) + error("Error opening MT32_CONTROL.ROM / CM32L_CONTROL.ROM"); + _pcmFile = new Common::File(); + if (!_pcmFile->open("MT32_PCM.ROM") && !_pcmFile->open("CM32L_PCM.ROM")) + error("Error opening MT32_PCM.ROM / CM32L_PCM.ROM"); + _controlROM = MT32Emu::ROMImage::makeROMImage(_controlFile); + _pcmROM = MT32Emu::ROMImage::makeROMImage(_pcmFile); + if (!_synth->open(*_controlROM, *_pcmROM)) return MERR_DEVICE_NOT_AVAILABLE; double gain = (double)ConfMan.getInt("midi_gain") / 100.0; @@ -352,8 +274,7 @@ void MidiDriver_MT32::close() { _mixer->stopHandle(_mixerSoundHandle); _synth->close(); - delete _synth; - _synth = NULL; + deleteMuntStructures(); } void MidiDriver_MT32::generateSamples(int16 *data, int len) { diff --git a/audio/softsynth/mt32/AReverbModel.cpp b/audio/softsynth/mt32/AReverbModel.cpp index 4ee6c87943..1d63832157 100644 --- a/audio/softsynth/mt32/AReverbModel.cpp +++ b/audio/softsynth/mt32/AReverbModel.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -16,64 +16,97 @@ */ #include "mt32emu.h" -#include "AReverbModel.h" - -using namespace MT32Emu; - -// Default reverb settings for modes 0-2 - -static const unsigned int NUM_ALLPASSES = 6; -static const unsigned int NUM_DELAYS = 5; -static const Bit32u MODE_0_ALLPASSES[] = {729, 78, 394, 994, 1250, 1889}; -static const Bit32u MODE_0_DELAYS[] = {846, 4, 1819, 778, 346}; -static const float MODE_0_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.9f}; -static const float MODE_0_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f}; +#if MT32EMU_USE_REVERBMODEL == 1 -static const Bit32u MODE_1_ALLPASSES[] = {176, 809, 1324, 1258}; -static const Bit32u MODE_1_DELAYS[] = {2262, 124, 974, 2516, 356}; -static const float MODE_1_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.95f}; -static const float MODE_1_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 1.01575f}; - -static const Bit32u MODE_2_ALLPASSES[] = {78, 729, 994, 389}; -static const Bit32u MODE_2_DELAYS[] = {846, 4, 1819, 778, 346}; -static const float MODE_2_TIMES[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f}; -static const float MODE_2_LEVELS[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f}; - -const AReverbSettings AReverbModel::REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_DELAYS, MODE_0_TIMES, MODE_0_LEVELS, 0.687770909f, 0.5f, 0.5f}; -const AReverbSettings AReverbModel::REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_DELAYS, MODE_1_TIMES, MODE_1_LEVELS, 0.712025098f, 0.375f, 0.625f}; -const AReverbSettings AReverbModel::REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_DELAYS, MODE_2_TIMES, MODE_2_LEVELS, 0.939522749f, 0.0f, 0.0f}; +#include "AReverbModel.h" -RingBuffer::RingBuffer(Bit32u newsize) { - index = 0; - size = newsize; +// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that +// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF) +// and followed by three parallel comb filters + +namespace MT32Emu { + +// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay, +// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data. +// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level, +// so we can simply increase the input buffer size. +static const Bit32u PROCESS_DELAY = 1; + +// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different. +// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog). + +static const Bit32u NUM_ALLPASSES = 3; +static const Bit32u NUM_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be perfectly processed via a comb here. + +static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78}; +static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632}; +static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960}; +static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145}; +static const Bit32u MODE_0_COMB_FACTOR[] = {0x3C, 0x60, 0x60, 0x60}; +static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; +static const Bit32u MODE_0_LEVELS[] = {10*1, 10*3, 10*5, 10*7, 11*9, 11*12, 11*15, 13*15}; +static const Bit32u MODE_0_LPF_AMP = 6; + +static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176}; +static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519}; +static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518}; +static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274}; +static const Bit32u MODE_1_COMB_FACTOR[] = {0x30, 0x60, 0x60, 0x60}; +static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; +static const Bit32u MODE_1_LEVELS[] = {10*1, 10*3, 11*5, 11*7, 11*9, 11*12, 11*15, 14*15}; +static const Bit32u MODE_1_LPF_AMP = 6; + +static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157}; +static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539}; +static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769}; +static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1}; +static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20}; +static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0}; +static const Bit32u MODE_2_LEVELS[] = {10*1, 10*3, 11*5, 11*7, 11*9, 11*12, 12*15, 14*15}; +static const Bit32u MODE_2_LPF_AMP = 8; + +static const AReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_ALLPASSES, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_LEVELS, MODE_0_LPF_AMP}; +static const AReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_ALLPASSES, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_LEVELS, MODE_1_LPF_AMP}; +static const AReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_ALLPASSES, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_LEVELS, MODE_2_LPF_AMP}; + +static const AReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_0_SETTINGS}; + +RingBuffer::RingBuffer(const Bit32u newsize) : size(newsize), index(0) { buffer = new float[size]; } RingBuffer::~RingBuffer() { delete[] buffer; buffer = NULL; - size = 0; } float RingBuffer::next() { - index++; - if (index >= size) { + if (++index >= size) { index = 0; } return buffer[index]; } -bool RingBuffer::isEmpty() { +bool RingBuffer::isEmpty() const { if (buffer == NULL) return true; float *buf = buffer; - float total = 0; + float max = 0.001f; for (Bit32u i = 0; i < size; i++) { - total += (*buf < 0 ? -*buf : *buf); + if ((*buf < -max) || (*buf > max)) return false; buf++; } - return ((total / size) < .0002 ? true : false); + return true; } void RingBuffer::mute() { @@ -83,59 +116,66 @@ void RingBuffer::mute() { } } -AllpassFilter::AllpassFilter(Bit32u useSize) : RingBuffer(useSize) { -} - -Delay::Delay(Bit32u useSize) : RingBuffer(useSize) { -} +AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} -float AllpassFilter::process(float in) { - // This model corresponds to the allpass filter implementation in the real CM-32L device +float AllpassFilter::process(const float in) { + // This model corresponds to the allpass filter implementation of the real CM-32L device // found from sample analysis - float out; - - out = next(); + const float bufferOut = next(); // store input - feedback / 2 - buffer[index] = in - 0.5f * out; + buffer[index] = in - 0.5f * bufferOut; // return buffer output + feedforward / 2 - return out + 0.5f * buffer[index]; + return bufferOut + 0.5f * buffer[index]; } -float Delay::process(float in) { - // Implements a very simple delay +CombFilter::CombFilter(const Bit32u useSize) : RingBuffer(useSize) {} - float out; +void CombFilter::process(const float in) { + // This model corresponds to the comb filter implementation of the real CM-32L device + // found from sample analysis + + // the previously stored value + float last = buffer[index]; - out = next(); + // prepare input + feedback + float filterIn = in + next() * feedbackFactor; - // store input - buffer[index] = in; + // store input + feedback processed by a low-pass filter + buffer[index] = filterFactor * last - filterIn; +} - // return buffer output - return out; +float CombFilter::getOutputAt(const Bit32u outIndex) const { + return buffer[(size + index - outIndex) % size]; } -AReverbModel::AReverbModel(const AReverbSettings *useSettings) : allpasses(NULL), delays(NULL), currentSettings(useSettings) { +void CombFilter::setFeedbackFactor(const float useFeedbackFactor) { + feedbackFactor = useFeedbackFactor; } +void CombFilter::setFilterFactor(const float useFilterFactor) { + filterFactor = useFilterFactor; +} + +AReverbModel::AReverbModel(const ReverbMode mode) : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]) {} + AReverbModel::~AReverbModel() { close(); } -void AReverbModel::open(unsigned int /*sampleRate*/) { - // FIXME: filter sizes must be multiplied by sample rate to 32000Hz ratio - // IIR filter values depend on sample rate as well +void AReverbModel::open() { allpasses = new AllpassFilter*[NUM_ALLPASSES]; for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - allpasses[i] = new AllpassFilter(currentSettings->allpassSizes[i]); + allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]); } - delays = new Delay*[NUM_DELAYS]; - for (Bit32u i = 0; i < NUM_DELAYS; i++) { - delays[i] = new Delay(currentSettings->delaySizes[i]); + combs = new CombFilter*[NUM_COMBS]; + for (Bit32u i = 0; i < NUM_COMBS; i++) { + combs[i] = new CombFilter(currentSettings.combSizes[i]); + combs[i]->setFilterFactor(currentSettings.filterFactor[i] / 256.0f); } + lpfAmp = currentSettings.lpfAmp / 16.0f; mute(); } @@ -150,84 +190,80 @@ void AReverbModel::close() { delete[] allpasses; allpasses = NULL; } - if (delays != NULL) { - for (Bit32u i = 0; i < NUM_DELAYS; i++) { - if (delays[i] != NULL) { - delete delays[i]; - delays[i] = NULL; + if (combs != NULL) { + for (Bit32u i = 0; i < NUM_COMBS; i++) { + if (combs[i] != NULL) { + delete combs[i]; + combs[i] = NULL; } } - delete[] delays; - delays = NULL; + delete[] combs; + combs = NULL; } } void AReverbModel::mute() { + if (allpasses == NULL || combs == NULL) return; for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { allpasses[i]->mute(); } - for (Bit32u i = 0; i < NUM_DELAYS; i++) { - delays[i]->mute(); + for (Bit32u i = 0; i < NUM_COMBS; i++) { + combs[i]->mute(); } - filterhist1 = 0; - filterhist2 = 0; - combhist = 0; } void AReverbModel::setParameters(Bit8u time, Bit8u level) { // FIXME: wetLevel definitely needs ramping when changed // Although, most games don't set reverb level during MIDI playback - decayTime = currentSettings->decayTimes[time]; - wetLevel = currentSettings->wetLevels[level]; + if (combs == NULL) return; + level &= 7; + time &= 7; + for (Bit32u i = 0; i < NUM_COMBS; i++) { + combs[i]->setFeedbackFactor(currentSettings.decayTimes[(i << 3) + time] / 256.0f); + } + wetLevel = (level == 0 && time == 0) ? 0.0f : 0.5f * lpfAmp * currentSettings.wetLevels[level] / 256.0f; } bool AReverbModel::isActive() const { - bool bActive = false; for (Bit32u i = 0; i < NUM_ALLPASSES; i++) { - bActive |= !allpasses[i]->isEmpty(); + if (!allpasses[i]->isEmpty()) return true; } - for (Bit32u i = 0; i < NUM_DELAYS; i++) { - bActive |= !delays[i]->isEmpty(); + for (Bit32u i = 0; i < NUM_COMBS; i++) { + if (!combs[i]->isEmpty()) return true; } - return bActive; + return false; } void AReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { -// Three series allpass filters followed by a delay, fourth allpass filter and another delay - float dry, link, outL1, outL2, outR1, outR2; + float dry, link, outL1; for (unsigned long i = 0; i < numSamples; i++) { - dry = *inLeft + *inRight; + dry = wetLevel * (*inLeft + *inRight); - // Implementation of 2-stage IIR single-pole low-pass filter - // found at the entrance of reverb processing on real devices - filterhist1 += (dry - filterhist1) * currentSettings->filtVal; - filterhist2 += (filterhist1 - filterhist2) * currentSettings->filtVal; + // Get the last stored sample before processing in order not to loose it + link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); - link = allpasses[0]->process(-filterhist2); - link = allpasses[1]->process(link); + combs[0]->process(-dry); - // this implements a comb filter cross-linked with the fourth allpass filter - link += combhist * decayTime; + link = allpasses[0]->process(link); + link = allpasses[1]->process(link); link = allpasses[2]->process(link); - link = delays[0]->process(link); - outL1 = link; - link = allpasses[3]->process(link); - link = delays[1]->process(link); - outR1 = link; - link = allpasses[4]->process(link); - link = delays[2]->process(link); - outL2 = link; - link = allpasses[5]->process(link); - link = delays[3]->process(link); - outR2 = link; - link = delays[4]->process(link); - - // comb filter end point - combhist = combhist * currentSettings->damp1 + link * currentSettings->damp2; - - *outLeft = (outL1 + outL2) * wetLevel; - *outRight = (outR1 + outR2) * wetLevel; + + // If the output position is equal to the comb size, get it now in order not to loose it + outL1 = 1.5f * combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); + + combs[1]->process(link); + combs[2]->process(link); + combs[3]->process(link); + + link = outL1 + 1.5f * combs[2]->getOutputAt(currentSettings.outLPositions[1]); + link += combs[3]->getOutputAt(currentSettings.outLPositions[2]); + *outLeft = link; + + link = 1.5f * combs[1]->getOutputAt(currentSettings.outRPositions[0]); + link += 1.5f * combs[2]->getOutputAt(currentSettings.outRPositions[1]); + link += combs[3]->getOutputAt(currentSettings.outRPositions[2]); + *outRight = link; inLeft++; inRight++; @@ -235,3 +271,7 @@ void AReverbModel::process(const float *inLeft, const float *inRight, float *out outRight++; } } + +} + +#endif diff --git a/audio/softsynth/mt32/AReverbModel.h b/audio/softsynth/mt32/AReverbModel.h index 3fae08c34c..c992478907 100644 --- a/audio/softsynth/mt32/AReverbModel.h +++ b/audio/softsynth/mt32/AReverbModel.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -21,66 +21,67 @@ namespace MT32Emu { struct AReverbSettings { - const Bit32u *allpassSizes; - const Bit32u *delaySizes; - const float *decayTimes; - const float *wetLevels; - float filtVal; - float damp1; - float damp2; + const Bit32u * const allpassSizes; + const Bit32u * const combSizes; + const Bit32u * const outLPositions; + const Bit32u * const outRPositions; + const Bit32u * const filterFactor; + const Bit32u * const decayTimes; + const Bit32u * const wetLevels; + const Bit32u lpfAmp; }; class RingBuffer { protected: float *buffer; - Bit32u size; + const Bit32u size; Bit32u index; + public: - RingBuffer(Bit32u size); + RingBuffer(const Bit32u size); virtual ~RingBuffer(); float next(); - bool isEmpty(); + bool isEmpty() const; void mute(); }; class AllpassFilter : public RingBuffer { public: - AllpassFilter(Bit32u size); - float process(float in); + AllpassFilter(const Bit32u size); + float process(const float in); }; -class Delay : public RingBuffer { +class CombFilter : public RingBuffer { + float feedbackFactor; + float filterFactor; + public: - Delay(Bit32u size); - float process(float in); + CombFilter(const Bit32u size); + void process(const float in); + float getOutputAt(const Bit32u outIndex) const; + void setFeedbackFactor(const float useFeedbackFactor); + void setFilterFactor(const float useFilterFactor); }; class AReverbModel : public ReverbModel { AllpassFilter **allpasses; - Delay **delays; + CombFilter **combs; - const AReverbSettings *currentSettings; - float decayTime; + const AReverbSettings ¤tSettings; + float lpfAmp; float wetLevel; - float filterhist1, filterhist2; - float combhist; void mute(); + public: - AReverbModel(const AReverbSettings *newSettings); + AReverbModel(const ReverbMode mode); ~AReverbModel(); - void open(unsigned int sampleRate); + void open(); void close(); void setParameters(Bit8u time, Bit8u level); void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); bool isActive() const; - - static const AReverbSettings REVERB_MODE_0_SETTINGS; - static const AReverbSettings REVERB_MODE_1_SETTINGS; - static const AReverbSettings REVERB_MODE_2_SETTINGS; }; -// Default reverb settings for modes 0-2 - } #endif diff --git a/audio/softsynth/mt32/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp new file mode 100644 index 0000000000..cc0219b741 --- /dev/null +++ b/audio/softsynth/mt32/BReverbModel.cpp @@ -0,0 +1,393 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "mt32emu.h" + +#if MT32EMU_USE_REVERBMODEL == 2 + +#include "BReverbModel.h" + +// Analysing of state of reverb RAM address lines gives exact sizes of the buffers of filters used. This also indicates that +// the reverb model implemented in the real devices consists of three series allpass filters preceded by a non-feedback comb (or a delay with a LPF) +// and followed by three parallel comb filters + +namespace MT32Emu { + +// Because LA-32 chip makes it's output available to process by the Boss chip with a significant delay, +// the Boss chip puts to the buffer the LA32 dry output when it is ready and performs processing of the _previously_ latched data. +// Of course, the right way would be to use a dedicated variable for this, but our reverb model is way higher level, +// so we can simply increase the input buffer size. +static const Bit32u PROCESS_DELAY = 1; + +static const Bit32u MODE_3_ADDITIONAL_DELAY = 1; +static const Bit32u MODE_3_FEEDBACK_DELAY = 1; + +// Default reverb settings for modes 0-2. These correspond to CM-32L / LAPC-I "new" reverb settings. MT-32 reverb is a bit different. +// Found by tracing reverb RAM data lines (thanks go to Lord_Nightmare & balrog). + +static const Bit32u MODE_0_NUMBER_OF_ALLPASSES = 3; +static const Bit32u MODE_0_ALLPASSES[] = {994, 729, 78}; +static const Bit32u MODE_0_NUMBER_OF_COMBS = 4; // Well, actually there are 3 comb filters, but the entrance LPF + delay can be processed via a hacked comb. +static const Bit32u MODE_0_COMBS[] = {705 + PROCESS_DELAY, 2349, 2839, 3632}; +static const Bit32u MODE_0_OUTL[] = {2349, 141, 1960}; +static const Bit32u MODE_0_OUTR[] = {1174, 1570, 145}; +static const Bit32u MODE_0_COMB_FACTOR[] = {0xA0, 0x60, 0x60, 0x60}; +static const Bit32u MODE_0_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; +static const Bit32u MODE_0_DRY_AMP[] = {0xA0, 0xA0, 0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xD0}; +static const Bit32u MODE_0_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0}; +static const Bit32u MODE_0_LPF_AMP = 0x60; + +static const Bit32u MODE_1_NUMBER_OF_ALLPASSES = 3; +static const Bit32u MODE_1_ALLPASSES[] = {1324, 809, 176}; +static const Bit32u MODE_1_NUMBER_OF_COMBS = 4; // Same as for mode 0 above +static const Bit32u MODE_1_COMBS[] = {961 + PROCESS_DELAY, 2619, 3545, 4519}; +static const Bit32u MODE_1_OUTL[] = {2618, 1760, 4518}; +static const Bit32u MODE_1_OUTR[] = {1300, 3532, 2274}; +static const Bit32u MODE_1_COMB_FACTOR[] = {0x80, 0x60, 0x60, 0x60}; +static const Bit32u MODE_1_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x48, 0x60, 0x70, 0x78, 0x80, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98, + 0x28, 0x48, 0x60, 0x78, 0x80, 0x88, 0x90, 0x98}; +static const Bit32u MODE_1_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xE0}; +static const Bit32u MODE_1_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0}; +static const Bit32u MODE_1_LPF_AMP = 0x60; + +static const Bit32u MODE_2_NUMBER_OF_ALLPASSES = 3; +static const Bit32u MODE_2_ALLPASSES[] = {969, 644, 157}; +static const Bit32u MODE_2_NUMBER_OF_COMBS = 4; // Same as for mode 0 above +static const Bit32u MODE_2_COMBS[] = {116 + PROCESS_DELAY, 2259, 2839, 3539}; +static const Bit32u MODE_2_OUTL[] = {2259, 718, 1769}; +static const Bit32u MODE_2_OUTR[] = {1136, 2128, 1}; +static const Bit32u MODE_2_COMB_FACTOR[] = {0, 0x20, 0x20, 0x20}; +static const Bit32u MODE_2_COMB_FEEDBACK[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0, + 0x30, 0x58, 0x78, 0x88, 0xA0, 0xB8, 0xC0, 0xD0}; +static const Bit32u MODE_2_DRY_AMP[] = {0xA0, 0xA0, 0xB0, 0xB0, 0xB0, 0xB0, 0xC0, 0xE0}; +static const Bit32u MODE_2_WET_AMP[] = {0x10, 0x30, 0x50, 0x70, 0x90, 0xC0, 0xF0, 0xF0}; +static const Bit32u MODE_2_LPF_AMP = 0x80; + +static const Bit32u MODE_3_NUMBER_OF_ALLPASSES = 0; +static const Bit32u MODE_3_NUMBER_OF_COMBS = 1; +static const Bit32u MODE_3_DELAY[] = {16000 + MODE_3_FEEDBACK_DELAY + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY}; +static const Bit32u MODE_3_OUTL[] = {400, 624, 960, 1488, 2256, 3472, 5280, 8000}; +static const Bit32u MODE_3_OUTR[] = {800, 1248, 1920, 2976, 4512, 6944, 10560, 16000}; +static const Bit32u MODE_3_COMB_FACTOR[] = {0x68}; +static const Bit32u MODE_3_COMB_FEEDBACK[] = {0x68, 0x60}; +static const Bit32u MODE_3_DRY_AMP[] = {0x20, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50}; +static const Bit32u MODE_3_WET_AMP[] = {0x18, 0x18, 0x28, 0x40, 0x60, 0x80, 0xA8, 0xF8}; + +static const BReverbSettings REVERB_MODE_0_SETTINGS = {MODE_0_NUMBER_OF_ALLPASSES, MODE_0_ALLPASSES, MODE_0_NUMBER_OF_COMBS, MODE_0_COMBS, MODE_0_OUTL, MODE_0_OUTR, MODE_0_COMB_FACTOR, MODE_0_COMB_FEEDBACK, MODE_0_DRY_AMP, MODE_0_WET_AMP, MODE_0_LPF_AMP}; +static const BReverbSettings REVERB_MODE_1_SETTINGS = {MODE_1_NUMBER_OF_ALLPASSES, MODE_1_ALLPASSES, MODE_1_NUMBER_OF_COMBS, MODE_1_COMBS, MODE_1_OUTL, MODE_1_OUTR, MODE_1_COMB_FACTOR, MODE_1_COMB_FEEDBACK, MODE_1_DRY_AMP, MODE_1_WET_AMP, MODE_1_LPF_AMP}; +static const BReverbSettings REVERB_MODE_2_SETTINGS = {MODE_2_NUMBER_OF_ALLPASSES, MODE_2_ALLPASSES, MODE_2_NUMBER_OF_COMBS, MODE_2_COMBS, MODE_2_OUTL, MODE_2_OUTR, MODE_2_COMB_FACTOR, MODE_2_COMB_FEEDBACK, MODE_2_DRY_AMP, MODE_2_WET_AMP, MODE_2_LPF_AMP}; +static const BReverbSettings REVERB_MODE_3_SETTINGS = {MODE_3_NUMBER_OF_ALLPASSES, NULL, MODE_3_NUMBER_OF_COMBS, MODE_3_DELAY, MODE_3_OUTL, MODE_3_OUTR, MODE_3_COMB_FACTOR, MODE_3_COMB_FEEDBACK, MODE_3_DRY_AMP, MODE_3_WET_AMP, 0}; + +static const BReverbSettings * const REVERB_SETTINGS[] = {&REVERB_MODE_0_SETTINGS, &REVERB_MODE_1_SETTINGS, &REVERB_MODE_2_SETTINGS, &REVERB_MODE_3_SETTINGS}; + +// This algorithm tries to emulate exactly Boss multiplication operation (at least this is what we see on reverb RAM data lines). +// Also LA32 is suspected to use the similar one to perform PCM interpolation and ring modulation. +static Bit32s weirdMul(Bit32s a, Bit8u addMask, Bit8u carryMask) { + Bit8u mask = 0x80; + Bit32s res = 0; + for (int i = 0; i < 8; i++) { + Bit32s carry = (a < 0) && (mask & carryMask) > 0 ? a & 1 : 0; + a >>= 1; + res += (mask & addMask) > 0 ? a + carry : 0; + mask >>= 1; + } + return res; +} + +RingBuffer::RingBuffer(Bit32u newsize) : size(newsize), index(0) { + buffer = new Bit16s[size]; +} + +RingBuffer::~RingBuffer() { + delete[] buffer; + buffer = NULL; +} + +Bit32s RingBuffer::next() { + if (++index >= size) { + index = 0; + } + return buffer[index]; +} + +bool RingBuffer::isEmpty() const { + if (buffer == NULL) return true; + + Bit16s *buf = buffer; + for (Bit32u i = 0; i < size; i++) { + if (*buf < -8 || *buf > 8) return false; + buf++; + } + return true; +} + +void RingBuffer::mute() { + Bit16s *buf = buffer; + for (Bit32u i = 0; i < size; i++) { + *buf++ = 0; + } +} + +AllpassFilter::AllpassFilter(const Bit32u useSize) : RingBuffer(useSize) {} + +Bit32s AllpassFilter::process(const Bit32s in) { + // This model corresponds to the allpass filter implementation of the real CM-32L device + // found from sample analysis + + Bit16s bufferOut = next(); + + // store input - feedback / 2 + buffer[index] = in - (bufferOut >> 1); + + // return buffer output + feedforward / 2 + return bufferOut + (buffer[index] >> 1); +} + +CombFilter::CombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : RingBuffer(useSize), filterFactor(useFilterFactor) {} + +void CombFilter::process(const Bit32s in) { + // This model corresponds to the comb filter implementation of the real CM-32L device + + // the previously stored value + Bit32s last = buffer[index]; + + // prepare input + feedback + Bit32s filterIn = in + weirdMul(next(), feedbackFactor, 0xF0 /* Maybe 0x80 ? */); + + // store input + feedback processed by a low-pass filter + buffer[index] = weirdMul(last, filterFactor, 0x40) - filterIn; +} + +Bit32s CombFilter::getOutputAt(const Bit32u outIndex) const { + return buffer[(size + index - outIndex) % size]; +} + +void CombFilter::setFeedbackFactor(const Bit32u useFeedbackFactor) { + feedbackFactor = useFeedbackFactor; +} + +DelayWithLowPassFilter::DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp) + : CombFilter(useSize, useFilterFactor), amp(useAmp) {} + +void DelayWithLowPassFilter::process(const Bit32s in) { + // the previously stored value + Bit32s last = buffer[index]; + + // move to the next index + next(); + + // low-pass filter process + Bit32s lpfOut = weirdMul(last, filterFactor, 0xFF) + in; + + // store lpfOut multiplied by LPF amp factor + buffer[index] = weirdMul(lpfOut, amp, 0xFF); +} + +TapDelayCombFilter::TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor) : CombFilter(useSize, useFilterFactor) {} + +void TapDelayCombFilter::process(const Bit32s in) { + // the previously stored value + Bit32s last = buffer[index]; + + // move to the next index + next(); + + // prepare input + feedback + // Actually, the size of the filter varies with the TIME parameter, the feedback sample is taken from the position just below the right output + Bit32s filterIn = in + weirdMul(getOutputAt(outR + MODE_3_FEEDBACK_DELAY), feedbackFactor, 0xF0); + + // store input + feedback processed by a low-pass filter + buffer[index] = weirdMul(last, filterFactor, 0xF0) - filterIn; +} + +Bit32s TapDelayCombFilter::getLeftOutput() const { + return getOutputAt(outL + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); +} + +Bit32s TapDelayCombFilter::getRightOutput() const { + return getOutputAt(outR + PROCESS_DELAY + MODE_3_ADDITIONAL_DELAY); +} + +void TapDelayCombFilter::setOutputPositions(const Bit32u useOutL, const Bit32u useOutR) { + outL = useOutL; + outR = useOutR; +} + +BReverbModel::BReverbModel(const ReverbMode mode) + : allpasses(NULL), combs(NULL), currentSettings(*REVERB_SETTINGS[mode]), tapDelayMode(mode == REVERB_MODE_TAP_DELAY) {} + +BReverbModel::~BReverbModel() { + close(); +} + +void BReverbModel::open() { + if (currentSettings.numberOfAllpasses > 0) { + allpasses = new AllpassFilter*[currentSettings.numberOfAllpasses]; + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + allpasses[i] = new AllpassFilter(currentSettings.allpassSizes[i]); + } + } + combs = new CombFilter*[currentSettings.numberOfCombs]; + if (tapDelayMode) { + *combs = new TapDelayCombFilter(*currentSettings.combSizes, *currentSettings.filterFactors); + } else { + combs[0] = new DelayWithLowPassFilter(currentSettings.combSizes[0], currentSettings.filterFactors[0], currentSettings.lpfAmp); + for (Bit32u i = 1; i < currentSettings.numberOfCombs; i++) { + combs[i] = new CombFilter(currentSettings.combSizes[i], currentSettings.filterFactors[i]); + } + } + mute(); +} + +void BReverbModel::close() { + if (allpasses != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + if (allpasses[i] != NULL) { + delete allpasses[i]; + allpasses[i] = NULL; + } + } + delete[] allpasses; + allpasses = NULL; + } + if (combs != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + if (combs[i] != NULL) { + delete combs[i]; + combs[i] = NULL; + } + } + delete[] combs; + combs = NULL; + } +} + +void BReverbModel::mute() { + if (allpasses != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + allpasses[i]->mute(); + } + } + if (combs != NULL) { + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + combs[i]->mute(); + } + } +} + +void BReverbModel::setParameters(Bit8u time, Bit8u level) { + if (combs == NULL) return; + level &= 7; + time &= 7; + if (tapDelayMode) { + TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs); + comb->setOutputPositions(currentSettings.outLPositions[time], currentSettings.outRPositions[time & 7]); + comb->setFeedbackFactor(currentSettings.feedbackFactors[((level < 3) || (time < 6)) ? 0 : 1]); + } else { + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + combs[i]->setFeedbackFactor(currentSettings.feedbackFactors[(i << 3) + time]); + } + } + if (time == 0 && level == 0) { + dryAmp = wetLevel = 0; + } else { + dryAmp = currentSettings.dryAmps[level]; + wetLevel = currentSettings.wetLevels[level]; + } +} + +bool BReverbModel::isActive() const { + for (Bit32u i = 0; i < currentSettings.numberOfAllpasses; i++) { + if (!allpasses[i]->isEmpty()) return true; + } + for (Bit32u i = 0; i < currentSettings.numberOfCombs; i++) { + if (!combs[i]->isEmpty()) return true; + } + return false; +} + +void BReverbModel::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { + Bit32s dry, link, outL1, outR1; + + for (unsigned long i = 0; i < numSamples; i++) { + if (tapDelayMode) { + dry = Bit32s(*inLeft * 8192.0f) + Bit32s(*inRight * 8192.0f); + } else { + dry = Bit32s(*inLeft * 8192.0f) / 2 + Bit32s(*inRight * 8192.0f) / 2; + } + + // Looks like dryAmp doesn't change in MT-32 but it does in CM-32L / LAPC-I + dry = weirdMul(dry, dryAmp, 0xFF); + + if (tapDelayMode) { + TapDelayCombFilter *comb = static_cast<TapDelayCombFilter *> (*combs); + comb->process(dry); + *outLeft = weirdMul(comb->getLeftOutput(), wetLevel, 0xFF) / 8192.0f; + *outRight = weirdMul(comb->getRightOutput(), wetLevel, 0xFF) / 8192.0f; + } else { + // Get the last stored sample before processing in order not to loose it + link = combs[0]->getOutputAt(currentSettings.combSizes[0] - 1); + + // Entrance LPF. Note, comb.process() differs a bit here. + combs[0]->process(dry); + + // This introduces reverb noise which actually makes output from the real Boss chip nondeterministic + link = link - 1; + link = allpasses[0]->process(link); + link = allpasses[1]->process(link); + link = allpasses[2]->process(link); + + // If the output position is equal to the comb size, get it now in order not to loose it + outL1 = combs[1]->getOutputAt(currentSettings.outLPositions[0] - 1); + outL1 += outL1 >> 1; + + combs[1]->process(link); + combs[2]->process(link); + combs[3]->process(link); + + link = combs[2]->getOutputAt(currentSettings.outLPositions[1]); + link += link >> 1; + link += outL1; + link += combs[3]->getOutputAt(currentSettings.outLPositions[2]); + *outLeft = weirdMul(link, wetLevel, 0xFF) / 8192.0f; + + outR1 = combs[1]->getOutputAt(currentSettings.outRPositions[0]); + outR1 += outR1 >> 1; + link = combs[2]->getOutputAt(currentSettings.outRPositions[1]); + link += link >> 1; + link += outR1; + link += combs[3]->getOutputAt(currentSettings.outRPositions[2]); + *outRight = weirdMul(link, wetLevel, 0xFF) / 8192.0f; + } + + inLeft++; + inRight++; + outLeft++; + outRight++; + } +} + +} + +#endif diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h new file mode 100644 index 0000000000..d6fcb73c13 --- /dev/null +++ b/audio/softsynth/mt32/BReverbModel.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MT32EMU_B_REVERB_MODEL_H +#define MT32EMU_B_REVERB_MODEL_H + +namespace MT32Emu { + +struct BReverbSettings { + const Bit32u numberOfAllpasses; + const Bit32u * const allpassSizes; + const Bit32u numberOfCombs; + const Bit32u * const combSizes; + const Bit32u * const outLPositions; + const Bit32u * const outRPositions; + const Bit32u * const filterFactors; + const Bit32u * const feedbackFactors; + const Bit32u * const dryAmps; + const Bit32u * const wetLevels; + const Bit32u lpfAmp; +}; + +class RingBuffer { +protected: + Bit16s *buffer; + const Bit32u size; + Bit32u index; + +public: + RingBuffer(const Bit32u size); + virtual ~RingBuffer(); + Bit32s next(); + bool isEmpty() const; + void mute(); +}; + +class AllpassFilter : public RingBuffer { +public: + AllpassFilter(const Bit32u size); + Bit32s process(const Bit32s in); +}; + +class CombFilter : public RingBuffer { +protected: + const Bit32u filterFactor; + Bit32u feedbackFactor; + +public: + CombFilter(const Bit32u size, const Bit32u useFilterFactor); + virtual void process(const Bit32s in); // Actually, no need to make it virtual, but for sure + Bit32s getOutputAt(const Bit32u outIndex) const; + void setFeedbackFactor(const Bit32u useFeedbackFactor); +}; + +class DelayWithLowPassFilter : public CombFilter { + Bit32u amp; + +public: + DelayWithLowPassFilter(const Bit32u useSize, const Bit32u useFilterFactor, const Bit32u useAmp); + void process(const Bit32s in); + void setFeedbackFactor(const Bit32u) {} +}; + +class TapDelayCombFilter : public CombFilter { + Bit32u outL; + Bit32u outR; + +public: + TapDelayCombFilter(const Bit32u useSize, const Bit32u useFilterFactor); + void process(const Bit32s in); + Bit32s getLeftOutput() const; + Bit32s getRightOutput() const; + void setOutputPositions(const Bit32u useOutL, const Bit32u useOutR); +}; + +class BReverbModel : public ReverbModel { + AllpassFilter **allpasses; + CombFilter **combs; + + const BReverbSettings ¤tSettings; + const bool tapDelayMode; + Bit32u dryAmp; + Bit32u wetLevel; + void mute(); + +public: + BReverbModel(const ReverbMode mode); + ~BReverbModel(); + void open(); + void close(); + void setParameters(Bit8u time, Bit8u level); + void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); + bool isActive() const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/DelayReverb.cpp b/audio/softsynth/mt32/DelayReverb.cpp index 89eebf0d79..d80c98acbc 100644 --- a/audio/softsynth/mt32/DelayReverb.cpp +++ b/audio/softsynth/mt32/DelayReverb.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -20,15 +20,14 @@ #include "mt32emu.h" #include "DelayReverb.h" -using namespace MT32Emu; +namespace MT32Emu { - -// CONFIRMED: The values below are found via analysis of digital samples. Checked with all time and level combinations. +// CONFIRMED: The values below are found via analysis of digital samples and tracing reverb RAM address / data lines. Checked with all time and level combinations. // Obviously: // rightDelay = (leftDelay - 2) * 2 + 2 // echoDelay = rightDelay - 1 // Leaving these separate in case it's useful for work on other reverb modes... -const Bit32u REVERB_TIMINGS[8][3]= { +static const Bit32u REVERB_TIMINGS[8][3]= { // {leftDelay, rightDelay, feedbackDelay} {402, 802, 801}, {626, 1250, 1249}, @@ -40,14 +39,16 @@ const Bit32u REVERB_TIMINGS[8][3]= { {8002, 16002, 16001} }; -const float REVERB_FADE[8] = {0.0f, -0.049400051f, -0.08220577f, -0.131861118f, -0.197344907f, -0.262956344f, -0.345162114f, -0.509508615f}; -const float REVERB_FEEDBACK67 = -0.629960524947437f; // = -EXP2F(-2 / 3) -const float REVERB_FEEDBACK = -0.682034520443118f; // = -EXP2F(-53 / 96) -const float LPF_VALUE = 0.594603558f; // = EXP2F(-0.75f) +// Reverb amp is found as dryAmp * wetAmp +static const Bit32u REVERB_AMP[8] = {0x20*0x18, 0x50*0x18, 0x50*0x28, 0x50*0x40, 0x50*0x60, 0x50*0x80, 0x50*0xA8, 0x50*0xF8}; +static const Bit32u REVERB_FEEDBACK67 = 0x60; +static const Bit32u REVERB_FEEDBACK = 0x68; +static const float LPF_VALUE = 0x68 / 256.0f; + +static const Bit32u BUFFER_SIZE = 16384; DelayReverb::DelayReverb() { buf = NULL; - sampleRate = 0; setParameters(0, 0); } @@ -55,27 +56,22 @@ DelayReverb::~DelayReverb() { delete[] buf; } -void DelayReverb::open(unsigned int newSampleRate) { - if (newSampleRate != sampleRate || buf == NULL) { - sampleRate = newSampleRate; - +void DelayReverb::open() { + if (buf == NULL) { delete[] buf; - // If we ever need a speedup, set bufSize to EXP2F(ceil(log2(bufSize))) and use & instead of % to find buf indexes - bufSize = 16384 * sampleRate / 32000; - buf = new float[bufSize]; + buf = new float[BUFFER_SIZE]; recalcParameters(); // mute buffer bufIx = 0; if (buf != NULL) { - for (unsigned int i = 0; i < bufSize; i++) { + for (unsigned int i = 0; i < BUFFER_SIZE; i++) { buf[i] = 0.0f; } } } - // FIXME: IIR filter value depends on sample rate as well } void DelayReverb::close() { @@ -92,59 +88,55 @@ void DelayReverb::setParameters(Bit8u newTime, Bit8u newLevel) { void DelayReverb::recalcParameters() { // Number of samples between impulse and eventual appearance on the left channel - delayLeft = REVERB_TIMINGS[time][0] * sampleRate / 32000; + delayLeft = REVERB_TIMINGS[time][0]; // Number of samples between impulse and eventual appearance on the right channel - delayRight = REVERB_TIMINGS[time][1] * sampleRate / 32000; + delayRight = REVERB_TIMINGS[time][1]; // Number of samples between a response and that response feeding back/echoing - delayFeedback = REVERB_TIMINGS[time][2] * sampleRate / 32000; + delayFeedback = REVERB_TIMINGS[time][2]; - if (time < 6) { - feedback = REVERB_FEEDBACK; + if (level < 3 || time < 6) { + feedback = REVERB_FEEDBACK / 256.0f; } else { - feedback = REVERB_FEEDBACK67; + feedback = REVERB_FEEDBACK67 / 256.0f; } - // Fading speed, i.e. amplitude ratio of neighbor responses - fade = REVERB_FADE[level]; + // Overall output amp + amp = (level == 0 && time == 0) ? 0.0f : REVERB_AMP[level] / 65536.0f; } void DelayReverb::process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples) { - if (buf == NULL) { - return; - } + if (buf == NULL) return; for (unsigned int sampleIx = 0; sampleIx < numSamples; sampleIx++) { // The ring buffer write index moves backwards; reads are all done with positive offsets. - Bit32u bufIxPrev = (bufIx + 1) % bufSize; - Bit32u bufIxLeft = (bufIx + delayLeft) % bufSize; - Bit32u bufIxRight = (bufIx + delayRight) % bufSize; - Bit32u bufIxFeedback = (bufIx + delayFeedback) % bufSize; + Bit32u bufIxPrev = (bufIx + 1) % BUFFER_SIZE; + Bit32u bufIxLeft = (bufIx + delayLeft) % BUFFER_SIZE; + Bit32u bufIxRight = (bufIx + delayRight) % BUFFER_SIZE; + Bit32u bufIxFeedback = (bufIx + delayFeedback) % BUFFER_SIZE; // Attenuated input samples and feedback response are directly added to the current ring buffer location - float sample = fade * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback]; + float lpfIn = amp * (inLeft[sampleIx] + inRight[sampleIx]) + feedback * buf[bufIxFeedback]; // Single-pole IIR filter found on real devices - buf[bufIx] = buf[bufIxPrev] + (sample - buf[bufIxPrev]) * LPF_VALUE; + buf[bufIx] = buf[bufIxPrev] * LPF_VALUE - lpfIn; outLeft[sampleIx] = buf[bufIxLeft]; outRight[sampleIx] = buf[bufIxRight]; - bufIx = (bufSize + bufIx - 1) % bufSize; + bufIx = (BUFFER_SIZE + bufIx - 1) % BUFFER_SIZE; } } bool DelayReverb::isActive() const { - // Quick hack: Return true iff all samples in the left buffer are the same and - // all samples in the right buffers are the same (within the sample output threshold). - if (buf == NULL) { - return false; - } - float last = buf[0] * 8192.0f; - for (unsigned int i = 1; i < bufSize; i++) { - float s = (buf[i] * 8192.0f); - if (fabs(s - last) > 1.0f) { - return true; - } + if (buf == NULL) return false; + + float *b = buf; + float max = 0.001f; + for (Bit32u i = 0; i < BUFFER_SIZE; i++) { + if ((*b < -max) || (*b > max)) return true; + b++; } return false; } + +} diff --git a/audio/softsynth/mt32/DelayReverb.h b/audio/softsynth/mt32/DelayReverb.h index 7c030fb839..c8003832b5 100644 --- a/audio/softsynth/mt32/DelayReverb.h +++ b/audio/softsynth/mt32/DelayReverb.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -25,17 +25,14 @@ private: Bit8u time; Bit8u level; - unsigned int sampleRate; - Bit32u bufSize; Bit32u bufIx; - float *buf; Bit32u delayLeft; Bit32u delayRight; Bit32u delayFeedback; - float fade; + float amp; float feedback; void recalcParameters(); @@ -43,7 +40,7 @@ private: public: DelayReverb(); ~DelayReverb(); - void open(unsigned int sampleRate); + void open(); void close(); void setParameters(Bit8u time, Bit8u level); void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); diff --git a/audio/softsynth/mt32/FreeverbModel.cpp b/audio/softsynth/mt32/FreeverbModel.cpp index c11fa859d8..bd9c70b6f4 100644 --- a/audio/softsynth/mt32/FreeverbModel.cpp +++ b/audio/softsynth/mt32/FreeverbModel.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -20,7 +20,7 @@ #include "freeverb.h" -using namespace MT32Emu; +namespace MT32Emu { FreeverbModel::FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp) { freeverb = NULL; @@ -35,9 +35,7 @@ FreeverbModel::~FreeverbModel() { delete freeverb; } -void FreeverbModel::open(unsigned int /*sampleRate*/) { - // FIXME: scaleTuning must be multiplied by sample rate to 32000Hz ratio - // IIR filter values depend on sample rate as well +void FreeverbModel::open() { if (freeverb == NULL) { freeverb = new revmodel(scaleTuning); } @@ -76,3 +74,5 @@ bool FreeverbModel::isActive() const { // FIXME: Not bothering to do this properly since we'll be replacing Freeverb soon... return false; } + +} diff --git a/audio/softsynth/mt32/FreeverbModel.h b/audio/softsynth/mt32/FreeverbModel.h index 925b2dbf96..5ea11f1f40 100644 --- a/audio/softsynth/mt32/FreeverbModel.h +++ b/audio/softsynth/mt32/FreeverbModel.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -32,7 +32,7 @@ class FreeverbModel : public ReverbModel { public: FreeverbModel(float useScaleTuning, float useFiltVal, float useWet, Bit8u useRoom, float useDamp); ~FreeverbModel(); - void open(unsigned int sampleRate); + void open(); void close(); void setParameters(Bit8u time, Bit8u level); void process(const float *inLeft, const float *inRight, float *outLeft, float *outRight, unsigned long numSamples); diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp index 9f1f01c3c2..b4ac6f1d46 100644 --- a/audio/softsynth/mt32/LA32Ramp.cpp +++ b/audio/softsynth/mt32/LA32Ramp.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -79,11 +79,16 @@ LA32Ramp::LA32Ramp() : void LA32Ramp::startRamp(Bit8u target, Bit8u increment) { // CONFIRMED: From sample analysis, this appears to be very accurate. - // FIXME: We could use a table for this in future if (increment == 0) { largeIncrement = 0; } else { - largeIncrement = (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f); + // Three bits in the fractional part, no need to interpolate + // (unsigned int)(EXP2F(((increment & 0x7F) + 24) / 8.0f) + 0.125f) + Bit32u expArg = increment & 0x7F; + largeIncrement = 8191 - Tables::getInstance().exp9[~(expArg << 6) & 511]; + largeIncrement <<= expArg >> 3; + largeIncrement += 64; + largeIncrement >>= 9; } descending = (increment & 0x80) != 0; if (descending) { diff --git a/audio/softsynth/mt32/LA32Ramp.h b/audio/softsynth/mt32/LA32Ramp.h index ae937eb7e1..8f55941a12 100644 --- a/audio/softsynth/mt32/LA32Ramp.h +++ b/audio/softsynth/mt32/LA32Ramp.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp new file mode 100644 index 0000000000..80650699fb --- /dev/null +++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp @@ -0,0 +1,418 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +//#include <cmath> +#include "mt32emu.h" +#include "mmath.h" +#include "LA32WaveGenerator.h" + +#if MT32EMU_ACCURATE_WG == 0 + +namespace MT32Emu { + +static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18; +static const Bit32u MIDDLE_CUTOFF_VALUE = 128 << 18; +static const Bit32u RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144 << 18; +static const Bit32u MAX_CUTOFF_VALUE = 240 << 18; +static const LogSample SILENCE = {65535, LogSample::POSITIVE}; + +Bit16u LA32Utilites::interpolateExp(const Bit16u fract) { + Bit16u expTabIndex = fract >> 3; + Bit16u extraBits = ~fract & 7; + Bit16u expTabEntry2 = 8191 - Tables::getInstance().exp9[expTabIndex]; + Bit16u expTabEntry1 = expTabIndex == 0 ? 8191 : (8191 - Tables::getInstance().exp9[expTabIndex - 1]); + return expTabEntry2 + (((expTabEntry1 - expTabEntry2) * extraBits) >> 3); +} + +Bit16s LA32Utilites::unlog(const LogSample &logSample) { + //Bit16s sample = (Bit16s)EXP2F(13.0f - logSample.logValue / 1024.0f); + Bit32u intLogValue = logSample.logValue >> 12; + Bit16u fracLogValue = logSample.logValue & 4095; + Bit16s sample = interpolateExp(fracLogValue) >> intLogValue; + return logSample.sign == LogSample::POSITIVE ? sample : -sample; +} + +void LA32Utilites::addLogSamples(LogSample &logSample1, const LogSample &logSample2) { + Bit32u logSampleValue = logSample1.logValue + logSample2.logValue; + logSample1.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535; + logSample1.sign = logSample1.sign == logSample2.sign ? LogSample::POSITIVE : LogSample::NEGATIVE; +} + +Bit32u LA32WaveGenerator::getSampleStep() { + // sampleStep = EXP2F(pitch / 4096.0f + 4.0f) + Bit32u sampleStep = LA32Utilites::interpolateExp(~pitch & 4095); + sampleStep <<= pitch >> 12; + sampleStep >>= 8; + sampleStep &= ~1; + return sampleStep; +} + +Bit32u LA32WaveGenerator::getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue) { + // resonanceWaveLengthFactor = (Bit32u)EXP2F(12.0f + effectiveCutoffValue / 4096.0f); + Bit32u resonanceWaveLengthFactor = LA32Utilites::interpolateExp(~effectiveCutoffValue & 4095); + resonanceWaveLengthFactor <<= effectiveCutoffValue >> 12; + return resonanceWaveLengthFactor; +} + +Bit32u LA32WaveGenerator::getHighLinearLength(Bit32u effectiveCutoffValue) { + // Ratio of positive segment to wave length + Bit32u effectivePulseWidthValue = 0; + if (pulseWidth > 128) { + effectivePulseWidthValue = (pulseWidth - 128) << 6; + } + + Bit32u highLinearLength = 0; + // highLinearLength = EXP2F(19.0f - effectivePulseWidthValue / 4096.0f + effectiveCutoffValue / 4096.0f) - 2 * SINE_SEGMENT_RELATIVE_LENGTH; + if (effectivePulseWidthValue < effectiveCutoffValue) { + Bit32u expArg = effectiveCutoffValue - effectivePulseWidthValue; + highLinearLength = LA32Utilites::interpolateExp(~expArg & 4095); + highLinearLength <<= 7 + (expArg >> 12); + highLinearLength -= 2 * SINE_SEGMENT_RELATIVE_LENGTH; + } + return highLinearLength; +} + +void LA32WaveGenerator::computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor) { + // Assuming 12-bit multiplication used here + squareWavePosition = resonanceSinePosition = (wavePosition >> 8) * (resonanceWaveLengthFactor >> 4); + if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) { + phase = POSITIVE_RISING_SINE_SEGMENT; + return; + } + squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH; + if (squareWavePosition < highLinearLength) { + phase = POSITIVE_LINEAR_SEGMENT; + return; + } + squareWavePosition -= highLinearLength; + if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) { + phase = POSITIVE_FALLING_SINE_SEGMENT; + return; + } + squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH; + resonanceSinePosition = squareWavePosition; + if (squareWavePosition < SINE_SEGMENT_RELATIVE_LENGTH) { + phase = NEGATIVE_FALLING_SINE_SEGMENT; + return; + } + squareWavePosition -= SINE_SEGMENT_RELATIVE_LENGTH; + if (squareWavePosition < lowLinearLength) { + phase = NEGATIVE_LINEAR_SEGMENT; + return; + } + squareWavePosition -= lowLinearLength; + phase = NEGATIVE_RISING_SINE_SEGMENT; +} + +void LA32WaveGenerator::advancePosition() { + wavePosition += getSampleStep(); + wavePosition %= 4 * SINE_SEGMENT_RELATIVE_LENGTH; + + Bit32u effectiveCutoffValue = (cutoffVal > MIDDLE_CUTOFF_VALUE) ? (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 10 : 0; + Bit32u resonanceWaveLengthFactor = getResonanceWaveLengthFactor(effectiveCutoffValue); + Bit32u highLinearLength = getHighLinearLength(effectiveCutoffValue); + Bit32u lowLinearLength = (resonanceWaveLengthFactor << 8) - 4 * SINE_SEGMENT_RELATIVE_LENGTH - highLinearLength; + computePositions(highLinearLength, lowLinearLength, resonanceWaveLengthFactor); + + // resonancePhase computation hack + *(int*)&resonancePhase = ((resonanceSinePosition >> 18) + (phase > POSITIVE_FALLING_SINE_SEGMENT ? 2 : 0)) & 3; +} + +void LA32WaveGenerator::generateNextSquareWaveLogSample() { + Bit32u logSampleValue; + switch (phase) { + case POSITIVE_RISING_SINE_SEGMENT: + case NEGATIVE_FALLING_SINE_SEGMENT: + logSampleValue = Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511]; + break; + case POSITIVE_FALLING_SINE_SEGMENT: + case NEGATIVE_RISING_SINE_SEGMENT: + logSampleValue = Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511]; + break; + case POSITIVE_LINEAR_SEGMENT: + case NEGATIVE_LINEAR_SEGMENT: + default: + logSampleValue = 0; + break; + } + logSampleValue <<= 2; + logSampleValue += amp >> 10; + if (cutoffVal < MIDDLE_CUTOFF_VALUE) { + logSampleValue += (MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9; + } + + squareLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535; + squareLogSample.sign = phase < NEGATIVE_FALLING_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE; +} + +void LA32WaveGenerator::generateNextResonanceWaveLogSample() { + Bit32u logSampleValue; + if (resonancePhase == POSITIVE_FALLING_RESONANCE_SINE_SEGMENT || resonancePhase == NEGATIVE_RISING_RESONANCE_SINE_SEGMENT) { + logSampleValue = Tables::getInstance().logsin9[~(resonanceSinePosition >> 9) & 511]; + } else { + logSampleValue = Tables::getInstance().logsin9[(resonanceSinePosition >> 9) & 511]; + } + logSampleValue <<= 2; + logSampleValue += amp >> 10; + + // From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments + Bit32u decayFactor = phase < NEGATIVE_FALLING_SINE_SEGMENT ? resAmpDecayFactor : resAmpDecayFactor + 1; + // Unsure about resonanceSinePosition here. It's possible that dedicated counter & decrement are used. Although, cutoff is finely ramped, so maybe not. + logSampleValue += resonanceAmpSubtraction + (((resonanceSinePosition >> 4) * decayFactor) >> 8); + + // To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment + if (phase == POSITIVE_RISING_SINE_SEGMENT || phase == NEGATIVE_FALLING_SINE_SEGMENT) { + // The window is synchronous sine here + logSampleValue += Tables::getInstance().logsin9[(squareWavePosition >> 9) & 511] << 2; + } else if (phase == POSITIVE_FALLING_SINE_SEGMENT || phase == NEGATIVE_RISING_SINE_SEGMENT) { + // The window is synchronous square sine here + logSampleValue += Tables::getInstance().logsin9[~(squareWavePosition >> 9) & 511] << 3; + } + + if (cutoffVal < MIDDLE_CUTOFF_VALUE) { + // For the cutoff values below the cutoff middle point, it seems the amp of the resonance wave is expotentially decayed + logSampleValue += 31743 + ((MIDDLE_CUTOFF_VALUE - cutoffVal) >> 9); + } else if (cutoffVal < RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE) { + // For the cutoff values below this point, the amp of the resonance wave is sinusoidally decayed + Bit32u sineIx = (cutoffVal - MIDDLE_CUTOFF_VALUE) >> 13; + logSampleValue += Tables::getInstance().logsin9[sineIx] << 2; + } + + // After all the amp decrements are added, it should be safe now to adjust the amp of the resonance wave to what we see on captures + logSampleValue -= 1 << 12; + + resonanceLogSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535; + resonanceLogSample.sign = resonancePhase < NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT ? LogSample::POSITIVE : LogSample::NEGATIVE; +} + +void LA32WaveGenerator::generateNextSawtoothCosineLogSample(LogSample &logSample) const { + Bit32u sawtoothCosinePosition = wavePosition + (1 << 18); + if ((sawtoothCosinePosition & (1 << 18)) > 0) { + logSample.logValue = Tables::getInstance().logsin9[~(sawtoothCosinePosition >> 9) & 511]; + } else { + logSample.logValue = Tables::getInstance().logsin9[(sawtoothCosinePosition >> 9) & 511]; + } + logSample.logValue <<= 2; + logSample.sign = ((sawtoothCosinePosition & (1 << 19)) == 0) ? LogSample::POSITIVE : LogSample::NEGATIVE; +} + +void LA32WaveGenerator::pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const { + Bit32u logSampleValue = (32787 - (pcmSample & 32767)) << 1; + logSampleValue += amp >> 10; + logSample.logValue = logSampleValue < 65536 ? (Bit16u)logSampleValue : 65535; + logSample.sign = pcmSample < 0 ? LogSample::NEGATIVE : LogSample::POSITIVE; +} + +void LA32WaveGenerator::generateNextPCMWaveLogSamples() { + // This should emulate the ladder we see in the PCM captures for pitches 01, 02, 07, etc. + // The most probable cause is the factor in the interpolation formula is one bit less + // accurate than the sample position counter + pcmInterpolationFactor = (wavePosition & 255) >> 1; + Bit32u pcmWaveTableIx = wavePosition >> 8; + pcmSampleToLogSample(firstPCMLogSample, pcmWaveAddress[pcmWaveTableIx]); + if (pcmWaveInterpolated) { + pcmWaveTableIx++; + if (pcmWaveTableIx < pcmWaveLength) { + pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]); + } else { + if (pcmWaveLooped) { + pcmWaveTableIx -= pcmWaveLength; + pcmSampleToLogSample(secondPCMLogSample, pcmWaveAddress[pcmWaveTableIx]); + } else { + secondPCMLogSample = SILENCE; + } + } + } else { + secondPCMLogSample = SILENCE; + } + // pcmSampleStep = (Bit32u)EXP2F(pitch / 4096.0f + 3.0f); + Bit32u pcmSampleStep = LA32Utilites::interpolateExp(~pitch & 4095); + pcmSampleStep <<= pitch >> 12; + // Seeing the actual lengths of the PCM wave for pitches 00..12, + // the pcmPosition counter can be assumed to have 8-bit fractions + pcmSampleStep >>= 9; + wavePosition += pcmSampleStep; + if (wavePosition >= (pcmWaveLength << 8)) { + if (pcmWaveLooped) { + wavePosition -= pcmWaveLength << 8; + } else { + deactivate(); + } + } +} + +void LA32WaveGenerator::initSynth(const bool useSawtoothWaveform, const Bit8u usePulseWidth, const Bit8u useResonance) { + sawtoothWaveform = useSawtoothWaveform; + pulseWidth = usePulseWidth; + resonance = useResonance; + + wavePosition = 0; + + squareWavePosition = 0; + phase = POSITIVE_RISING_SINE_SEGMENT; + + resonanceSinePosition = 0; + resonancePhase = POSITIVE_RISING_RESONANCE_SINE_SEGMENT; + resonanceAmpSubtraction = (32 - resonance) << 10; + resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2] << 2; + + pcmWaveAddress = NULL; + active = true; +} + +void LA32WaveGenerator::initPCM(const Bit16s * const usePCMWaveAddress, const Bit32u usePCMWaveLength, const bool usePCMWaveLooped, const bool usePCMWaveInterpolated) { + pcmWaveAddress = usePCMWaveAddress; + pcmWaveLength = usePCMWaveLength; + pcmWaveLooped = usePCMWaveLooped; + pcmWaveInterpolated = usePCMWaveInterpolated; + + wavePosition = 0; + active = true; +} + +void LA32WaveGenerator::generateNextSample(const Bit32u useAmp, const Bit16u usePitch, const Bit32u useCutoffVal) { + if (!active) { + return; + } + + amp = useAmp; + pitch = usePitch; + + if (isPCMWave()) { + generateNextPCMWaveLogSamples(); + return; + } + + // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4). + // More research is needed to be sure that this is correct, however. + cutoffVal = (useCutoffVal > MAX_CUTOFF_VALUE) ? MAX_CUTOFF_VALUE : useCutoffVal; + + generateNextSquareWaveLogSample(); + generateNextResonanceWaveLogSample(); + if (sawtoothWaveform) { + LogSample cosineLogSample; + generateNextSawtoothCosineLogSample(cosineLogSample); + LA32Utilites::addLogSamples(squareLogSample, cosineLogSample); + LA32Utilites::addLogSamples(resonanceLogSample, cosineLogSample); + } + advancePosition(); +} + +LogSample LA32WaveGenerator::getOutputLogSample(const bool first) const { + if (!isActive()) { + return SILENCE; + } + if (isPCMWave()) { + return first ? firstPCMLogSample : secondPCMLogSample; + } + return first ? squareLogSample : resonanceLogSample; +} + +void LA32WaveGenerator::deactivate() { + active = false; +} + +bool LA32WaveGenerator::isActive() const { + return active; +} + +bool LA32WaveGenerator::isPCMWave() const { + return pcmWaveAddress != NULL; +} + +Bit32u LA32WaveGenerator::getPCMInterpolationFactor() const { + return pcmInterpolationFactor; +} + +void LA32PartialPair::init(const bool useRingModulated, const bool useMixed) { + ringModulated = useRingModulated; + mixed = useMixed; +} + +void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { + if (useMaster == MASTER) { + master.initSynth(sawtoothWaveform, pulseWidth, resonance); + } else { + slave.initSynth(sawtoothWaveform, pulseWidth, resonance); + } +} + +void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { + if (useMaster == MASTER) { + master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true); + } else { + slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated); + } +} + +void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { + if (useMaster == MASTER) { + master.generateNextSample(amp, pitch, cutoff); + } else { + slave.generateNextSample(amp, pitch, cutoff); + } +} + +Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample) { + if (!wg.isActive() || ((ringModulatingLogSample != NULL) && (ringModulatingLogSample->logValue == SILENCE.logValue))) { + return 0; + } + LogSample firstLogSample = wg.getOutputLogSample(true); + LogSample secondLogSample = wg.getOutputLogSample(false); + if (ringModulatingLogSample != NULL) { + LA32Utilites::addLogSamples(firstLogSample, *ringModulatingLogSample); + LA32Utilites::addLogSamples(secondLogSample, *ringModulatingLogSample); + } + Bit16s firstSample = LA32Utilites::unlog(firstLogSample); + Bit16s secondSample = LA32Utilites::unlog(secondLogSample); + if (wg.isPCMWave()) { + return Bit16s(firstSample + ((Bit32s(secondSample - firstSample) * wg.getPCMInterpolationFactor()) >> 7)); + } + return firstSample + secondSample; +} + +Bit16s LA32PartialPair::nextOutSample() { + if (ringModulated) { + LogSample slaveFirstLogSample = slave.getOutputLogSample(true); + LogSample slaveSecondLogSample = slave.getOutputLogSample(false); + Bit16s sample = unlogAndMixWGOutput(master, &slaveFirstLogSample); + if (!slave.isPCMWave()) { + sample += unlogAndMixWGOutput(master, &slaveSecondLogSample); + } + if (mixed) { + sample += unlogAndMixWGOutput(master, NULL); + } + return sample; + } + return unlogAndMixWGOutput(master, NULL) + unlogAndMixWGOutput(slave, NULL); +} + +void LA32PartialPair::deactivate(const PairType useMaster) { + if (useMaster == MASTER) { + master.deactivate(); + } else { + slave.deactivate(); + } +} + +bool LA32PartialPair::isActive(const PairType useMaster) const { + return useMaster == MASTER ? master.isActive() : slave.isActive(); +} + +} + +#endif // #if MT32EMU_ACCURATE_WG == 0 diff --git a/audio/softsynth/mt32/LA32WaveGenerator.h b/audio/softsynth/mt32/LA32WaveGenerator.h new file mode 100644 index 0000000000..37a4aead85 --- /dev/null +++ b/audio/softsynth/mt32/LA32WaveGenerator.h @@ -0,0 +1,246 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#if MT32EMU_ACCURATE_WG == 0 + +#ifndef MT32EMU_LA32_WAVE_GENERATOR_H +#define MT32EMU_LA32_WAVE_GENERATOR_H + +namespace MT32Emu { + +/** + * LA32 performs wave generation in the log-space that allows replacing multiplications by cheap additions + * It's assumed that only low-bit multiplications occur in a few places which are unavoidable like these: + * - interpolation of exponent table (obvious, a delta value has 4 bits) + * - computation of resonance amp decay envelope (the table contains values with 1-2 "1" bits except the very first value 31 but this case can be found using inversion) + * - interpolation of PCM samples (obvious, the wave position counter is in the linear space, there is no log() table in the chip) + * and it seems to be implemented in the same way as in the Boss chip, i.e. right shifted additions which involved noticeable precision loss + * Subtraction is supposed to be replaced by simple inversion + * As the logarithmic sine is always negative, all the logarithmic values are treated as decrements + */ +struct LogSample { + // 16-bit fixed point value, includes 12-bit fractional part + // 4-bit integer part allows to present any 16-bit sample in the log-space + // Obviously, the log value doesn't contain the sign of the resulting sample + Bit16u logValue; + enum { + POSITIVE, + NEGATIVE + } sign; +}; + +class LA32Utilites { +public: + static Bit16u interpolateExp(const Bit16u fract); + static Bit16s unlog(const LogSample &logSample); + static void addLogSamples(LogSample &logSample1, const LogSample &logSample2); +}; + +/** + * LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator. + * The output square wave is created by adding high / low linear segments in-between + * the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis. + * Behaviour of a true resonance filter is emulated by adding decaying sine wave. + * The beginning and the ending of the resonant sine is multiplied by a cosine window. + * To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave. + */ +class LA32WaveGenerator { + //*************************************************************************** + // The local copy of partial parameters below + //*************************************************************************** + + bool active; + + // True means the resulting square wave is to be multiplied by the synchronous cosine + bool sawtoothWaveform; + + // Logarithmic amp of the wave generator + Bit32u amp; + + // Logarithmic frequency of the resulting wave + Bit16u pitch; + + // Values in range [1..31] + // Value 1 correspong to the minimum resonance + Bit8u resonance; + + // Processed value in range [0..255] + // Values in range [0..128] have no effect and the resulting wave remains symmetrical + // Value 255 corresponds to the maximum possible asymmetric of the resulting wave + Bit8u pulseWidth; + + // Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier + Bit32u cutoffVal; + + // Logarithmic PCM sample start address + const Bit16s *pcmWaveAddress; + + // Logarithmic PCM sample length + Bit32u pcmWaveLength; + + // true for looped logarithmic PCM samples + bool pcmWaveLooped; + + // false for slave PCM partials in the structures with the ring modulation + bool pcmWaveInterpolated; + + //*************************************************************************** + // Internal variables below + //*************************************************************************** + + // Relative position within either the synth wave or the PCM sampled wave + // 0 - start of the positive rising sine segment of the square wave or start of the PCM sample + // 1048576 (2^20) - end of the negative rising sine segment of the square wave + // For PCM waves, the address of the currently playing sample equals (wavePosition / 256) + Bit32u wavePosition; + + // Relative position within a square wave phase: + // 0 - start of the phase + // 262144 (2^18) - end of a sine phase in the square wave + Bit32u squareWavePosition; + + // Relative position within the positive or negative wave segment: + // 0 - start of the corresponding positive or negative segment of the square wave + // 262144 (2^18) - corresponds to end of the first sine phase in the square wave + // The same increment sampleStep is used to indicate the current position + // since the length of the resonance wave is always equal to four square wave sine segments. + Bit32u resonanceSinePosition; + + // The amp of the resonance sine wave grows with the resonance value + // As the resonance value cannot change while the partial is active, it is initialised once + Bit32u resonanceAmpSubtraction; + + // The decay speed of resonance sine wave, depends on the resonance value + Bit32u resAmpDecayFactor; + + // Fractional part of the pcmPosition + Bit32u pcmInterpolationFactor; + + // Current phase of the square wave + enum { + POSITIVE_RISING_SINE_SEGMENT, + POSITIVE_LINEAR_SEGMENT, + POSITIVE_FALLING_SINE_SEGMENT, + NEGATIVE_FALLING_SINE_SEGMENT, + NEGATIVE_LINEAR_SEGMENT, + NEGATIVE_RISING_SINE_SEGMENT + } phase; + + // Current phase of the resonance wave + enum { + POSITIVE_RISING_RESONANCE_SINE_SEGMENT, + POSITIVE_FALLING_RESONANCE_SINE_SEGMENT, + NEGATIVE_FALLING_RESONANCE_SINE_SEGMENT, + NEGATIVE_RISING_RESONANCE_SINE_SEGMENT + } resonancePhase; + + // Resulting log-space samples of the square and resonance waves + LogSample squareLogSample; + LogSample resonanceLogSample; + + // Processed neighbour log-space samples of the PCM wave + LogSample firstPCMLogSample; + LogSample secondPCMLogSample; + + //*************************************************************************** + // Internal methods below + //*************************************************************************** + + Bit32u getSampleStep(); + Bit32u getResonanceWaveLengthFactor(Bit32u effectiveCutoffValue); + Bit32u getHighLinearLength(Bit32u effectiveCutoffValue); + + void computePositions(Bit32u highLinearLength, Bit32u lowLinearLength, Bit32u resonanceWaveLengthFactor); + void advancePosition(); + + void generateNextSquareWaveLogSample(); + void generateNextResonanceWaveLogSample(); + void generateNextSawtoothCosineLogSample(LogSample &logSample) const; + + void pcmSampleToLogSample(LogSample &logSample, const Bit16s pcmSample) const; + void generateNextPCMWaveLogSamples(); + +public: + // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters + void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); + + // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters + void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated); + + // Update parameters with respect to TVP, TVA and TVF, and generate next sample + void generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); + + // WG output in the log-space consists of two components which are to be added (or ring modulated) in the linear-space afterwards + LogSample getOutputLogSample(const bool first) const; + + // Deactivate the WG engine + void deactivate(); + + // Return active state of the WG engine + bool isActive() const; + + // Return true if the WG engine generates PCM wave samples + bool isPCMWave() const; + + // Return current PCM interpolation factor + Bit32u getPCMInterpolationFactor() const; +}; + +// LA32PartialPair contains a structure of two partials being mixed / ring modulated +class LA32PartialPair { + LA32WaveGenerator master; + LA32WaveGenerator slave; + bool ringModulated; + bool mixed; + + static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample); + +public: + enum PairType { + MASTER, + SLAVE + }; + + // ringModulated should be set to false for the structures with mixing or stereo output + // ringModulated should be set to true for the structures with ring modulation + // mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output + void init(const bool ringModulated, const bool mixed); + + // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters + void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); + + // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters + void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped); + + // Update parameters with respect to TVP, TVA and TVF, and generate next sample + void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); + + // Perform mixing / ring modulation and return the result + Bit16s nextOutSample(); + + // Deactivate the WG engine + void deactivate(const PairType master); + + // Return active state of the WG engine + bool isActive(const PairType master) const; +}; + +} // namespace MT32Emu + +#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H + +#endif // #if MT32EMU_ACCURATE_WG == 0 diff --git a/audio/softsynth/mt32/LegacyWaveGenerator.cpp b/audio/softsynth/mt32/LegacyWaveGenerator.cpp new file mode 100644 index 0000000000..35ca975018 --- /dev/null +++ b/audio/softsynth/mt32/LegacyWaveGenerator.cpp @@ -0,0 +1,347 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +//#include <cmath> +#include "mt32emu.h" +#include "mmath.h" +#include "LegacyWaveGenerator.h" + +#if MT32EMU_ACCURATE_WG == 1 + +namespace MT32Emu { + +static const float MIDDLE_CUTOFF_VALUE = 128.0f; +static const float RESONANCE_DECAY_THRESHOLD_CUTOFF_VALUE = 144.0f; +static const float MAX_CUTOFF_VALUE = 240.0f; + +float LA32WaveGenerator::getPCMSample(unsigned int position) { + if (position >= pcmWaveLength) { + if (!pcmWaveLooped) { + return 0; + } + position = position % pcmWaveLength; + } + Bit16s pcmSample = pcmWaveAddress[position]; + float sampleValue = EXP2F(((pcmSample & 32767) - 32787.0f) / 2048.0f); + return ((pcmSample & 32768) == 0) ? sampleValue : -sampleValue; +} + +void LA32WaveGenerator::initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { + this->sawtoothWaveform = sawtoothWaveform; + this->pulseWidth = pulseWidth; + this->resonance = resonance; + + wavePos = 0.0f; + lastFreq = 0.0f; + + pcmWaveAddress = NULL; + active = true; +} + +void LA32WaveGenerator::initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated) { + this->pcmWaveAddress = pcmWaveAddress; + this->pcmWaveLength = pcmWaveLength; + this->pcmWaveLooped = pcmWaveLooped; + this->pcmWaveInterpolated = pcmWaveInterpolated; + + pcmPosition = 0.0f; + active = true; +} + +float LA32WaveGenerator::generateNextSample(const Bit32u ampVal, const Bit16u pitch, const Bit32u cutoffRampVal) { + if (!active) { + return 0.0f; + } + + this->amp = amp; + this->pitch = pitch; + + float sample = 0.0f; + + // SEMI-CONFIRMED: From sample analysis: + // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. + // This gives results within +/- 2 at the output (before any DAC bitshifting) + // when sustaining at levels 156 - 255 with no modifiers. + // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255. + // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces + // positive amps, so negative still needs to be explored, as well as lower levels. + // + // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. + + float amp = EXP2F(ampVal / -1024.0f / 4096.0f); + float freq = EXP2F(pitch / 4096.0f - 16.0f) * SAMPLE_RATE; + + if (isPCMWave()) { + // Render PCM waveform + int len = pcmWaveLength; + int intPCMPosition = (int)pcmPosition; + if (intPCMPosition >= len && !pcmWaveLooped) { + // We're now past the end of a non-looping PCM waveform so it's time to die. + deactivate(); + return 0.0f; + } + float positionDelta = freq * 2048.0f / SAMPLE_RATE; + + // Linear interpolation + float firstSample = getPCMSample(intPCMPosition); + // We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. + // It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial + // is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). + if (pcmWaveInterpolated) { + sample = firstSample + (getPCMSample(intPCMPosition + 1) - firstSample) * (pcmPosition - intPCMPosition); + } else { + sample = firstSample; + } + + float newPCMPosition = pcmPosition + positionDelta; + if (pcmWaveLooped) { + newPCMPosition = fmod(newPCMPosition, (float)pcmWaveLength); + } + pcmPosition = newPCMPosition; + } else { + // Render synthesised waveform + wavePos *= lastFreq / freq; + lastFreq = freq; + + float resAmp = EXP2F(1.0f - (32 - resonance) / 4.0f); + { + //static const float resAmpFactor = EXP2F(-7); + //resAmp = EXP2I(resonance << 10) * resAmpFactor; + } + + // The cutoffModifier may not be supposed to be directly added to the cutoff - + // it may for example need to be multiplied in some way. + // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4). + // More research is needed to be sure that this is correct, however. + float cutoffVal = cutoffRampVal / 262144.0f; + if (cutoffVal > MAX_CUTOFF_VALUE) { + cutoffVal = MAX_CUTOFF_VALUE; + } + + // Wave length in samples + float waveLen = SAMPLE_RATE / freq; + + // Init cosineLen + float cosineLen = 0.5f * waveLen; + if (cutoffVal > MIDDLE_CUTOFF_VALUE) { + cosineLen *= EXP2F((cutoffVal - MIDDLE_CUTOFF_VALUE) / -16.0f); // found from sample analysis + } + + // Start playing in center of first cosine segment + // relWavePos is shifted by a half of cosineLen + float relWavePos = wavePos + 0.5f * cosineLen; + if (relWavePos > waveLen) { + relWavePos -= waveLen; + } + + // Ratio of positive segment to wave length + float pulseLen = 0.5f; + if (pulseWidth > 128) { + pulseLen = EXP2F((64 - pulseWidth) / 64.0f); + //static const float pulseLenFactor = EXP2F(-192 / 64); + //pulseLen = EXP2I((256 - pulseWidthVal) << 6) * pulseLenFactor; + } + pulseLen *= waveLen; + + float hLen = pulseLen - cosineLen; + + // Ignore pulsewidths too high for given freq + if (hLen < 0.0f) { + hLen = 0.0f; + } + + // Ignore pulsewidths too high for given freq and cutoff + float lLen = waveLen - hLen - 2 * cosineLen; + if (lLen < 0.0f) { + lLen = 0.0f; + } + + // Correct resAmp for cutoff in range 50..66 + if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) { + resAmp *= sin(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f); + } + + // Produce filtered square wave with 2 cosine waves on slopes + + // 1st cosine segment + if (relWavePos < cosineLen) { + sample = -cos(FLOAT_PI * relWavePos / cosineLen); + } else + + // high linear segment + if (relWavePos < (cosineLen + hLen)) { + sample = 1.f; + } else + + // 2nd cosine segment + if (relWavePos < (2 * cosineLen + hLen)) { + sample = cos(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen); + } else { + + // low linear segment + sample = -1.f; + } + + if (cutoffVal < 128.0f) { + + // Attenuate samples below cutoff 50 + // Found by sample analysis + sample *= EXP2F(-0.125f * (128.0f - cutoffVal)); + } else { + + // Add resonance sine. Effective for cutoff > 50 only + float resSample = 1.0f; + + // Resonance decay speed factor + float resAmpDecayFactor = Tables::getInstance().resAmpDecayFactor[resonance >> 2]; + + // Now relWavePos counts from the middle of first cosine + relWavePos = wavePos; + + // negative segments + if (!(relWavePos < (cosineLen + hLen))) { + resSample = -resSample; + relWavePos -= cosineLen + hLen; + + // From the digital captures, the decaying speed of the resonance sine is found a bit different for the positive and the negative segments + resAmpDecayFactor += 0.25f; + } + + // Resonance sine WG + resSample *= sin(FLOAT_PI * relWavePos / cosineLen); + + // Resonance sine amp + float resAmpFadeLog2 = -0.125f * resAmpDecayFactor * (relWavePos / cosineLen); // seems to be exact + float resAmpFade = EXP2F(resAmpFadeLog2); + + // Now relWavePos set negative to the left from center of any cosine + relWavePos = wavePos; + + // negative segment + if (!(wavePos < (waveLen - 0.5f * cosineLen))) { + relWavePos -= waveLen; + } else + + // positive segment + if (!(wavePos < (hLen + 0.5f * cosineLen))) { + relWavePos -= cosineLen + hLen; + } + + // To ensure the output wave has no breaks, two different windows are appied to the beginning and the ending of the resonance sine segment + if (relWavePos < 0.5f * cosineLen) { + float syncSine = sin(FLOAT_PI * relWavePos / cosineLen); + if (relWavePos < 0.0f) { + // The window is synchronous square sine here + resAmpFade *= syncSine * syncSine; + } else { + // The window is synchronous sine here + resAmpFade *= syncSine; + } + } + + sample += resSample * resAmp * resAmpFade; + } + + // sawtooth waves + if (sawtoothWaveform) { + sample *= cos(FLOAT_2PI * wavePos / waveLen); + } + + wavePos++; + + // wavePos isn't supposed to be > waveLen + if (wavePos > waveLen) { + wavePos -= waveLen; + } + } + + // Multiply sample with current TVA value + sample *= amp; + return sample; +} + +void LA32WaveGenerator::deactivate() { + active = false; +} + +bool LA32WaveGenerator::isActive() const { + return active; +} + +bool LA32WaveGenerator::isPCMWave() const { + return pcmWaveAddress != NULL; +} + +void LA32PartialPair::init(const bool ringModulated, const bool mixed) { + this->ringModulated = ringModulated; + this->mixed = mixed; + masterOutputSample = 0.0f; + slaveOutputSample = 0.0f; +} + +void LA32PartialPair::initSynth(const PairType useMaster, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance) { + if (useMaster == MASTER) { + master.initSynth(sawtoothWaveform, pulseWidth, resonance); + } else { + slave.initSynth(sawtoothWaveform, pulseWidth, resonance); + } +} + +void LA32PartialPair::initPCM(const PairType useMaster, const Bit16s *pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped) { + if (useMaster == MASTER) { + master.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, true); + } else { + slave.initPCM(pcmWaveAddress, pcmWaveLength, pcmWaveLooped, !ringModulated); + } +} + +void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff) { + if (useMaster == MASTER) { + masterOutputSample = master.generateNextSample(amp, pitch, cutoff); + } else { + slaveOutputSample = slave.generateNextSample(amp, pitch, cutoff); + } +} + +Bit16s LA32PartialPair::nextOutSample() { + float outputSample; + if (ringModulated) { + float ringModulatedSample = masterOutputSample * slaveOutputSample; + outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample; + } else { + outputSample = masterOutputSample + slaveOutputSample; + } + return Bit16s(outputSample * 8192.0f); +} + +void LA32PartialPair::deactivate(const PairType useMaster) { + if (useMaster == MASTER) { + master.deactivate(); + masterOutputSample = 0.0f; + } else { + slave.deactivate(); + slaveOutputSample = 0.0f; + } +} + +bool LA32PartialPair::isActive(const PairType useMaster) const { + return useMaster == MASTER ? master.isActive() : slave.isActive(); +} + +} + +#endif // #if MT32EMU_ACCURATE_WG == 1 diff --git a/audio/softsynth/mt32/LegacyWaveGenerator.h b/audio/softsynth/mt32/LegacyWaveGenerator.h new file mode 100644 index 0000000000..81c1b9c713 --- /dev/null +++ b/audio/softsynth/mt32/LegacyWaveGenerator.h @@ -0,0 +1,146 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#if MT32EMU_ACCURATE_WG == 1 + +#ifndef MT32EMU_LA32_WAVE_GENERATOR_H +#define MT32EMU_LA32_WAVE_GENERATOR_H + +namespace MT32Emu { + +/** + * LA32WaveGenerator is aimed to represent the exact model of LA32 wave generator. + * The output square wave is created by adding high / low linear segments in-between + * the rising and falling cosine segments. Basically, it’s very similar to the phase distortion synthesis. + * Behaviour of a true resonance filter is emulated by adding decaying sine wave. + * The beginning and the ending of the resonant sine is multiplied by a cosine window. + * To synthesise sawtooth waves, the resulting square wave is multiplied by synchronous cosine wave. + */ +class LA32WaveGenerator { + //*************************************************************************** + // The local copy of partial parameters below + //*************************************************************************** + + bool active; + + // True means the resulting square wave is to be multiplied by the synchronous cosine + bool sawtoothWaveform; + + // Logarithmic amp of the wave generator + Bit32u amp; + + // Logarithmic frequency of the resulting wave + Bit16u pitch; + + // Values in range [1..31] + // Value 1 correspong to the minimum resonance + Bit8u resonance; + + // Processed value in range [0..255] + // Values in range [0..128] have no effect and the resulting wave remains symmetrical + // Value 255 corresponds to the maximum possible asymmetric of the resulting wave + Bit8u pulseWidth; + + // Composed of the base cutoff in range [78..178] left-shifted by 18 bits and the TVF modifier + Bit32u cutoffVal; + + // Logarithmic PCM sample start address + const Bit16s *pcmWaveAddress; + + // Logarithmic PCM sample length + Bit32u pcmWaveLength; + + // true for looped logarithmic PCM samples + bool pcmWaveLooped; + + // false for slave PCM partials in the structures with the ring modulation + bool pcmWaveInterpolated; + + //*************************************************************************** + // Internal variables below + //*************************************************************************** + + float wavePos; + float lastFreq; + float pcmPosition; + + float getPCMSample(unsigned int position); + +public: + // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters + void initSynth(const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); + + // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters + void initPCM(const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped, const bool pcmWaveInterpolated); + + // Update parameters with respect to TVP, TVA and TVF, and generate next sample + float generateNextSample(const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); + + // Deactivate the WG engine + void deactivate(); + + // Return active state of the WG engine + bool isActive() const; + + // Return true if the WG engine generates PCM wave samples + bool isPCMWave() const; +}; + +// LA32PartialPair contains a structure of two partials being mixed / ring modulated +class LA32PartialPair { + LA32WaveGenerator master; + LA32WaveGenerator slave; + bool ringModulated; + bool mixed; + float masterOutputSample; + float slaveOutputSample; + +public: + enum PairType { + MASTER, + SLAVE + }; + + // ringModulated should be set to false for the structures with mixing or stereo output + // ringModulated should be set to true for the structures with ring modulation + // mixed is used for the structures with ring modulation and indicates whether the master partial output is mixed to the ring modulator output + void init(const bool ringModulated, const bool mixed); + + // Initialise the WG engine for generation of synth partial samples and set up the invariant parameters + void initSynth(const PairType master, const bool sawtoothWaveform, const Bit8u pulseWidth, const Bit8u resonance); + + // Initialise the WG engine for generation of PCM partial samples and set up the invariant parameters + void initPCM(const PairType master, const Bit16s * const pcmWaveAddress, const Bit32u pcmWaveLength, const bool pcmWaveLooped); + + // Update parameters with respect to TVP, TVA and TVF, and generate next sample + void generateNextSample(const PairType master, const Bit32u amp, const Bit16u pitch, const Bit32u cutoff); + + // Perform mixing / ring modulation and return the result + Bit16s nextOutSample(); + + // Deactivate the WG engine + void deactivate(const PairType master); + + // Return active state of the WG engine + bool isActive(const PairType master) const; +}; + +} // namespace MT32Emu + +#endif // #ifndef MT32EMU_LA32_WAVE_GENERATOR_H + +#endif // #if MT32EMU_ACCURATE_WG == 1 diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp index 75912f38a8..62ba346c35 100644 --- a/audio/softsynth/mt32/Part.cpp +++ b/audio/softsynth/mt32/Part.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -68,18 +68,16 @@ Part::Part(Synth *useSynth, unsigned int usePartNum) { activePartialCount = 0; memset(patchCache, 0, sizeof(patchCache)); for (int i = 0; i < MT32EMU_MAX_POLY; i++) { - freePolys.push_front(new Poly(this)); + freePolys.prepend(new Poly(this)); } } Part::~Part() { - while (!activePolys.empty()) { - delete activePolys.front(); - activePolys.pop_front(); + while (!activePolys.isEmpty()) { + delete activePolys.takeFirst(); } - while (!freePolys.empty()) { - delete freePolys.front(); - freePolys.pop_front(); + while (!freePolys.isEmpty()) { + delete freePolys.takeFirst(); } } @@ -209,6 +207,7 @@ void RhythmPart::setTimbre(TimbreParam * /*timbre*/) { void Part::setTimbre(TimbreParam *timbre) { *timbreTemp = *timbre; + synth->newTimbreSet(partNum, timbre->common.name); } unsigned int RhythmPart::getAbsTimbreNum() const { @@ -245,8 +244,8 @@ void Part::backupCacheToPartials(PatchCache cache[4]) { // if so then duplicate the cached data from the part to the partial so that // we can change the part's cache without affecting the partial. // We delay this until now to avoid a copy operation with every note played - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - (*polyIt)->backupCacheToPartials(cache); + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { + poly->backupCacheToPartials(cache); } } @@ -445,8 +444,7 @@ void Part::abortPoly(Poly *poly) { } bool Part::abortFirstPoly(unsigned int key) { - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getKey() == key) { abortPoly(poly); return true; @@ -456,8 +454,7 @@ bool Part::abortFirstPoly(unsigned int key) { } bool Part::abortFirstPoly(PolyState polyState) { - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getState() == polyState) { abortPoly(poly); return true; @@ -474,10 +471,10 @@ bool Part::abortFirstPolyPreferHeld() { } bool Part::abortFirstPoly() { - if (activePolys.empty()) { + if (activePolys.isEmpty()) { return false; } - abortPoly(activePolys.front()); + abortPoly(activePolys.getFirst()); return true; } @@ -502,17 +499,16 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt return; } - if (freePolys.empty()) { + if (freePolys.isEmpty()) { synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity); return; } - Poly *poly = freePolys.front(); - freePolys.pop_front(); + Poly *poly = freePolys.takeFirst(); if (patchTemp->patch.assignMode & 1) { // Priority to data first received - activePolys.push_front(poly); + activePolys.prepend(poly); } else { - activePolys.push_back(poly); + activePolys.append(poly); } Partial *partials[4]; @@ -537,16 +533,20 @@ void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhyt #if MT32EMU_MONITOR_PARTIALS > 1 synth->printPartialUsage(); #endif + synth->partStateChanged(partNum, true); + synth->polyStateChanged(partNum); } void Part::allNotesOff() { // The MIDI specification states - and Mok confirms - that all notes off (0x7B) // should treat the hold pedal as usual. - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; - // FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed - // applies to AllNotesOff. - poly->noteOff(holdpedal); + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { + // FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed applies to AllNotesOff. + // if (poly->canSustain() || poly->getKey() == 0) { + // FIXME: The real devices are found to be ignoring non-sustaining polys while processing AllNotesOff. Need to be confirmed. + if (poly->canSustain()) { + poly->noteOff(holdpedal); + } } } @@ -554,15 +554,13 @@ void Part::allSoundOff() { // MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal. // This controller is not actually implemented by the synths, though (according to the docs and Mok) - // we're only using this method internally. - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { poly->startDecay(); } } void Part::stopPedalHold() { - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { poly->stopPedalHold(); } } @@ -580,8 +578,7 @@ void Part::stopNote(unsigned int key) { synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key); #endif - for (Common::List<Poly *>::iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { // Generally, non-sustaining instruments ignore note off. They die away eventually anyway. // Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held. if (poly->getKey() == key && (poly->canSustain() || key == 0)) { @@ -602,8 +599,7 @@ unsigned int Part::getActivePartialCount() const { unsigned int Part::getActiveNonReleasingPartialCount() const { unsigned int activeNonReleasingPartialCount = 0; - for (Common::List<Poly *>::const_iterator polyIt = activePolys.begin(); polyIt != activePolys.end(); polyIt++) { - Poly *poly = *polyIt; + for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) { if (poly->getState() != POLY_Releasing) { activeNonReleasingPartialCount += poly->getActivePartialCount(); } @@ -615,7 +611,103 @@ void Part::partialDeactivated(Poly *poly) { activePartialCount--; if (!poly->isActive()) { activePolys.remove(poly); - freePolys.push_front(poly); + freePolys.prepend(poly); + synth->polyStateChanged(partNum); + } + if (activePartialCount == 0) { + synth->partStateChanged(partNum, false); + } +} + +//#define POLY_LIST_DEBUG + +PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {} + +bool PolyList::isEmpty() const { +#ifdef POLY_LIST_DEBUG + if ((firstPoly == NULL || lastPoly == NULL) && firstPoly != lastPoly) { + printf("PolyList: desynchronised firstPoly & lastPoly pointers\n"); + } +#endif + return firstPoly == NULL && lastPoly == NULL; +} + +Poly *PolyList::getFirst() const { + return firstPoly; +} + +Poly *PolyList::getLast() const { + return lastPoly; +} + +void PolyList::prepend(Poly *poly) { +#ifdef POLY_LIST_DEBUG + if (poly->getNext() != NULL) { + printf("PolyList: Non-NULL next field in a Poly being prepended is ignored\n"); + } +#endif + poly->setNext(firstPoly); + firstPoly = poly; + if (lastPoly == NULL) { + lastPoly = poly; + } +} + +void PolyList::append(Poly *poly) { +#ifdef POLY_LIST_DEBUG + if (poly->getNext() != NULL) { + printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n"); + } +#endif + poly->setNext(NULL); + if (lastPoly != NULL) { +#ifdef POLY_LIST_DEBUG + if (lastPoly->getNext() != NULL) { + printf("PolyList: Non-NULL next field in the lastPoly\n"); + } +#endif + lastPoly->setNext(poly); + } + lastPoly = poly; + if (firstPoly == NULL) { + firstPoly = poly; + } +} + +Poly *PolyList::takeFirst() { + Poly *oldFirst = firstPoly; + firstPoly = oldFirst->getNext(); + if (firstPoly == NULL) { +#ifdef POLY_LIST_DEBUG + if (lastPoly != oldFirst) { + printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n"); + } +#endif + lastPoly = NULL; + } + oldFirst->setNext(NULL); + return oldFirst; +} + +void PolyList::remove(Poly * const polyToRemove) { + if (polyToRemove == firstPoly) { + takeFirst(); + return; + } + for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) { + if (poly->getNext() == polyToRemove) { + if (polyToRemove == lastPoly) { +#ifdef POLY_LIST_DEBUG + if (lastPoly->getNext() != NULL) { + printf("PolyList: Non-NULL next field in the lastPoly\n"); + } +#endif + lastPoly = poly; + } + poly->setNext(polyToRemove->getNext()); + polyToRemove->setNext(NULL); + break; + } } } diff --git a/audio/softsynth/mt32/Part.h b/audio/softsynth/mt32/Part.h index 5c59c6d61f..b6585880fe 100644 --- a/audio/softsynth/mt32/Part.h +++ b/audio/softsynth/mt32/Part.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -18,13 +18,27 @@ #ifndef MT32EMU_PART_H #define MT32EMU_PART_H -#include <common/list.h> - namespace MT32Emu { class PartialManager; class Synth; +class PolyList { +private: + Poly *firstPoly; + Poly *lastPoly; + +public: + PolyList(); + bool isEmpty() const; + Poly *getFirst() const; + Poly *getLast() const; + void prepend(Poly *poly); + void append(Poly *poly); + Poly *takeFirst(); + void remove(Poly * const poly); +}; + class Part { private: // Direct pointer to sysex-addressable memory dedicated to this part (valid for parts 1-8, NULL for rhythm) @@ -37,8 +51,8 @@ private: unsigned int activePartialCount; PatchCache patchCache[4]; - Common::List<Poly *> freePolys; - Common::List<Poly *> activePolys; + PolyList freePolys; + PolyList activePolys; void setPatch(const PatchParam *patch); unsigned int midiKeyToKey(unsigned int midiKey); diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index 03bec560b8..a0aec90ec4 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -22,7 +22,7 @@ #include "mt32emu.h" #include "mmath.h" -using namespace MT32Emu; +namespace MT32Emu { #ifdef INACCURATE_SMOOTH_PAN // Mok wanted an option for smoother panning, and we love Mok. @@ -35,7 +35,12 @@ static const float PAN_NUMERATOR_MASTER[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f}; Partial::Partial(Synth *useSynth, int useDebugPartialNum) : - synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0), tva(new TVA(this, &Ramp)), tvp(new TVP(this)), tvf(new TVF(this, &cutoffModifierRamp)) { + synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) { + // Initialisation of tva, tvp and tvf uses 'this' pointer + // and thus should not be in the initializer list to avoid a compiler warning + tva = new TVA(this, &Ramp); + tvp = new TVP(this); + tvf = new TVF(this, &cutoffModifierRamp); ownerPart = -1; poly = NULL; pair = NULL; @@ -81,26 +86,23 @@ void Partial::deactivate() { ownerPart = -1; if (poly != NULL) { poly->partialDeactivated(this); - if (pair != NULL) { - pair->pair = NULL; - } } + synth->partialStateChanged(this, tva->getPhase(), TVA_PHASE_DEAD); #if MT32EMU_MONITOR_PARTIALS > 2 synth->printDebug("[+%lu] [Partial %d] Deactivated", sampleNum, debugPartialNum); synth->printPartialUsage(sampleNum); #endif -} - -// DEPRECATED: This should probably go away eventually, it's currently only used as a kludge to protect our old assumptions that -// rhythm part notes were always played as key MIDDLEC. -int Partial::getKey() const { - if (poly == NULL) { - return -1; - } else if (ownerPart == 8) { - // FIXME: Hack, should go away after new pitch stuff is committed (and possibly some TVF changes) - return MIDDLEC; + if (isRingModulatingSlave()) { + pair->la32Pair.deactivate(LA32PartialPair::SLAVE); } else { - return poly->getKey(); + la32Pair.deactivate(LA32PartialPair::MASTER); + if (hasRingModulatingSlave()) { + pair->deactivate(); + pair = NULL; + } + } + if (pair != NULL) { + pair->pair = NULL; } } @@ -133,6 +135,25 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us stereoVolume.leftVol = panVal / 7.0f; stereoVolume.rightVol = 1.0f - stereoVolume.leftVol; + // SEMI-CONFIRMED: From sample analysis: + // Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways. + // Either partial pairs are added or subtracted, it depends on how the partial pairs are allocated. + // It seems that partials are grouped into quarters and if the partial pairs are allocated in different quarters the subtraction happens. + // Though, this matters little for the majority of timbres, it becomes crucial for timbres which contain several partials that sound very close. + // In this case that timbre can sound totally different depending of the way it is mixed up. + // Most easily this effect can be displayed with the help of a special timbre consisting of several identical square wave partials (3 or 4). + // Say, it is 3-partial timbre. Just play any two notes simultaneously and the polys very probably are mixed differently. + // Moreover, the partial allocator retains the last partial assignment it did and all the subsequent notes will sound the same as the last released one. + // The situation is better with 4-partial timbres since then a whole quarter is assigned for each poly. However, if a 3-partial timbre broke the normal + // whole-quarter assignment or after some partials got aborted, even 4-partial timbres can be found sounding differently. + // This behaviour is also confirmed with two more special timbres: one with identical sawtooth partials, and one with PCM wave 02. + // For my personal taste, this behaviour rather enriches the sounding and should be emulated. + // Also, the current partial allocator model probably needs to be refined. + if (debugPartialNum & 8) { + stereoVolume.leftVol = -stereoVolume.leftVol; + stereoVolume.rightVol = -stereoVolume.rightVol; + } + if (patchCache->PCMPartial) { pcmNum = patchCache->pcm; if (synth->controlROMMap->pcmCount > 128) { @@ -144,37 +165,75 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us pcmWave = &synth->pcmWaves[pcmNum]; } else { pcmWave = NULL; - wavePos = 0.0f; - lastFreq = 0.0; } // CONFIRMED: pulseWidthVal calculation is based on information from Mok - pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + synth->tables.pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth]; + pulseWidthVal = (poly->getVelocity() - 64) * (patchCache->srcPartial.wg.pulseWidthVeloSensitivity - 7) + Tables::getInstance().pulseWidth100To255[patchCache->srcPartial.wg.pulseWidth]; if (pulseWidthVal < 0) { pulseWidthVal = 0; } else if (pulseWidthVal > 255) { pulseWidthVal = 255; } - pcmPosition = 0.0f; pair = pairPartial; alreadyOutputed = false; tva->reset(part, patchCache->partialParam, rhythmTemp); tvp->reset(part, patchCache->partialParam); tvf->reset(patchCache->partialParam, tvp->getBasePitch()); + + LA32PartialPair::PairType pairType; + LA32PartialPair *useLA32Pair; + if (isRingModulatingSlave()) { + pairType = LA32PartialPair::SLAVE; + useLA32Pair = &pair->la32Pair; + } else { + pairType = LA32PartialPair::MASTER; + la32Pair.init(hasRingModulatingSlave(), mixType == 1); + useLA32Pair = &la32Pair; + } + if (isPCM()) { + useLA32Pair->initPCM(pairType, &synth->pcmROMData[pcmWave->addr], pcmWave->len, pcmWave->loop); + } else { + useLA32Pair->initSynth(pairType, (patchCache->waveform & 1) != 0, pulseWidthVal, patchCache->srcPartial.tvf.resonance + 1); + } + if (!hasRingModulatingSlave()) { + la32Pair.deactivate(LA32PartialPair::SLAVE); + } + // Temporary integration hack + stereoVolume.leftVol /= 8192.0f; + stereoVolume.rightVol /= 8192.0f; } -float Partial::getPCMSample(unsigned int position) { - if (position >= pcmWave->len) { - if (!pcmWave->loop) { - return 0; - } - position = position % pcmWave->len; +Bit32u Partial::getAmpValue() { + // SEMI-CONFIRMED: From sample analysis: + // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. + // This gives results within +/- 2 at the output (before any DAC bitshifting) + // when sustaining at levels 156 - 255 with no modifiers. + // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255. + // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces + // positive amps, so negative still needs to be explored, as well as lower levels. + // + // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. + // TODO: The tests above were performed using the float model, to be refined + Bit32u ampRampVal = 67117056 - ampRamp.nextValue(); + if (ampRamp.checkInterrupt()) { + tva->handleInterrupt(); } - return synth->pcmROMData[pcmWave->addr + position]; + return ampRampVal; } -unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) { +Bit32u Partial::getCutoffValue() { + if (isPCM()) { + return 0; + } + Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue(); + if (cutoffModifierRamp.checkInterrupt()) { + tvf->handleInterrupt(); + } + return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal; +} + +unsigned long Partial::generateSamples(Bit16s *partialBuf, unsigned long length) { if (!isActive() || alreadyOutputed) { return 0; } @@ -182,279 +241,31 @@ unsigned long Partial::generateSamples(float *partialBuf, unsigned long length) synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()!", debugPartialNum); return 0; } - alreadyOutputed = true; - // Generate samples - for (sampleNum = 0; sampleNum < length; sampleNum++) { - float sample = 0; - Bit32u ampRampVal = ampRamp.nextValue(); - if (ampRamp.checkInterrupt()) { - tva->handleInterrupt(); - } - if (!tva->isPlaying()) { + if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) { deactivate(); break; } - // SEMI-CONFIRMED: From sample analysis: - // (1) Tested with a single partial playing PCM wave 77 with pitchCoarse 36 and no keyfollow, velocity follow, etc. - // This gives results within +/- 2 at the output (before any DAC bitshifting) - // when sustaining at levels 156 - 255 with no modifiers. - // (2) Tested with a special square wave partial (internal capture ID tva5) at TVA envelope levels 155-255. - // This gives deltas between -1 and 0 compared to the real output. Note that this special partial only produces - // positive amps, so negative still needs to be explored, as well as lower levels. - // - // Also still partially unconfirmed is the behaviour when ramping between levels, as well as the timing. - float amp = EXP2F((32772 - ampRampVal / 2048) / -2048.0f); - - Bit16u pitch = tvp->nextPitch(); - float freq = synth->tables.pitchToFreq[pitch]; - - if (patchCache->PCMPartial) { - // Render PCM waveform - int len = pcmWave->len; - int intPCMPosition = (int)pcmPosition; - if (intPCMPosition >= len && !pcmWave->loop) { - // We're now past the end of a non-looping PCM waveform so it's time to die. - deactivate(); - break; - } - Bit32u pcmAddr = pcmWave->addr; - float positionDelta = freq * 2048.0f / synth->myProp.sampleRate; - - // Linear interpolation - float firstSample = synth->pcmROMData[pcmAddr + intPCMPosition]; - float nextSample = getPCMSample(intPCMPosition + 1); - sample = firstSample + (nextSample - firstSample) * (pcmPosition - intPCMPosition); - - float newPCMPosition = pcmPosition + positionDelta; - if (pcmWave->loop) { - newPCMPosition = fmod(newPCMPosition, (float)pcmWave->len); - } - pcmPosition = newPCMPosition; - } else { - // Render synthesised waveform - wavePos *= lastFreq / freq; - lastFreq = freq; - - Bit32u cutoffModifierRampVal = cutoffModifierRamp.nextValue(); - if (cutoffModifierRamp.checkInterrupt()) { - tvf->handleInterrupt(); - } - float cutoffModifier = cutoffModifierRampVal / 262144.0f; - - // res corresponds to a value set in an LA32 register - Bit8u res = patchCache->srcPartial.tvf.resonance + 1; - - // EXP2F(1.0f - (32 - res) / 4.0f); - float resAmp = synth->tables.resAmpMax[res]; - - // The cutoffModifier may not be supposed to be directly added to the cutoff - - // it may for example need to be multiplied in some way. - // The 240 cutoffVal limit was determined via sample analysis (internal Munt capture IDs: glop3, glop4). - // More research is needed to be sure that this is correct, however. - float cutoffVal = tvf->getBaseCutoff() + cutoffModifier; - if (cutoffVal > 240.0f) { - cutoffVal = 240.0f; - } - - // Wave length in samples - float waveLen = synth->myProp.sampleRate / freq; - - // Init cosineLen - float cosineLen = 0.5f * waveLen; - if (cutoffVal > 128.0f) { -#if MT32EMU_ACCURATE_WG == 1 - cosineLen *= EXP2F((cutoffVal - 128.0f) / -16.0f); // found from sample analysis -#else - cosineLen *= synth->tables.cutoffToCosineLen[Bit32u((cutoffVal - 128.0f) * 8.0f)]; -#endif - } - - // Start playing in center of first cosine segment - // relWavePos is shifted by a half of cosineLen - float relWavePos = wavePos + 0.5f * cosineLen; - if (relWavePos > waveLen) { - relWavePos -= waveLen; - } - - float pulseLen = 0.5f; - if (pulseWidthVal > 128) { - pulseLen += synth->tables.pulseLenFactor[pulseWidthVal - 128]; - } - pulseLen *= waveLen; - - float lLen = pulseLen - cosineLen; - - // Ignore pulsewidths too high for given freq - if (lLen < 0.0f) { - lLen = 0.0f; - } - - // Ignore pulsewidths too high for given freq and cutoff - float hLen = waveLen - lLen - 2 * cosineLen; - if (hLen < 0.0f) { - hLen = 0.0f; - } - - // Correct resAmp for cutoff in range 50..66 - if ((cutoffVal >= 128.0f) && (cutoffVal < 144.0f)) { -#if MT32EMU_ACCURATE_WG == 1 - resAmp *= sinf(FLOAT_PI * (cutoffVal - 128.0f) / 32.0f); -#else - resAmp *= synth->tables.sinf10[Bit32u(64 * (cutoffVal - 128.0f))]; -#endif - } - - // Produce filtered square wave with 2 cosine waves on slopes - - // 1st cosine segment - if (relWavePos < cosineLen) { -#if MT32EMU_ACCURATE_WG == 1 - sample = -cosf(FLOAT_PI * relWavePos / cosineLen); -#else - sample = -synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) + 1024]; -#endif - } else - - // high linear segment - if (relWavePos < (cosineLen + hLen)) { - sample = 1.f; - } else - - // 2nd cosine segment - if (relWavePos < (2 * cosineLen + hLen)) { -#if MT32EMU_ACCURATE_WG == 1 - sample = cosf(FLOAT_PI * (relWavePos - (cosineLen + hLen)) / cosineLen); -#else - sample = synth->tables.sinf10[Bit32u(2048.0f * (relWavePos - (cosineLen + hLen)) / cosineLen) + 1024]; -#endif - } else { - - // low linear segment - sample = -1.f; - } - - if (cutoffVal < 128.0f) { - - // Attenuate samples below cutoff 50 - // Found by sample analysis -#if MT32EMU_ACCURATE_WG == 1 - sample *= EXP2F(-0.125f * (128.0f - cutoffVal)); -#else - sample *= synth->tables.cutoffToFilterAmp[Bit32u(cutoffVal * 8.0f)]; -#endif - } else { - - // Add resonance sine. Effective for cutoff > 50 only - float resSample = 1.0f; - - // Now relWavePos counts from the middle of first cosine - relWavePos = wavePos; - - // negative segments - if (!(relWavePos < (cosineLen + hLen))) { - resSample = -resSample; - relWavePos -= cosineLen + hLen; + la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue()); + if (hasRingModulatingSlave()) { + la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue()); + if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) { + pair->deactivate(); + if (mixType == 2) { + deactivate(); + break; } - - // Resonance sine WG -#if MT32EMU_ACCURATE_WG == 1 - resSample *= sinf(FLOAT_PI * relWavePos / cosineLen); -#else - resSample *= synth->tables.sinf10[Bit32u(2048.0f * relWavePos / cosineLen) & 4095]; -#endif - - // Resonance sine amp - float resAmpFade = EXP2F(-synth->tables.resAmpFadeFactor[res >> 2] * (relWavePos / cosineLen)); // seems to be exact - - // Now relWavePos set negative to the left from center of any cosine - relWavePos = wavePos; - - // negative segment - if (!(wavePos < (waveLen - 0.5f * cosineLen))) { - relWavePos -= waveLen; - } else - - // positive segment - if (!(wavePos < (hLen + 0.5f * cosineLen))) { - relWavePos -= cosineLen + hLen; - } - - // Fading to zero while within cosine segments to avoid jumps in the wave - // Sample analysis suggests that this window is very close to cosine - if (relWavePos < 0.5f * cosineLen) { -#if MT32EMU_ACCURATE_WG == 1 - resAmpFade *= 0.5f * (1.0f - cosf(FLOAT_PI * relWavePos / (0.5f * cosineLen))); -#else - resAmpFade *= 0.5f * (1.0f + synth->tables.sinf10[Bit32s(2048.0f * relWavePos / (0.5f * cosineLen)) + 3072]); -#endif - } - - sample += resSample * resAmp * resAmpFade; - } - - // sawtooth waves - if ((patchCache->waveform & 1) != 0) { -#if MT32EMU_ACCURATE_WG == 1 - sample *= cosf(FLOAT_2PI * wavePos / waveLen); -#else - sample *= synth->tables.sinf10[(Bit32u(4096.0f * wavePos / waveLen) & 4095) + 1024]; -#endif - } - - wavePos++; - - // wavePos isn't supposed to be > waveLen - if (wavePos > waveLen) { - wavePos -= waveLen; } } - - // Multiply sample with current TVA value - sample *= amp; - *partialBuf++ = sample; + *partialBuf++ = la32Pair.nextOutSample(); } unsigned long renderedSamples = sampleNum; sampleNum = 0; return renderedSamples; } -float *Partial::mixBuffersRingMix(float *buf1, float *buf2, unsigned long len) { - if (buf1 == NULL) { - return NULL; - } - if (buf2 == NULL) { - return buf1; - } - - while (len--) { - // FIXME: At this point we have no idea whether this is remotely correct... - *buf1 = *buf1 * *buf2 + *buf1; - buf1++; - buf2++; - } - return buf1; -} - -float *Partial::mixBuffersRing(float *buf1, float *buf2, unsigned long len) { - if (buf1 == NULL) { - return NULL; - } - if (buf2 == NULL) { - return NULL; - } - - while (len--) { - // FIXME: At this point we have no idea whether this is remotely correct... - *buf1 = *buf1 * *buf2; - buf1++; - buf2++; - } - return buf1; -} - bool Partial::hasRingModulatingSlave() const { return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2); } @@ -486,54 +297,14 @@ bool Partial::produceOutput(float *leftBuf, float *rightBuf, unsigned long lengt synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum); return false; } - - float *partialBuf = &myBuffer[0]; - unsigned long numGenerated = generateSamples(partialBuf, length); - if (mixType == 1 || mixType == 2) { - float *pairBuf; - unsigned long pairNumGenerated; - if (pair == NULL) { - pairBuf = NULL; - pairNumGenerated = 0; - } else { - pairBuf = &pair->myBuffer[0]; - pairNumGenerated = pair->generateSamples(pairBuf, numGenerated); - // pair will have been set to NULL if it deactivated within generateSamples() - if (pair != NULL) { - if (!isActive()) { - pair->deactivate(); - pair = NULL; - } else if (!pair->isActive()) { - pair = NULL; - } - } - } - if (pairNumGenerated > 0) { - if (mixType == 1) { - mixBuffersRingMix(partialBuf, pairBuf, pairNumGenerated); - } else { - mixBuffersRing(partialBuf, pairBuf, pairNumGenerated); - } - } - if (numGenerated > pairNumGenerated) { - if (mixType == 1) { - mixBuffersRingMix(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated); - } else { - mixBuffersRing(partialBuf + pairNumGenerated, NULL, numGenerated - pairNumGenerated); - } - } - } - + unsigned long numGenerated = generateSamples(myBuffer, length); for (unsigned int i = 0; i < numGenerated; i++) { - *leftBuf++ = partialBuf[i] * stereoVolume.leftVol; + *leftBuf++ = myBuffer[i] * stereoVolume.leftVol; + *rightBuf++ = myBuffer[i] * stereoVolume.rightVol; } - for (unsigned int i = 0; i < numGenerated; i++) { - *rightBuf++ = partialBuf[i] * stereoVolume.rightVol; - } - while (numGenerated < length) { + for (; numGenerated < length; numGenerated++) { *leftBuf++ = 0.0f; *rightBuf++ = 0.0f; - numGenerated++; } return true; } @@ -555,3 +326,5 @@ void Partial::startDecayAll() { tvp->startDecay(); tvf->startDecay(); } + +} diff --git a/audio/softsynth/mt32/Partial.h b/audio/softsynth/mt32/Partial.h index 5e250769ec..21b1bfe376 100644 --- a/audio/softsynth/mt32/Partial.h +++ b/audio/softsynth/mt32/Partial.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -44,12 +44,7 @@ private: int structurePosition; // 0 or 1 of a structure pair StereoVolume stereoVolume; - // Distance in (possibly fractional) samples from the start of the current pulse - float wavePos; - - float lastFreq; - - float myBuffer[MAX_SAMPLES_PER_RUN]; + Bit16s myBuffer[MAX_SAMPLES_PER_RUN]; // Only used for PCM partials int pcmNum; @@ -60,17 +55,16 @@ private: // Range: 0-255 int pulseWidthVal; - float pcmPosition; - Poly *poly; LA32Ramp ampRamp; LA32Ramp cutoffModifierRamp; - float *mixBuffersRingMix(float *buf1, float *buf2, unsigned long len); - float *mixBuffersRing(float *buf1, float *buf2, unsigned long len); + // TODO: This should be owned by PartialPair + LA32PartialPair la32Pair; - float getPCMSample(unsigned int position); + Bit32u getAmpValue(); + Bit32u getCutoffValue(); public: const PatchCache *patchCache; @@ -90,7 +84,6 @@ public: unsigned long debugGetSampleNum() const; int getOwnerPart() const; - int getKey() const; const Poly *getPoly() const; bool isActive() const; void activate(int part); @@ -111,7 +104,7 @@ public: bool produceOutput(float *leftBuf, float *rightBuf, unsigned long length); // This function writes mono sample output to the provided buffer, and returns the number of samples written - unsigned long generateSamples(float *partialBuf, unsigned long length); + unsigned long generateSamples(Bit16s *partialBuf, unsigned long length); }; } diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp index 0a6be826d6..436e7a353e 100644 --- a/audio/softsynth/mt32/PartialManager.cpp +++ b/audio/softsynth/mt32/PartialManager.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -20,7 +20,7 @@ #include "mt32emu.h" #include "PartialManager.h" -using namespace MT32Emu; +namespace MT32Emu { PartialManager::PartialManager(Synth *useSynth, Part **useParts) { synth = useSynth; @@ -248,3 +248,5 @@ const Partial *PartialManager::getPartial(unsigned int partialNum) const { } return partialTable[partialNum]; } + +} diff --git a/audio/softsynth/mt32/PartialManager.h b/audio/softsynth/mt32/PartialManager.h index bb78672457..a1c9266ea1 100644 --- a/audio/softsynth/mt32/PartialManager.h +++ b/audio/softsynth/mt32/PartialManager.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp index a2f00db73c..46574f8967 100644 --- a/audio/softsynth/mt32/Poly.cpp +++ b/audio/softsynth/mt32/Poly.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -29,6 +29,7 @@ Poly::Poly(Part *usePart) { partials[i] = NULL; } state = POLY_Inactive; + next = NULL; } void Poly::reset(unsigned int newKey, unsigned int newVelocity, bool newSustain, Partial **newPartials) { @@ -58,6 +59,9 @@ bool Poly::noteOff(bool pedalHeld) { return false; } if (pedalHeld) { + if (state == POLY_Held) { + return false; + } state = POLY_Held; } else { startDecay(); @@ -171,4 +175,12 @@ void Poly::partialDeactivated(Partial *partial) { part->partialDeactivated(this); } +Poly *Poly::getNext() { + return next; +} + +void Poly::setNext(Poly *poly) { + next = poly; +} + } diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h index cd15a776f5..068cf73d35 100644 --- a/audio/softsynth/mt32/Poly.h +++ b/audio/softsynth/mt32/Poly.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -41,6 +41,8 @@ private: Partial *partials[4]; + Poly *next; + public: Poly(Part *part); void reset(unsigned int key, unsigned int velocity, bool sustain, Partial **partials); @@ -60,6 +62,9 @@ public: bool isActive() const; void partialDeactivated(Partial *partial); + + Poly *getNext(); + void setNext(Poly *poly); }; } diff --git a/audio/softsynth/mt32/ROMInfo.cpp b/audio/softsynth/mt32/ROMInfo.cpp new file mode 100644 index 0000000000..9d05420874 --- /dev/null +++ b/audio/softsynth/mt32/ROMInfo.cpp @@ -0,0 +1,111 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +//#include <cstring> +#include "ROMInfo.h" + +namespace MT32Emu { + +// Known ROMs +static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_MT32_V1_05 = {65536, "e17a3a6d265bf1fa150312061134293d2b58288c", ROMInfo::Control, "ctrl_mt32_1_05", "MT-32 Control v1.05", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_MT32_V1_06 = {65536, "a553481f4e2794c10cfe597fef154eef0d8257de", ROMInfo::Control, "ctrl_mt32_1_06", "MT-32 Control v1.06", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_MT32_V1_07 = {65536, "b083518fffb7f66b03c23b7eb4f868e62dc5a987", ROMInfo::Control, "ctrl_mt32_1_07", "MT-32 Control v1.07", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_MT32_BLUER = {65536, "7b8c2a5ddb42fd0732e2f22b3340dcf5360edf92", ROMInfo::Control, "ctrl_mt32_bluer", "MT-32 Control BlueRidge", ROMInfo::Full, NULL, NULL}; + +static const ROMInfo CTRL_CM32L_V1_00 = {65536, "73683d585cd6948cc19547942ca0e14a0319456d", ROMInfo::Control, "ctrl_cm32l_1_00", "CM-32L/LAPC-I Control v1.00", ROMInfo::Full, NULL, NULL}; +static const ROMInfo CTRL_CM32L_V1_02 = {65536, "a439fbb390da38cada95a7cbb1d6ca199cd66ef8", ROMInfo::Control, "ctrl_cm32l_1_02", "CM-32L/LAPC-I Control v1.02", ROMInfo::Full, NULL, NULL}; + +static const ROMInfo PCM_MT32 = {524288, "f6b1eebc4b2d200ec6d3d21d51325d5b48c60252", ROMInfo::PCM, "pcm_mt32", "MT-32 PCM ROM", ROMInfo::Full, NULL, NULL}; +static const ROMInfo PCM_CM32L = {1048576, "289cc298ad532b702461bfc738009d9ebe8025ea", ROMInfo::PCM, "pcm_cm32l", "CM-32L/CM-64/LAPC-I PCM ROM", ROMInfo::Full, NULL, NULL}; + +static const ROMInfo * const ROM_INFOS[] = { + &CTRL_MT32_V1_04, + &CTRL_MT32_V1_05, + &CTRL_MT32_V1_06, + &CTRL_MT32_V1_07, + &CTRL_MT32_BLUER, + &CTRL_CM32L_V1_00, + &CTRL_CM32L_V1_02, + &PCM_MT32, + &PCM_CM32L, + NULL}; + +const ROMInfo* ROMInfo::getROMInfo(Common::File *file) { + size_t fileSize = file->size(); + // We haven't added the SHA1 checksum code in ScummVM, as the file size + // suffices for our needs for now. + //const char *fileDigest = file->getSHA1(); + for (int i = 0; ROM_INFOS[i] != NULL; i++) { + const ROMInfo *romInfo = ROM_INFOS[i]; + if (fileSize == romInfo->fileSize /*&& !strcmp(fileDigest, romInfo->sha1Digest)*/) { + return romInfo; + } + } + return NULL; +} + +void ROMInfo::freeROMInfo(const ROMInfo *romInfo) { + (void) romInfo; +} + +static int getROMCount() { + int count; + for(count = 0; ROM_INFOS[count] != NULL; count++) { + } + return count; +} + +const ROMInfo** ROMInfo::getROMInfoList(unsigned int types, unsigned int pairTypes) { + const ROMInfo **romInfoList = new const ROMInfo*[getROMCount() + 1]; + const ROMInfo **currentROMInList = romInfoList; + for(int i = 0; ROM_INFOS[i] != NULL; i++) { + const ROMInfo *romInfo = ROM_INFOS[i]; + if ((types & (1 << romInfo->type)) && (pairTypes & (1 << romInfo->pairType))) { + *currentROMInList++ = romInfo; + } + } + *currentROMInList = NULL; + return romInfoList; +} + +void ROMInfo::freeROMInfoList(const ROMInfo **romInfoList) { + delete[] romInfoList; +} + +const ROMImage* ROMImage::makeROMImage(Common::File *file) { + ROMImage *romImage = new ROMImage; + romImage->file = file; + romImage->romInfo = ROMInfo::getROMInfo(romImage->file); + return romImage; +} + +void ROMImage::freeROMImage(const ROMImage *romImage) { + ROMInfo::freeROMInfo(romImage->romInfo); + delete romImage; +} + + +Common::File* ROMImage::getFile() const { + return file; +} + +const ROMInfo* ROMImage::getROMInfo() const { + return romInfo; +} + +} diff --git a/audio/softsynth/mt32/ROMInfo.h b/audio/softsynth/mt32/ROMInfo.h new file mode 100644 index 0000000000..1d837dc5ee --- /dev/null +++ b/audio/softsynth/mt32/ROMInfo.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MT32EMU_ROMINFO_H +#define MT32EMU_ROMINFO_H + +//#include <cstddef> +#include "common/file.h" + +namespace MT32Emu { + +// Defines vital info about ROM file to be used by synth and applications + +struct ROMInfo { +public: + size_t fileSize; + const char *sha1Digest; + enum Type {PCM, Control, Reverb} type; + const char *shortName; + const char *description; + enum PairType {Full, FirstHalf, SecondHalf, Mux0, Mux1} pairType; + ROMInfo *pairROMInfo; + void *controlROMInfo; + + // Returns a ROMInfo struct by inspecting the size and the SHA1 hash + static const ROMInfo* getROMInfo(Common::File *file); + + // Currently no-op + static void freeROMInfo(const ROMInfo *romInfo); + + // Allows retrieving a NULL-terminated list of ROMInfos for a range of types and pairTypes + // (specified by bitmasks) + // Useful for GUI/console app to output information on what ROMs it supports + static const ROMInfo** getROMInfoList(unsigned int types, unsigned int pairTypes); + + // Frees the list of ROMInfos given + static void freeROMInfoList(const ROMInfo **romInfos); +}; + +// Synth::open() is to require a full control ROMImage and a full PCM ROMImage to work + +class ROMImage { +private: + Common::File *file; + const ROMInfo *romInfo; + +public: + + // Creates a ROMImage object given a ROMInfo and a File. Keeps a reference + // to the File and ROMInfo given, which must be freed separately by the user + // after the ROMImage is freed + static const ROMImage* makeROMImage(Common::File *file); + + // Must only be done after all Synths using the ROMImage are deleted + static void freeROMImage(const ROMImage *romImage); + + Common::File *getFile() const; + const ROMInfo *getROMInfo() const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/Structures.h b/audio/softsynth/mt32/Structures.h index cbce89ae18..43d2d1f226 100644 --- a/audio/softsynth/mt32/Structures.h +++ b/audio/softsynth/mt32/Structures.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index 7a1b5c2275..b7af992b99 100644 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -27,8 +27,10 @@ #include "mmath.h" #include "PartialManager.h" -#if MT32EMU_USE_AREVERBMODEL == 1 +#if MT32EMU_USE_REVERBMODEL == 1 #include "AReverbModel.h" +#elif MT32EMU_USE_REVERBMODEL == 2 +#include "BReverbModel.h" #else #include "FreeverbModel.h" #endif @@ -36,7 +38,7 @@ namespace MT32Emu { -const ControlROMMap ControlROMMaps[7] = { +static const ControlROMMap ControlROMMaps[7] = { // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO, tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A}, {0x4014, 22, "\000 ver1.05 06 Aug, 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x7414, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A}, @@ -140,22 +142,36 @@ Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { return checksum; } -Synth::Synth() { +Synth::Synth(ReportHandler *useReportHandler) { isOpen = false; reverbEnabled = true; reverbOverridden = false; -#if MT32EMU_USE_AREVERBMODEL == 1 - reverbModels[0] = new AReverbModel(&AReverbModel::REVERB_MODE_0_SETTINGS); - reverbModels[1] = new AReverbModel(&AReverbModel::REVERB_MODE_1_SETTINGS); - reverbModels[2] = new AReverbModel(&AReverbModel::REVERB_MODE_2_SETTINGS); + if (useReportHandler == NULL) { + reportHandler = new ReportHandler; + isDefaultReportHandler = true; + } else { + reportHandler = useReportHandler; + isDefaultReportHandler = false; + } + +#if MT32EMU_USE_REVERBMODEL == 1 + reverbModels[REVERB_MODE_ROOM] = new AReverbModel(REVERB_MODE_ROOM); + reverbModels[REVERB_MODE_HALL] = new AReverbModel(REVERB_MODE_HALL); + reverbModels[REVERB_MODE_PLATE] = new AReverbModel(REVERB_MODE_PLATE); + reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb(); +#elif MT32EMU_USE_REVERBMODEL == 2 + reverbModels[REVERB_MODE_ROOM] = new BReverbModel(REVERB_MODE_ROOM); + reverbModels[REVERB_MODE_HALL] = new BReverbModel(REVERB_MODE_HALL); + reverbModels[REVERB_MODE_PLATE] = new BReverbModel(REVERB_MODE_PLATE); + reverbModels[REVERB_MODE_TAP_DELAY] = new BReverbModel(REVERB_MODE_TAP_DELAY); #else - reverbModels[0] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f); - reverbModels[1] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f); - reverbModels[2] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f); + reverbModels[REVERB_MODE_ROOM] = new FreeverbModel(0.76f, 0.687770909f, 0.63f, 0, 0.5f); + reverbModels[REVERB_MODE_HALL] = new FreeverbModel(2.0f, 0.712025098f, 0.86f, 1, 0.5f); + reverbModels[REVERB_MODE_PLATE] = new FreeverbModel(0.4f, 0.939522749f, 0.38f, 2, 0.05f); + reverbModels[REVERB_MODE_TAP_DELAY] = new DelayReverb(); #endif - reverbModels[3] = new DelayReverb(); reverbModel = NULL; setDACInputMode(DACInputMode_NICE); setOutputGain(1.0f); @@ -170,31 +186,49 @@ Synth::~Synth() { for (int i = 0; i < 4; i++) { delete reverbModels[i]; } + if (isDefaultReportHandler) { + delete reportHandler; + } +} + +void ReportHandler::showLCDMessage(const char *data) { + printf("WRITE-LCD: %s", data); + printf("\n"); } -int Synth::report(ReportType type, const void *data) { - if (myProp.report != NULL) { - return myProp.report(myProp.userData, type, data); +void ReportHandler::printDebug(const char *fmt, va_list list) { + vprintf(fmt, list); + printf("\n"); +} + +void Synth::partStateChanged(int partNum, bool isPartActive) { + reportHandler->onPartStateChanged(partNum, isPartActive); +} + +void Synth::polyStateChanged(int partNum) { + reportHandler->onPolyStateChanged(partNum); +} + +void Synth::partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase) { + for (int i = 0; i < MT32EMU_MAX_PARTIALS; i++) { + if (getPartial(i) == partial) { + reportHandler->onPartialStateChanged(i, oldPartialPhase, newPartialPhase); + break; + } } - return 0; } -unsigned int Synth::getSampleRate() const { - return myProp.sampleRate; +void Synth::newTimbreSet(int partNum, char patchName[]) { + reportHandler->onProgramChanged(partNum, patchName); } void Synth::printDebug(const char *fmt, ...) { va_list ap; va_start(ap, fmt); - if (myProp.printDebug != NULL) { - myProp.printDebug(myProp.userData, fmt, ap); - } else { #if MT32EMU_DEBUG_SAMPLESTAMPS > 0 - printf("[%u] ", renderedSampleCount); + reportHandler->printDebug("[%u] ", renderedSampleCount); #endif - vprintf(fmt, ap); - printf("\n"); - } + reportHandler->printDebug(fmt, ap); va_end(ap); } @@ -244,80 +278,60 @@ void Synth::setReverbOutputGain(float newReverbOutputGain) { reverbOutputGain = newReverbOutputGain; } -Common::File *Synth::openFile(const char *filename) { - if (myProp.openFile != NULL) { - return myProp.openFile(myProp.userData, filename); - } - char pathBuf[2048]; - if (myProp.baseDir != NULL) { - strcpy(&pathBuf[0], myProp.baseDir); - strcat(&pathBuf[0], filename); - filename = pathBuf; - } - Common::File *file = new Common::File(); - if (!file->open(filename)) { - delete file; - return NULL; - } - return file; -} - -void Synth::closeFile(Common::File *file) { - if (myProp.closeFile != NULL) { - myProp.closeFile(myProp.userData, file); - } else { - file->close(); - delete file; - } -} - -LoadResult Synth::loadControlROM(const char *filename) { - Common::File *file = openFile(filename); // ROM File - if (file == NULL) { - return LoadResult_NotFound; - } - size_t fileSize = file->size(); - if (fileSize != CONTROL_ROM_SIZE) { - printDebug("Control ROM file %s size mismatch: %i", filename, fileSize); +bool Synth::loadControlROM(const ROMImage &controlROMImage) { + if (&controlROMImage == NULL) return false; + Common::File *file = controlROMImage.getFile(); + const ROMInfo *controlROMInfo = controlROMImage.getROMInfo(); + if ((controlROMInfo == NULL) + || (controlROMInfo->type != ROMInfo::Control) + || (controlROMInfo->pairType != ROMInfo::Full)) { + return false; } +#if MT32EMU_MONITOR_INIT + printDebug("Found Control ROM: %s, %s", controlROMInfo->shortName, controlROMInfo->description); +#endif file->read(controlROMData, CONTROL_ROM_SIZE); - if (file->err()) { - closeFile(file); - return LoadResult_Unreadable; - } - closeFile(file); // Control ROM successfully loaded, now check whether it's a known type controlROMMap = NULL; for (unsigned int i = 0; i < sizeof(ControlROMMaps) / sizeof(ControlROMMaps[0]); i++) { if (memcmp(&controlROMData[ControlROMMaps[i].idPos], ControlROMMaps[i].idBytes, ControlROMMaps[i].idLen) == 0) { controlROMMap = &ControlROMMaps[i]; - return LoadResult_OK; + return true; } } - printDebug("%s does not match a known control ROM type", filename); - return LoadResult_Invalid; +#if MT32EMU_MONITOR_INIT + printDebug("Control ROM failed to load"); +#endif + return false; } -LoadResult Synth::loadPCMROM(const char *filename) { - Common::File *file = openFile(filename); // ROM File - if (file == NULL) { - return LoadResult_NotFound; +bool Synth::loadPCMROM(const ROMImage &pcmROMImage) { + if (&pcmROMImage == NULL) return false; + Common::File *file = pcmROMImage.getFile(); + const ROMInfo *pcmROMInfo = pcmROMImage.getROMInfo(); + if ((pcmROMInfo == NULL) + || (pcmROMInfo->type != ROMInfo::PCM) + || (pcmROMInfo->pairType != ROMInfo::Full)) { + return false; } +#if MT32EMU_MONITOR_INIT + printDebug("Found PCM ROM: %s, %s", pcmROMInfo->shortName, pcmROMInfo->description); +#endif size_t fileSize = file->size(); - if (fileSize < (size_t)(2 * pcmROMSize)) { - printDebug("PCM ROM file is too short (expected %d, got %d)", 2 * pcmROMSize, fileSize); - closeFile(file); - return LoadResult_Invalid; - } - if (file->err()) { - closeFile(file); - return LoadResult_Unreadable; + if (fileSize != (2 * pcmROMSize)) { +#if MT32EMU_MONITOR_INIT + printDebug("PCM ROM file has wrong size (expected %d, got %d)", 2 * pcmROMSize, fileSize); +#endif + return false; } - LoadResult rc = LoadResult_OK; - for (int i = 0; i < pcmROMSize; i++) { - Bit8u s = file->readByte(); - Bit8u c = file->readByte(); + + byte *buffer = new byte[file->size()]; + file->read(buffer, file->size()); + const byte *fileData = buffer; + for (size_t i = 0; i < pcmROMSize; i++) { + Bit8u s = *(fileData++); + Bit8u c = *(fileData++); int order[16] = {0, 9, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 8}; @@ -331,28 +345,20 @@ LoadResult Synth::loadPCMROM(const char *filename) { } log = log | (short)(bit << (15 - u)); } - bool negative = log < 0; - log &= 0x7FFF; - - // CONFIRMED from sample analysis to be 99.99%+ accurate with current TVA multiplier - float lin = EXP2F((32787 - log) / -2048.0f); + pcmROMData[i] = log; + } - if (negative) { - lin = -lin; - } + delete[] buffer; - pcmROMData[i] = lin; - } - closeFile(file); - return rc; + return true; } bool Synth::initPCMList(Bit16u mapAddress, Bit16u count) { ControlROMPCMStruct *tps = (ControlROMPCMStruct *)&controlROMData[mapAddress]; for (int i = 0; i < count; i++) { - int rAddr = tps[i].pos * 0x800; - int rLenExp = (tps[i].len & 0x70) >> 4; - int rLen = 0x800 << rLenExp; + size_t rAddr = tps[i].pos * 0x800; + size_t rLenExp = (tps[i].len & 0x70) >> 4; + size_t rLen = 0x800 << rLenExp; if (rAddr + rLen > pcmROMSize) { printDebug("Control ROM error: Wave map entry %d points to invalid PCM address 0x%04X, length 0x%04X", i, rAddr, rLen); return false; @@ -414,26 +420,19 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi return true; } -bool Synth::open(SynthProperties &useProp) { +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage) { if (isOpen) { return false; } prerenderReadIx = prerenderWriteIx = 0; - myProp = useProp; #if MT32EMU_MONITOR_INIT printDebug("Initialising Constant Tables"); #endif - tables.init(); #if !MT32EMU_REDUCE_REVERB_MEMORY for (int i = 0; i < 4; i++) { reverbModels[i]->open(useProp.sampleRate); } #endif - if (useProp.baseDir != NULL) { - char *baseDirCopy = new char[strlen(useProp.baseDir) + 1]; - strcpy(baseDirCopy, useProp.baseDir); - myProp.baseDir = baseDirCopy; - } // This is to help detect bugs memset(&mt32ram, '?', sizeof(mt32ram)); @@ -441,12 +440,10 @@ bool Synth::open(SynthProperties &useProp) { #if MT32EMU_MONITOR_INIT printDebug("Loading Control ROM"); #endif - if (loadControlROM("CM32L_CONTROL.ROM") != LoadResult_OK) { - if (loadControlROM("MT32_CONTROL.ROM") != LoadResult_OK) { - printDebug("Init Error - Missing or invalid MT32_CONTROL.ROM"); - //report(ReportType_errorControlROM, &errno); - return false; - } + if (!loadControlROM(controlROMImage)) { + printDebug("Init Error - Missing or invalid Control ROM image"); + reportHandler->onErrorControlROM(); + return false; } initMemoryRegions(); @@ -455,17 +452,15 @@ bool Synth::open(SynthProperties &useProp) { // 1MB PCM ROM for CM-32L, LAPC-I, CM-64, CM-500 // Note that the size below is given in samples (16-bit), not bytes pcmROMSize = controlROMMap->pcmCount == 256 ? 512 * 1024 : 256 * 1024; - pcmROMData = new float[pcmROMSize]; + pcmROMData = new Bit16s[pcmROMSize]; #if MT32EMU_MONITOR_INIT printDebug("Loading PCM ROM"); #endif - if (loadPCMROM("CM32L_PCM.ROM") != LoadResult_OK) { - if (loadPCMROM("MT32_PCM.ROM") != LoadResult_OK) { - printDebug("Init Error - Missing MT32_PCM.ROM"); - //report(ReportType_errorPCMROM, &errno); - return false; - } + if (!loadPCMROM(pcmROMImage)) { + printDebug("Init Error - Missing PCM ROM image"); + reportHandler->onErrorPCMROM(); + return false; } #if MT32EMU_MONITOR_INIT @@ -594,9 +589,6 @@ void Synth::close() { parts[i] = NULL; } - delete[] myProp.baseDir; - myProp.baseDir = NULL; - delete[] pcmWaves; delete[] pcmROMData; @@ -627,6 +619,11 @@ void Synth::playMsg(Bit32u msg) { return; } playMsgOnPart(part, code, note, velocity); + + // This ensures minimum 1-sample delay between sequential MIDI events + // Without this, a sequence of NoteOn and immediately succeeding NoteOff messages is always silent + // Technically, it's also impossible to send events through the MIDI interface faster than about each millisecond + prerender(); } void Synth::playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity) { @@ -1178,7 +1175,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le case MR_System: region->write(0, off, data, len); - report(ReportType_devReconfig, NULL); + reportHandler->onDeviceReconfig(); // FIXME: We haven't properly confirmed any of this behaviour // In particular, we tend to reset things such as reverb even if the write contained // the same parameters as were already set, which may be wrong. @@ -1216,7 +1213,7 @@ void Synth::writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u le #if MT32EMU_MONITOR_SYSEX > 0 printDebug("WRITE-LCD: %s", buf); #endif - report(ReportType_lcdMessage, buf); + reportHandler->showLCDMessage(buf); break; case MR_Reset: reset(); @@ -1244,9 +1241,9 @@ void Synth::refreshSystemReverbParameters() { #endif return; } - report(ReportType_newReverbMode, &mt32ram.system.reverbMode); - report(ReportType_newReverbTime, &mt32ram.system.reverbTime); - report(ReportType_newReverbLevel, &mt32ram.system.reverbLevel); + reportHandler->onNewReverbMode(mt32ram.system.reverbMode); + reportHandler->onNewReverbTime(mt32ram.system.reverbTime); + reportHandler->onNewReverbLevel(mt32ram.system.reverbLevel); ReverbModel *newReverbModel = reverbModels[mt32ram.system.reverbMode]; #if MT32EMU_REDUCE_REVERB_MEMORY @@ -1254,7 +1251,7 @@ void Synth::refreshSystemReverbParameters() { if (reverbModel != NULL) { reverbModel->close(); } - newReverbModel->open(myProp.sampleRate); + newReverbModel->open(); } #endif reverbModel = newReverbModel; @@ -1309,7 +1306,7 @@ void Synth::reset() { #if MT32EMU_MONITOR_SYSEX > 0 printDebug("RESET"); #endif - report(ReportType_devReset, NULL); + reportHandler->onDeviceReset(); partialManager->deactivateAll(); mt32ram = mt32default; for (int i = 0; i < 9; i++) { diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index ccabce7282..56e88e6156 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -26,6 +26,7 @@ class TableInitialiser; class Partial; class PartialManager; class Part; +class ROMImage; /** * Methods for emulating the connection between the LA32 and the DAC, which involves @@ -57,71 +58,6 @@ enum DACInputMode { DACInputMode_GENERATION2 }; -enum ReportType { - // Errors - ReportType_errorControlROM = 1, - ReportType_errorPCMROM, - ReportType_errorSampleRate, - - // Progress - ReportType_progressInit, - - // HW spec - ReportType_availableSSE, - ReportType_available3DNow, - ReportType_usingSSE, - ReportType_using3DNow, - - // General info - ReportType_lcdMessage, - ReportType_devReset, - ReportType_devReconfig, - ReportType_newReverbMode, - ReportType_newReverbTime, - ReportType_newReverbLevel -}; - -enum LoadResult { - LoadResult_OK, - LoadResult_NotFound, - LoadResult_Unreadable, - LoadResult_Invalid -}; - -struct SynthProperties { - // Sample rate to use in mixing - unsigned int sampleRate; - - // Deprecated - ignored. Use Synth::setReverbEnabled() instead. - bool useReverb; - // Deprecated - ignored. Use Synth::setReverbOverridden() instead. - bool useDefaultReverb; - // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. - unsigned char reverbType; - // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. - unsigned char reverbTime; - // Deprecated - ignored. Use Synth::playSysex*() to configure reverb instead. - unsigned char reverbLevel; - // The name of the directory in which the ROM and data files are stored (with trailing slash/backslash) - // Not used if "openFile" is set. May be NULL in any case. - const char *baseDir; - // This is used as the first argument to all callbacks - void *userData; - // Callback for reporting various errors and information. May be NULL - int (*report)(void *userData, ReportType type, const void *reportData); - // Callback for debug messages, in vprintf() format - void (*printDebug)(void *userData, const char *fmt, va_list list); - // Callback for providing an implementation of File, opened and ready for use - // May be NULL, in which case a default implementation will be used. - Common::File *(*openFile)(void *userData, const char *filename); - // Callback for closing a File. May be NULL, in which case the File will automatically be close()d/deleted. - void (*closeFile)(void *userData, Common::File *file); -}; - -// This is the specification of the Callback routine used when calling the RecalcWaveforms -// function -typedef void (*recalcStatusCallback)(int percDone); - typedef void (*FloatToBit16sFunc)(Bit16s *target, const float *source, Bit32u len, float outputGain); const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; @@ -179,6 +115,13 @@ enum MemoryRegionType { MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset }; +enum ReverbMode { + REVERB_MODE_ROOM, + REVERB_MODE_HALL, + REVERB_MODE_PLATE, + REVERB_MODE_TAP_DELAY +}; + class MemoryRegion { private: Synth *synth; @@ -278,7 +221,7 @@ class ReverbModel { public: virtual ~ReverbModel() {} // After construction or a close(), open() will be called at least once before any other call (with the exception of close()). - virtual void open(unsigned int sampleRate) = 0; + virtual void open() = 0; // May be called multiple times without an open() in between. virtual void close() = 0; virtual void setParameters(Bit8u time, Bit8u level) = 0; @@ -286,6 +229,32 @@ public: virtual bool isActive() const = 0; }; +class ReportHandler { +friend class Synth; + +public: + virtual ~ReportHandler() {} + +protected: + + // Callback for debug messages, in vprintf() format + virtual void printDebug(const char *fmt, va_list list); + + // Callbacks for reporting various errors and information + virtual void onErrorControlROM() {} + virtual void onErrorPCMROM() {} + virtual void showLCDMessage(const char *message); + virtual void onDeviceReset() {} + virtual void onDeviceReconfig() {} + virtual void onNewReverbMode(Bit8u /* mode */) {} + virtual void onNewReverbTime(Bit8u /* time */) {} + virtual void onNewReverbLevel(Bit8u /* level */) {} + virtual void onPartStateChanged(int /* partNum */, bool /* isActive */) {} + virtual void onPolyStateChanged(int /* partNum */) {} + virtual void onPartialStateChanged(int /* partialNum */, int /* oldPartialPhase */, int /* newPartialPhase */) {} + virtual void onProgramChanged(int /* partNum */, char * /* patchName */) {} +}; + class Synth { friend class Part; friend class RhythmPart; @@ -314,14 +283,13 @@ private: const ControlROMMap *controlROMMap; Bit8u controlROMData[CONTROL_ROM_SIZE]; - float *pcmROMData; - int pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM + Bit16s *pcmROMData; + size_t pcmROMSize; // This is in 16-bit samples, therefore half the number of bytes in the ROM Bit8s chantable[32]; Bit32u renderedSampleCount; - Tables tables; MemParams mt32ram, mt32default; @@ -337,6 +305,9 @@ private: bool isOpen; + bool isDefaultReportHandler; + ReportHandler *reportHandler; + PartialManager *partialManager; Part *parts[9]; @@ -369,8 +340,6 @@ private: int prerenderReadIx; int prerenderWriteIx; - SynthProperties myProp; - bool prerender(); void copyPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u pos, Bit32u len); void checkPrerender(Bit16s *nonReverbLeft, Bit16s *nonReverbRight, Bit16s *reverbDryLeft, Bit16s *reverbDryRight, Bit16s *reverbWetLeft, Bit16s *reverbWetRight, Bit32u &pos, Bit32u &len); @@ -384,8 +353,8 @@ private: void writeMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, const Bit8u *data); void readMemoryRegion(const MemoryRegion *region, Bit32u addr, Bit32u len, Bit8u *data); - LoadResult loadControlROM(const char *filename); - LoadResult loadPCMROM(const char *filename); + bool loadControlROM(const ROMImage &controlROMImage); + bool loadPCMROM(const ROMImage &pcmROMImage); bool initPCMList(Bit16u mapAddress, Bit16u count); bool initTimbres(Bit16u mapAddress, Bit16u offset, int timbreCount, int startTimbre, bool compressed); @@ -399,24 +368,25 @@ private: void refreshSystem(); void reset(); - unsigned int getSampleRate() const; - void printPartialUsage(unsigned long sampleOffset = 0); -protected: - int report(ReportType type, const void *reportData); - Common::File *openFile(const char *filename); - void closeFile(Common::File *file); + + void partStateChanged(int partNum, bool isPartActive); + void polyStateChanged(int partNum); + void partialStateChanged(const Partial * const partial, int oldPartialPhase, int newPartialPhase); + void newTimbreSet(int partNum, char patchName[]); void printDebug(const char *fmt, ...); public: static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); - Synth(); + // Optionally sets callbacks for reporting various errors, information and debug messages + Synth(ReportHandler *useReportHandler = NULL); ~Synth(); // Used to initialise the MT-32. Must be called before any other function. // Returns true if initialization was sucessful, otherwise returns false. - bool open(SynthProperties &useProp); + // controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth. + bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage); // Closes the MT-32 and deallocates any memory used by the synthesizer void close(void); diff --git a/audio/softsynth/mt32/TVA.cpp b/audio/softsynth/mt32/TVA.cpp index f3e3f7bbc7..65e5256048 100644 --- a/audio/softsynth/mt32/TVA.cpp +++ b/audio/softsynth/mt32/TVA.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -30,10 +30,13 @@ namespace MT32Emu { static Bit8u biasLevelToAmpSubtractionCoeff[13] = {255, 187, 137, 100, 74, 54, 40, 29, 21, 15, 10, 5, 0}; TVA::TVA(const Partial *usePartial, LA32Ramp *useAmpRamp) : - partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system) { + partial(usePartial), ampRamp(useAmpRamp), system_(&usePartial->getSynth()->mt32ram.system), phase(TVA_PHASE_DEAD) { } void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) { + if (newPhase != phase) { + partial->getSynth()->partialStateChanged(partial, phase, newPhase); + } target = newTarget; phase = newPhase; ampRamp->startRamp(newTarget, newIncrement); @@ -43,6 +46,9 @@ void TVA::startRamp(Bit8u newTarget, Bit8u newIncrement, int newPhase) { } void TVA::end(int newPhase) { + if (newPhase != phase) { + partial->getSynth()->partialStateChanged(partial, phase, newPhase); + } phase = newPhase; playing = false; #if MT32EMU_MONITOR_TVA >= 1 @@ -154,7 +160,7 @@ void TVA::reset(const Part *newPart, const TimbreParam::PartialParam *newPartial playing = true; - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); int key = partial->getPoly()->getKey(); int velocity = partial->getPoly()->getVelocity(); @@ -215,7 +221,7 @@ void TVA::recalcSustain() { return; } // We're sustaining. Recalculate all the values - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); int newTarget = calcBasicAmp(tables, partial, system_, partialParam, patchTemp, rhythmTemp, biasAmpSubtraction, veloAmpSubtraction, part->getExpression()); newTarget += partialParam->tva.envLevel[3]; // Since we're in TVA_PHASE_SUSTAIN at this point, we know that target has been reached and an interrupt fired, so we can rely on it being the current amp. @@ -241,7 +247,7 @@ int TVA::getPhase() const { } void TVA::nextPhase() { - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); if (phase >= TVA_PHASE_DEAD || !playing) { partial->getSynth()->printDebug("TVA::nextPhase(): Shouldn't have got here with phase %d, playing=%s", phase, playing ? "true" : "false"); @@ -274,7 +280,7 @@ void TVA::nextPhase() { } int newTarget; - int newIncrement = 0; + int newIncrement = 0; // Initialised to please compilers int envPointIndex = phase; if (!allLevelsZeroFromNowOn) { diff --git a/audio/softsynth/mt32/TVA.h b/audio/softsynth/mt32/TVA.h index a104fe4c1f..e6e5cc4bc7 100644 --- a/audio/softsynth/mt32/TVA.h +++ b/audio/softsynth/mt32/TVA.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/audio/softsynth/mt32/TVF.cpp b/audio/softsynth/mt32/TVF.cpp index 80b592ea67..49a0d85c9e 100644 --- a/audio/softsynth/mt32/TVF.cpp +++ b/audio/softsynth/mt32/TVF.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -117,7 +117,7 @@ void TVF::reset(const TimbreParam::PartialParam *newPartialParam, unsigned int b unsigned int key = partial->getPoly()->getKey(); unsigned int velocity = partial->getPoly()->getVelocity(); - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); baseCutoff = calcBaseCutoff(newPartialParam, basePitch, key); #if MT32EMU_MONITOR_TVF >= 1 @@ -179,7 +179,7 @@ void TVF::startDecay() { } void TVF::nextPhase() { - Tables *tables = &partial->getSynth()->tables; + const Tables *tables = &Tables::getInstance(); int newPhase = phase + 1; switch (newPhase) { diff --git a/audio/softsynth/mt32/TVF.h b/audio/softsynth/mt32/TVF.h index 490d8de504..22d81da7b0 100644 --- a/audio/softsynth/mt32/TVF.h +++ b/audio/softsynth/mt32/TVF.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp index 0b339e8d71..c3e64c18d0 100644 --- a/audio/softsynth/mt32/TVP.cpp +++ b/audio/softsynth/mt32/TVP.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -47,12 +47,11 @@ static Bit16u keyToPitchTable[] = { TVP::TVP(const Partial *usePartial) : partial(usePartial), system_(&usePartial->getSynth()->mt32ram.system) { - unsigned int sampleRate = usePartial->getSynth()->myProp.sampleRate; // We want to do processing 4000 times per second. FIXME: This is pretty arbitrary. - maxCounter = sampleRate / 4000; + maxCounter = SAMPLE_RATE / 4000; // The timer runs at 500kHz. We only need to bother updating it every maxCounter samples, before we do processing. // This is how much to increment it by every maxCounter samples. - processTimerIncrement = 500000 * maxCounter / sampleRate; + processTimerIncrement = 500000 * maxCounter / SAMPLE_RATE; } static Bit16s keyToPitch(unsigned int key) { @@ -171,9 +170,14 @@ void TVP::updatePitch() { if (newPitch < 0) { newPitch = 0; } + +// Note: Temporary #ifdef until we have proper "quirk" configuration +// This is about right emulation of MT-32 GEN0 quirk exploited in Colonel's Bequest timbre "Lightning" +#ifndef MT32EMU_QUIRK_PITCH_ENVELOPE_OVERFLOW_MT32 if (newPitch > 59392) { newPitch = 59392; } +#endif pitch = (Bit16u)newPitch; // FIXME: We're doing this here because that's what the CM-32L does - we should probably move this somewhere more appropriate in future. diff --git a/audio/softsynth/mt32/TVP.h b/audio/softsynth/mt32/TVP.h index f6f62f8d39..cd5fb4cdc7 100644 --- a/audio/softsynth/mt32/TVP.h +++ b/audio/softsynth/mt32/TVP.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/audio/softsynth/mt32/Tables.cpp b/audio/softsynth/mt32/Tables.cpp index c9bd40b7a4..743820b1f8 100644 --- a/audio/softsynth/mt32/Tables.cpp +++ b/audio/softsynth/mt32/Tables.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -22,18 +22,14 @@ #include "mt32emu.h" #include "mmath.h" -using namespace MT32Emu; +namespace MT32Emu { -Tables::Tables() { - initialised = false; +const Tables &Tables::getInstance() { + static const Tables instance; + return instance; } -void Tables::init() { - if (initialised) { - return; - } - initialised = true; - +Tables::Tables() { int lf; for (lf = 0; lf <= 100; lf++) { // CONFIRMED:KG: This matches a ROM table found by Mok @@ -76,44 +72,25 @@ void Tables::init() { //synth->printDebug("%d: %d", i, pulseWidth100To255[i]); } - // Ratio of negative segment to wave length - for (int i = 0; i < 128; i++) { - // Formula determined from sample analysis. - float pt = 0.5f / 127.0f * i; - pulseLenFactor[i] = (1.241857812f - pt) * pt; // seems to be 2 ^ (5 / 16) = 1.241857812f - } - - for (int i = 0; i < 65536; i++) { - // Aka (slightly slower): EXP2F(pitchVal / 4096.0f - 16.0f) * 32000.0f - pitchToFreq[i] = EXP2F(i / 4096.0f - 1.034215715f); - } - - // found from sample analysis - for (int i = 0; i < 1024; i++) { - cutoffToCosineLen[i] = EXP2F(i / -128.0f); + // The LA32 chip contains an exponent table inside. The table contains 12-bit integer values. + // The actual table size is 512 rows. The 9 higher bits of the fractional part of the argument are used as a lookup address. + // To improve the precision of computations, the lower bits are supposed to be used for interpolation as the LA32 chip also + // contains another 512-row table with inverted differences between the main table values. + for (int i = 0; i < 512; i++) { + exp9[i] = Bit16u(8191.5f - EXP2F(13.0f + ~i / 512.0f)); } - // found from sample analysis - for (int i = 0; i < 1024; i++) { - cutoffToFilterAmp[i] = EXP2F(-0.125f * (128.0f - i / 8.0f)); + // There is a logarithmic sine table inside the LA32 chip. The table contains 13-bit integer values. + for (int i = 1; i < 512; i++) { + logsin9[i] = Bit16u(0.5f - LOG2F(sin((i + 0.5f) / 1024.0f * FLOAT_PI)) * 1024.0f); } - // found from sample analysis - for (int i = 0; i < 32; i++) { - resAmpMax[i] = EXP2F(1.0f - (32 - i) / 4.0f); - } + // The very first value is clamped to the maximum possible 13-bit integer + logsin9[0] = 8191; // found from sample analysis - resAmpFadeFactor[7] = 1.0f / 8.0f; - resAmpFadeFactor[6] = 2.0f / 8.0f; - resAmpFadeFactor[5] = 3.0f / 8.0f; - resAmpFadeFactor[4] = 5.0f / 8.0f; - resAmpFadeFactor[3] = 8.0f / 8.0f; - resAmpFadeFactor[2] = 12.0f / 8.0f; - resAmpFadeFactor[1] = 16.0f / 8.0f; - resAmpFadeFactor[0] = 31.0f / 8.0f; + static const Bit8u resAmpDecayFactorTable[] = {31, 16, 12, 8, 5, 3, 2, 1}; + resAmpDecayFactor = resAmpDecayFactorTable; +} - for (int i = 0; i < 5120; i++) { - sinf10[i] = sin(FLOAT_PI * i / 2048.0f); - } } diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h index a2b5ff5d56..8b4580df0e 100644 --- a/audio/softsynth/mt32/Tables.h +++ b/audio/softsynth/mt32/Tables.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -20,14 +20,23 @@ namespace MT32Emu { +// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent. +// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator. +// The output from the synth is supposed to be resampled to convert the sample rate. +const unsigned int SAMPLE_RATE = 32000; + const int MIDDLEC = 60; class Synth; class Tables { - bool initialised; +private: + Tables(); + Tables(Tables &); public: + static const Tables &getInstance(); + // Constant LUTs // CONFIRMED: This is used to convert several parameters to amp-modifying values in the TVA envelope: @@ -47,16 +56,10 @@ public: // CONFIRMED: Bit8u pulseWidth100To255[101]; - float pulseLenFactor[128]; - float pitchToFreq[65536]; - float cutoffToCosineLen[1024]; - float cutoffToFilterAmp[1024]; - float resAmpMax[32]; - float resAmpFadeFactor[8]; - float sinf10[5120]; + Bit16u exp9[512]; + Bit16u logsin9[512]; - Tables(); - void init(); + const Bit8u *resAmpDecayFactor; }; } diff --git a/audio/softsynth/mt32/mmath.h b/audio/softsynth/mt32/mmath.h index 226d73e27e..ee6a652c36 100644 --- a/audio/softsynth/mt32/mmath.h +++ b/audio/softsynth/mt32/mmath.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index 995e450076..e7afdfd2b4 100644 --- a/audio/softsynth/mt32/module.mk +++ b/audio/softsynth/mt32/module.mk @@ -2,13 +2,17 @@ MODULE := audio/softsynth/mt32 MODULE_OBJS := \ AReverbModel.o \ + BReverbModel.o \ DelayReverb.o \ FreeverbModel.o \ LA32Ramp.o \ + LA32WaveGenerator.o \ + LegacyWaveGenerator.o \ Part.o \ Partial.o \ PartialManager.o \ Poly.o \ + ROMInfo.o \ Synth.o \ TVA.o \ TVF.o \ diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index 091819b95c..971a0886d5 100644 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -1,5 +1,5 @@ /* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher - * Copyright (C) 2011 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * Copyright (C) 2011, 2012, 2013 Dean Beeler, Jerome Fisher, Sergey V. Mikayev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -59,13 +59,6 @@ #define MT32EMU_MONITOR_TVA 0 #define MT32EMU_MONITOR_TVF 0 - -// 0: Use LUTs to speedup WG -// 1: Use precise float math -#define MT32EMU_ACCURATE_WG 0 - -#define MT32EMU_USE_EXTINT 0 - // Configuration // The maximum number of partials playing simultaneously #define MT32EMU_MAX_PARTIALS 32 @@ -77,9 +70,14 @@ // If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path. #define MT32EMU_REDUCE_REVERB_MEMORY 1 -// 0: Use standard Freeverb -// 1: Use AReverb (currently not properly tuned) -#define MT32EMU_USE_AREVERBMODEL 0 +// 0: Use legacy Freeverb +// 1: Use Accurate Reverb model aka AReverb +// 2: Use Bit-perfect Boss Reverb model aka BReverb (for developers, not much practical use) +#define MT32EMU_USE_REVERBMODEL 1 + +// 0: Use refined wave generator based on logarithmic fixed-point computations and LUTs +// 1: Use legacy accurate wave generator based on float computations +#define MT32EMU_ACCURATE_WG 0 namespace MT32Emu { @@ -104,11 +102,14 @@ const unsigned int MAX_PRERENDER_SAMPLES = 1024; #include "Tables.h" #include "Poly.h" #include "LA32Ramp.h" +#include "LA32WaveGenerator.h" +#include "LegacyWaveGenerator.h" #include "TVA.h" #include "TVP.h" #include "TVF.h" #include "Partial.h" #include "Part.h" +#include "ROMInfo.h" #include "Synth.h" #endif diff --git a/audio/softsynth/opl/dosbox.cpp b/audio/softsynth/opl/dosbox.cpp index e039845b8f..a1a736f9de 100644 --- a/audio/softsynth/opl/dosbox.cpp +++ b/audio/softsynth/opl/dosbox.cpp @@ -247,7 +247,7 @@ byte OPL::read(int port) { } void OPL::writeReg(int r, int v) { - byte tempReg = 0; + int tempReg = 0; switch (_type) { case Config::kOpl2: case Config::kDualOpl2: @@ -257,12 +257,27 @@ void OPL::writeReg(int r, int v) { // Backup old setup register tempReg = _reg.normal; - // We need to set the register we want to write to via port 0x388 - write(0x388, r); - // Do the real writing to the register - write(0x389, v); + // We directly allow writing to secondary OPL3 registers by using + // register values >= 0x100. + if (_type == Config::kOpl3 && r >= 0x100) { + // We need to set the register we want to write to via port 0x222, + // since we want to write to the secondary register set. + write(0x222, r); + // Do the real writing to the register + write(0x223, v); + } else { + // We need to set the register we want to write to via port 0x388 + write(0x388, r); + // Do the real writing to the register + write(0x389, v); + } + // Restore the old register - write(0x388, tempReg); + if (_type == Config::kOpl3 && tempReg >= 0x100) { + write(0x222, tempReg & ~0x100); + } else { + write(0x388, tempReg); + } break; }; } diff --git a/audio/softsynth/opl/mame.cpp b/audio/softsynth/opl/mame.cpp index c54f620a10..2db7d421b6 100644 --- a/audio/softsynth/opl/mame.cpp +++ b/audio/softsynth/opl/mame.cpp @@ -223,7 +223,7 @@ static int *ENV_CURVE; /* multiple table */ -#define ML(a) (int)(a * 2) +#define ML(a) (uint)(a * 2) static const uint MUL_TABLE[16]= { /* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */ ML(0.50), ML(1.00), ML(2.00), ML(3.00), ML(4.00), ML(5.00), ML(6.00), ML(7.00), diff --git a/audio/softsynth/sid.cpp b/audio/softsynth/sid.cpp index 1ad822b86a..b6f1c87c4b 100644 --- a/audio/softsynth/sid.cpp +++ b/audio/softsynth/sid.cpp @@ -512,7 +512,7 @@ void Filter::enable_filter(bool enable) { enabled = enable; } -void Filter::reset(){ +void Filter::reset() { fc = 0; res = 0; |