diff --git a/CMakeLists.txt b/CMakeLists.txt index 710543308..98a4ba594 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ option(EDGE_IMF_SUPPORT "Enable support for IMF music format" ON) option(EDGE_MP3_SUPPORT "Enable support for MP3 music format" ON) option(EDGE_MUS_SUPPORT "Enable support for MUS MIDI format" ON) option(EDGE_OGG_SUPPORT "Enable support for OGG music format" ON) +option(EDGE_OPL_SUPPORT "Enable support for MIDI playback via OPL emulation" ON) option(EDGE_SID_SUPPORT "Enable support for C64 SID music" ON) option(EDGE_TRACKER_SUPPORT "Enable support for Tracker music (MOD/XM/S3M/IT)" ON) option(EDGE_XMI_SUPPORT "Enable support for XMI MIDI format" ON) @@ -204,6 +205,10 @@ if (EDGE_OGG_SUPPORT) add_definitions(-DEDGE_OGG_SUPPORT) endif() +if (EDGE_OPL_SUPPORT) + add_definitions(-DEDGE_OPL_SUPPORT) +endif() + if (EDGE_SID_SUPPORT) add_definitions(-DEDGE_SID_SUPPORT) endif() diff --git a/docs/licenses/License Attribution.txt b/docs/licenses/License Attribution.txt index 890d95dd8..c349b9b50 100644 --- a/docs/licenses/License Attribution.txt +++ b/docs/licenses/License Attribution.txt @@ -21,6 +21,10 @@ Freedoom (various assets) - Copyright (c) 2001-2019 Contributors to the Freedoom Mod4Play library - Copyright (c) 2024-2025 dashodanger Copyright (c) 2022 Olav Sørensen +OpalMIDI library - Copyright (c) 2025, Dashodanger + Copyright (c) 2021-2024, Devin Acker + Opal Emulation released into Public Domain by Reality Productions + ZDoom FName implementation - Copyright (c) 2005-2007 Randy Heit =========================================================================================== @@ -123,8 +127,6 @@ Public Domain Fraction.hpp - Bisqwit -Opal library - Reality Productions - Miniaudio library - David Reid prns.h - Marc B. Reynolds diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index b59b46eae..1c1c311ab 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -19,8 +19,8 @@ if (EDGE_TRACKER_SUPPORT) add_subdirectory(m4p) endif() add_subdirectory(miniz) -if (EDGE_IMF_SUPPORT) - add_subdirectory(opal) +if (EDGE_IMF_SUPPORT OR EDGE_OPL_SUPPORT) + add_subdirectory(opalmidi) endif() add_subdirectory(pl_mpeg) add_subdirectory(prns) diff --git a/libraries/opal/CMakeLists.txt b/libraries/opal/CMakeLists.txt deleted file mode 100644 index e98a4ccaa..000000000 --- a/libraries/opal/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -########################################## -# opal -########################################## - -add_library( - opal - opal.cpp -) - -target_include_directories(opal PUBLIC ./) \ No newline at end of file diff --git a/libraries/opal/LICENSE.txt b/libraries/opal/LICENSE.txt deleted file mode 100644 index 560ba326f..000000000 --- a/libraries/opal/LICENSE.txt +++ /dev/null @@ -1,18 +0,0 @@ -A quote from a letter sent by Willy Reeve to Vitaly Novichkov -as a reply from a letter sent by Reality's (https://www.3eality.com/) -contact form ----------------------------------------------------------------- -All source code supplied with RAD is in the public domain. ----------------------------------------------------------------- - -================================================================ -The complete letter: -================================================================ -Hi, - -All source code supplied with RAD is in the public domain. -We'll clarify that in the documentation for the next release. - -Later... -SHAYDE -================================================================ diff --git a/libraries/opalmidi/CMakeLists.txt b/libraries/opalmidi/CMakeLists.txt new file mode 100644 index 000000000..2050645fc --- /dev/null +++ b/libraries/opalmidi/CMakeLists.txt @@ -0,0 +1,17 @@ +########################################## +# opalmidi +########################################## + +set(OPALMIDI_SOURCE_FILES opal.cpp) + +# If only IMF is supported, we don't need the MIDI components +if(EDGE_OPL_SUPPORT) + set(OPALMIDI_SOURCE_FILES ${OPALMIDI_SOURCE_FILES} opalmidi.cpp patches.cpp) +endif() + +add_library( + opalmidi + ${OPALMIDI_SOURCE_FILES} +) + +target_include_directories(opalmidi PUBLIC ./) \ No newline at end of file diff --git a/libraries/opalmidi/LICENSE.txt b/libraries/opalmidi/LICENSE.txt new file mode 100644 index 000000000..c125456fc --- /dev/null +++ b/libraries/opalmidi/LICENSE.txt @@ -0,0 +1,53 @@ +BSD 3-Clause License + +Copyright (c) 2025, Dashodanger +Copyright (c) 2021-2024, Devin Acker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Opal emulation is derived from the RAD Tracker v2 source release, and has +been put into the public domain by Reality Productions (see below) + +A quote from a letter sent by Willy Reeve to Vitaly Novichkov +as a reply from a letter sent by Reality's (https://www.3eality.com/) +contact form +---------------------------------------------------------------- +All source code supplied with RAD is in the public domain. +---------------------------------------------------------------- + +================================================================ +The complete letter: +================================================================ +Hi, + +All source code supplied with RAD is in the public domain. +We'll clarify that in the documentation for the next release. + +Later... +SHAYDE +================================================================ diff --git a/libraries/opal/opal.cpp b/libraries/opalmidi/opal.cpp similarity index 96% rename from libraries/opal/opal.cpp rename to libraries/opalmidi/opal.cpp index 807b3eb3c..e05d5c1ca 100644 --- a/libraries/opal/opal.cpp +++ b/libraries/opalmidi/opal.cpp @@ -423,40 +423,6 @@ void Opal::Sample(int16_t *left, int16_t *right) { SampleAccum += OPL3SampleRate; } -//================================================================================================== -// Generate sample. Every time you call this you will get two floating-point samples (one for each -// stereo channel) which will sound correct when played back at the sample rate given when the -// class was constructed. -//================================================================================================== -void Opal::SampleFloat(float *left, float *right) { - - // If the destination sample rate is higher than the OPL3 sample rate, we need to skip ahead - while (SampleAccum >= SampleRate) { - - LastOutput[0] = CurrOutput[0]; - LastOutput[1] = CurrOutput[1]; - - Output(CurrOutput[0], CurrOutput[1]); - - SampleAccum -= SampleRate; - } - - // Mix with the partial accumulation - int32_t omblend = SampleRate - SampleAccum; - -#if defined _MSC_VER || (defined __SIZEOF_FLOAT__ && __SIZEOF_FLOAT__ == 4) - *(uint32_t *)left=0x43818000^((uint16_t)((LastOutput[0] * omblend + CurrOutput[0] * SampleAccum) / SampleRate)); - *left -= 259.0f; - *(uint32_t *)right=0x43818000^((uint16_t)((LastOutput[1] * omblend + CurrOutput[1] * SampleAccum) / SampleRate)); - *right -= 259.0f; -#else - *left = (float)((LastOutput[0] * omblend + CurrOutput[0] * SampleAccum) / SampleRate) * 0.000030517578125f; - *right = (float)((LastOutput[1] * omblend + CurrOutput[1] * SampleAccum) / SampleRate) * 0.000030517578125f; -#endif - - SampleAccum += OPL3SampleRate; -} - //================================================================================================== // Produce final output from the chip. This is at the OPL3 sample-rate. //================================================================================================== diff --git a/libraries/opal/opal.h b/libraries/opalmidi/opal.h similarity index 99% rename from libraries/opal/opal.h rename to libraries/opalmidi/opal.h index e461da163..7f9aba823 100644 --- a/libraries/opal/opal.h +++ b/libraries/opalmidi/opal.h @@ -203,7 +203,6 @@ class Opal void SetSampleRate(int sample_rate); void Port(uint16_t reg_num, uint8_t val); void Sample(int16_t *left, int16_t *right); - void SampleFloat(float *left, float *right); protected: void Init(int sample_rate); diff --git a/libraries/opalmidi/opalmidi.cpp b/libraries/opalmidi/opalmidi.cpp new file mode 100644 index 000000000..a86ccb428 --- /dev/null +++ b/libraries/opalmidi/opalmidi.cpp @@ -0,0 +1,681 @@ +#include "opalmidi.h" + +#include +#include + +static const unsigned voice_num[18] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, + 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108}; + +static const unsigned oper_num[18] = {0x0, 0x1, 0x2, 0x8, 0x9, 0xA, 0x10, 0x11, 0x12, + 0x100, 0x101, 0x102, 0x108, 0x109, 0x10A, 0x110, 0x111, 0x112}; + +// ---------------------------------------------------------------------------- +OPLPlayer::OPLPlayer(int frequency) +{ + m_voices.resize(18); + + rate = frequency; + m_output.first = 0; + m_output.second = 0; + + reset(); +} + +// ---------------------------------------------------------------------------- +OPLPlayer::~OPLPlayer() +{ + delete m_opl3; +} + +// ---------------------------------------------------------------------------- +bool OPLPlayer::loadPatches(const uint8_t *data, size_t size) +{ + return OPLPatch::load(m_patches, data, size); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::loadDefaultPatches() +{ + OPLPatch::loadDefault(m_patches); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::generate(int16_t *data, unsigned numSamples) +{ + unsigned int samp = 0; + + while (samp < numSamples * 2) + { + updateMIDI(); + + data[samp] = m_output.first; + data[samp + 1] = m_output.second; + + samp += 2; + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::updateMIDI() +{ + for (auto &voice : m_voices) + { + if (voice.duration < UINT_MAX) + voice.duration++; + voice.justChanged = false; + } + + if (m_sampleFIFO.empty()) + { + m_opl3->Sample(&m_output.first, &m_output.second); + } + else + { + m_output = m_sampleFIFO.front(); + m_sampleFIFO.pop(); + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::reset() +{ + delete m_opl3; + m_opl3 = new Opal(rate); + // enable OPL3 stuff + write(REG_NEW, 1); + + // reset MIDI channel and OPL voice status + m_midiType = GeneralMIDI; + for (int i = 0; i < 16; i++) + { + m_channels[i] = MIDIChannel(); + m_channels[i].num = i; + } + m_channels[9].percussion = true; + + for (size_t i = 0; i < m_voices.size(); i++) + { + m_voices[i] = OPLVoice(); + m_voices[i].chip = i / 18; + m_voices[i].num = voice_num[i % 18]; + m_voices[i].op = oper_num[i % 18]; + + // configure 4op voices + + switch (i % 9) + { + case 0: + case 1: + case 2: + m_voices[i].fourOpPrimary = true; + m_voices[i].fourOpOther = &m_voices[i + 3]; + break; + case 3: + case 4: + case 5: + m_voices[i].fourOpPrimary = false; + m_voices[i].fourOpOther = &m_voices[i - 3]; + break; + default: + m_voices[i].fourOpPrimary = false; + m_voices[i].fourOpOther = nullptr; + break; + } + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::runSamples(unsigned count) +{ + // clock one sample after changing the 4op state before writing other registers + // so that opal can reassign operators to channels, etc + // add some delay between register writes where needed + // (i.e. when forcing a voice off, changing 4op flags, etc.) + while (count--) + { + std::pair output; + m_opl3->Sample(&output.first, &output.second); + m_sampleFIFO.push(output); + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::write(uint16_t addr, uint8_t data) +{ + m_opl3->Port(addr, data); +} + +// ---------------------------------------------------------------------------- +OPLVoice *OPLPlayer::findVoice(uint8_t channel, const OPLPatch *patch, uint8_t note) +{ + OPLVoice *found = nullptr; + uint32_t duration = 0; + + // try to find the "oldest" voice, prioritizing released notes + // (or voices that haven't ever been used yet) + for (auto &voice : m_voices) + { + if (useFourOp(patch) && !voice.fourOpPrimary) + continue; + + if (!voice.channel) + return &voice; + + if (!voice.on && !voice.justChanged) + { + if (voice.channel->num == channel && voice.note == note && voice.duration < UINT_MAX) + { + // found an old voice that was using the same note and patch + // don't immediately use it, but make it a high priority candidate for later + // (to help avoid pop/click artifacts when retriggering a recently off note) + silenceVoice(voice); + if (useFourOp(voice.patch) && voice.fourOpOther) + silenceVoice(*voice.fourOpOther); + } + else if (voice.duration > duration) + { + found = &voice; + duration = voice.duration; + } + } + } + + if (found) + return found; + // if we didn't find one yet, just try to find an old one + // using the same patch, even if it should still be playing. + + for (auto &voice : m_voices) + { + if (useFourOp(patch) && !voice.fourOpPrimary) + continue; + + if (voice.patch == patch && voice.duration > duration) + { + found = &voice; + duration = voice.duration; + } + } + + if (found) + return found; + // last resort - just find any old voice at all + + for (auto &voice : m_voices) + { + if (useFourOp(patch) && !voice.fourOpPrimary) + continue; + // don't let a 2op instrument steal an active voice from a 4op one + if (!useFourOp(patch) && voice.on && useFourOp(voice.patch)) + continue; + + if (voice.duration > duration) + { + found = &voice; + duration = voice.duration; + } + } + + return found; +} + +// ---------------------------------------------------------------------------- +OPLVoice *OPLPlayer::findVoice(uint8_t channel, uint8_t note, bool justChanged) +{ + channel &= 15; + for (auto &voice : m_voices) + { + if (voice.on && voice.justChanged == justChanged && voice.channel == &m_channels[channel] && voice.note == note) + { + return &voice; + } + } + + return nullptr; +} + +// ---------------------------------------------------------------------------- +const OPLPatch *OPLPlayer::findPatch(uint8_t channel, uint8_t note) const +{ + uint16_t key; + const MIDIChannel &ch = m_channels[channel & 15]; + + if (ch.percussion) + key = 0x80 | note | (ch.patchNum << 8); + else + key = ch.patchNum | (ch.bank << 8); + + // if this patch+bank combo doesn't exist, default to bank 0 + if (!m_patches.count(key)) + key &= 0x00ff; + // if patch still doesn't exist in bank 0, use patch 0 (or drum note 0) + if (!m_patches.count(key)) + key &= 0x0080; + // if that somehow still doesn't exist, forget it + if (!m_patches.count(key)) + return nullptr; + + return &m_patches.at(key); +} + +// ---------------------------------------------------------------------------- +bool OPLPlayer::useFourOp(const OPLPatch *patch) const +{ + return patch->fourOp; +} + +// ---------------------------------------------------------------------------- +std::pair OPLPlayer::activeCarriers(const OPLVoice &voice) const +{ + bool scale[2] = {0}; + const auto patchVoice = voice.patchVoice; + + if (!patchVoice) + { + scale[0] = scale[1] = false; + } + else if (!useFourOp(voice.patch)) + { + // 2op FM (0): scale op 2 only + // 2op AM (1): scale op 1 and 2 + scale[0] = (patchVoice->conn & 1); + scale[1] = true; + } + else if (voice.fourOpPrimary) + { + // 4op FM+FM (0, 0): don't scale op 1 or 2 + // 4op AM+FM (1, 0): scale op 1 only + // 4op FM+AM (0, 1): scale op 2 only + // 4op AM+AM (1, 1): scale op 1 only + scale[0] = (voice.patch->voice[0].conn & 1); + scale[1] = (voice.patch->voice[1].conn & 1) && !scale[0]; + } + else + { + // 4op FM+FM (0, 0): scale op 4 only + // 4op AM+FM (1, 0): scale op 4 only + // 4op FM+AM (0, 1): scale op 4 only + // 4op AM+AM (1, 1): scale op 3 and 4 + scale[0] = (voice.patch->voice[0].conn & 1) && (voice.patch->voice[1].conn & 1); + scale[1] = true; + } + + return std::make_pair(scale[0], scale[1]); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::updateChannelVoices(uint8_t channel, void (OPLPlayer::*func)(OPLVoice &)) +{ + for (auto &voice : m_voices) + { + if (voice.channel == &m_channels[channel & 15]) + (this->*func)(voice); + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::updatePatch(OPLVoice &voice, const OPLPatch *newPatch, uint8_t numVoice) +{ + // assign the MIDI channel's current patch (or the current drum patch) to this voice + + const PatchVoice &patchVoice = newPatch->voice[numVoice]; + + if (voice.patchVoice != &patchVoice) + { + bool oldFourOp = voice.patch ? useFourOp(voice.patch) : false; + + voice.patch = newPatch; + voice.patchVoice = &patchVoice; + + // update enable status for 4op channels on this chip + if (useFourOp(newPatch) != oldFourOp) + { + // if going from part of a 4op patch to a 2op one, kill the other one + OPLVoice *other = voice.fourOpOther; + if (other && other->patch && useFourOp(other->patch) && !useFourOp(newPatch)) + { + silenceVoice(*other); + } + + uint8_t enable = 0x00; + uint8_t bit = 0x01; + for (int i = voice.chip * 18; i < voice.chip * 18 + 18; i++) + { + if (m_voices[i].fourOpPrimary) + { + if (m_voices[i].patch && useFourOp(m_voices[i].patch)) + enable |= bit; + bit <<= 1; + } + } + + write(REG_4OP, enable); + } + + // kill an existing voice, then send the chip far enough forward in time to let the envelope die off + // (ROTT: fixes nasty reverse cymbal noises in spray.mid + // without disrupting note timing too much for the staccato drums in fanfare2.mid) + silenceVoice(voice); + runSamples(48); + + // 0x20: vibrato, sustain, multiplier + write(REG_OP_MODE + voice.op, patchVoice.op_mode[0]); + write(REG_OP_MODE + voice.op + 3, patchVoice.op_mode[1]); + // 0x60: attack/decay + write(REG_OP_AD + voice.op, patchVoice.op_ad[0]); + write(REG_OP_AD + voice.op + 3, patchVoice.op_ad[1]); + // 0xe0: waveform + write(REG_OP_WAVEFORM + voice.op, patchVoice.op_wave[0]); + write(REG_OP_WAVEFORM + voice.op + 3, patchVoice.op_wave[1]); + } + + // 0x80: sustain/release + // update even for the same patch in case silenceVoice was called from somewhere else on this voice + write(REG_OP_SR + voice.op, patchVoice.op_sr[0]); + write(REG_OP_SR + voice.op + 3, patchVoice.op_sr[1]); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::updateVolume(OPLVoice &voice) +{ + // lookup table shamelessly stolen from Nuke.YKT + static const uint8_t opl_volume_map[32] = {80, 63, 40, 36, 32, 28, 23, 21, 19, 17, 15, 14, 13, 12, 11, 10, + 9, 8, 7, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0}; + + if (!voice.patch || !voice.channel) + return; + + uint8_t atten = opl_volume_map[(voice.velocity * voice.channel->volume) >> 10]; + uint8_t level; + + const auto patchVoice = voice.patchVoice; + const auto scale = activeCarriers(voice); + + // 0x40: key scale / volume + if (scale.first) + level = std::min(0x3f, patchVoice->op_level[0] + atten); + else + level = patchVoice->op_level[0]; + write(REG_OP_LEVEL + voice.op, level | patchVoice->op_ksr[0]); + + if (scale.second) + level = std::min(0x3f, patchVoice->op_level[1] + atten); + else + level = patchVoice->op_level[1]; + write(REG_OP_LEVEL + voice.op + 3, level | patchVoice->op_ksr[1]); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::updatePanning(OPLVoice &voice) +{ + if (!voice.patch || !voice.channel) + return; + + // 0xc0: output/feedback/mode + uint8_t pan = 0x30; + if (voice.channel->pan < 32) + pan = 0x10; + else if (voice.channel->pan >= 96) + pan = 0x20; + + write(REG_VOICE_CNT + voice.num, voice.patchVoice->conn | pan); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::updateFrequency(OPLVoice &voice) +{ + static const uint16_t noteFreq[12] = {// calculated from A440 + 345, 365, 387, 410, 435, 460, 488, 517, 547, 580, 615, 651}; + + if (!voice.patch || !voice.channel) + return; + if (useFourOp(voice.patch) && !voice.fourOpPrimary) + return; + + int note = (!voice.channel->percussion ? voice.note : voice.patch->fixedNote) + voice.patchVoice->tune; + + int octave = note / 12; + note %= 12; + + // calculate base frequency (and apply pitch bend / patch detune) + unsigned freq = (note >= 0) ? noteFreq[note] : (noteFreq[note + 12] >> 1); + if (octave < 0) + freq >>= -octave; + else if (octave > 0) + freq <<= octave; + + freq *= voice.channel->pitch * voice.patchVoice->finetune; + + // convert the calculated frequency back to a block and F-number + octave = 0; + while (freq > 0x3ff) + { + freq >>= 1; + octave++; + } + octave = std::min(7, octave); + voice.freq = freq | (octave << 10); + + write(REG_VOICE_FREQL + voice.num, voice.freq & 0xff); + write(REG_VOICE_FREQH + voice.num, (voice.freq >> 8) | (voice.on ? (1 << 5) : 0)); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::silenceVoice(OPLVoice &voice) +{ + voice.on = false; + voice.justChanged = true; + voice.duration = UINT_MAX; + + write(REG_OP_SR + voice.op, 0xff); + write(REG_OP_SR + voice.op + 3, 0xff); + write(REG_VOICE_FREQH + voice.num, voice.freq >> 8); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::midiNoteOn(uint8_t channel, uint8_t note, uint8_t velocity) +{ + note &= 0x7f; + velocity &= 0x7f; + + // if we just now turned this same note on, don't do it again + if (findVoice(channel, note, true)) + return; + + if (!velocity) + return midiNoteOff(channel, note); + + const OPLPatch *newPatch = findPatch(channel, note); + if (!newPatch) + return; + + const int numVoices = ((useFourOp(newPatch) || newPatch->dualTwoOp) ? 2 : 1); + + OPLVoice *voice = nullptr; + for (int i = 0; i < numVoices; i++) + { + if (voice && useFourOp(newPatch) && voice->fourOpOther) + voice = voice->fourOpOther; + else + voice = findVoice(channel, newPatch, note); + if (!voice) + continue; // ?? + + updatePatch(*voice, newPatch, i); + + // update the note parameters for this voice + voice->channel = &m_channels[channel & 15]; + voice->on = voice->justChanged = true; + voice->note = note; + voice->velocity = (((int)velocity + newPatch->velocity) & 127); + voice->duration = 0; + + updateVolume(*voice); + updatePanning(*voice); + + // for 4op instruments, don't key on until we've written both voices... + if (!useFourOp(newPatch)) + { + updateFrequency(*voice); + } + else if (i > 0) + { + updateFrequency(*voice->fourOpOther); + } + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::midiNoteOff(uint8_t channel, uint8_t note) +{ + note &= 0x7f; + + // printf("midiNoteOff: chn %u, note %u\n", channel, note); + OPLVoice *voice; + while ((voice = findVoice(channel, note)) != nullptr) + { + voice->justChanged = voice->on; + voice->on = false; + + write(REG_VOICE_FREQH + voice->num, voice->freq >> 8); + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::midiPitchControl(uint8_t channel, double pitch) +{ + // printf("midiPitchControl: chn %u, val %.02f\n", channel, pitch); + MIDIChannel &ch = m_channels[channel & 15]; + + ch.basePitch = pitch; + ch.pitch = midiCalcBend(pitch * ch.bendRange); + updateChannelVoices(channel, &OPLPlayer::updateFrequency); +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::midiProgramChange(uint8_t channel, uint8_t patchNum) +{ + m_channels[channel & 15].patchNum = patchNum & 0x7f; + // patch change will take effect on the next note for this channel +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::midiControlChange(uint8_t channel, uint8_t control, uint8_t value) +{ + channel &= 15; + control &= 0x7f; + value &= 0x7f; + + MIDIChannel &ch = m_channels[channel]; + + switch (control) + { + case 0: + if (m_midiType == RolandGS) + ch.bank = value; + else if (m_midiType == YamahaXG) + ch.percussion = (value == 0x7f); + break; + + case 6: + if (ch.rpn == 0) + { + ch.bendRange = value; + midiPitchControl(channel, ch.basePitch); + } + break; + + case 7: + ch.volume = value; + updateChannelVoices(channel, &OPLPlayer::updateVolume); + break; + + case 10: + ch.pan = value; + updateChannelVoices(channel, &OPLPlayer::updatePanning); + break; + + case 32: + if (m_midiType == YamahaXG || m_midiType == GeneralMIDI2) + ch.bank = value; + break; + + case 98: + case 99: + ch.rpn = 0x3fff; + break; + + case 100: + ch.rpn &= 0x3f80; + ch.rpn |= value; + break; + + case 101: + ch.rpn &= 0x7f; + ch.rpn |= (value << 7); + break; + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::midiSysEx(const uint8_t *data, uint32_t length) +{ + if (length > 0 && data[0] == 0xF0) + { + data++; + length--; + } + + if (length == 0) + return; + + if (data[0] == 0x7e) // universal non-realtime + { + if (length == 5 && data[1] == 0x7f && data[2] == 0x09) + { + if (data[3] == 0x01) + m_midiType = GeneralMIDI; + else if (data[3] == 0x03) + m_midiType = GeneralMIDI2; + } + } + else if (data[0] == 0x41 && length >= 10 // Roland + && data[2] == 0x42 && data[3] == 0x12) + { + // if we received one of these, assume GS mode + // (some MIDIs seem to e.g. send drum map messages without a GS reset) + m_midiType = RolandGS; + + uint32_t address = (data[4] << 16) | (data[5] << 8) | data[6]; + // for single part parameters, map "part number" to channel number + // (using the default mapping) + uint8_t channel = (address & 0xf00) >> 8; + if (channel == 0) + channel = 9; + else if (channel <= 9) + channel--; + + // Roland GS part parameters + if ((address & 0xfff0ff) == 0x401015) // set drum map + m_channels[channel].percussion = (data[7] != 0x00); + } + else if (length >= 8 && !memcmp(data, "\x43\x10\x4c\x00\x00\x7e\x00\xf7", 8)) // Yamaha + { + m_midiType = YamahaXG; + } +} + +// ---------------------------------------------------------------------------- +void OPLPlayer::midiRawOPL(uint16_t addr, uint8_t data) +{ + if ((addr & 0xF0) == 0xC0) + data |= 0x30; + write(addr, data); + runSamples(1); +} + +// ---------------------------------------------------------------------------- +double OPLPlayer::midiCalcBend(double semitones) +{ + return pow(2, semitones / 12.0); +} diff --git a/libraries/opalmidi/opalmidi.h b/libraries/opalmidi/opalmidi.h new file mode 100644 index 000000000..3cf948850 --- /dev/null +++ b/libraries/opalmidi/opalmidi.h @@ -0,0 +1,161 @@ +#ifndef __OPALMIDI_H +#define __OPALMIDI_H + +#include +#include +#include + +#include "opal.h" +#include "patches.h" + +struct MIDIChannel +{ + uint8_t num = 0; + + bool percussion = false; + uint8_t bank = 0; + uint8_t patchNum = 0; + uint8_t volume = 127; + uint8_t pan = 64; + double basePitch = 0.0; // pitch wheel position + double pitch = 1.0; // frequency multiplier + + uint16_t rpn = 0x3fff; + + uint8_t bendRange = 2; +}; + +struct OPLVoice +{ + int chip = 0; + const MIDIChannel *channel = nullptr; + const OPLPatch *patch = nullptr; + const PatchVoice *patchVoice = nullptr; + + uint16_t num = 0; + uint16_t op = 0; // base operator number, set based on voice num. + bool fourOpPrimary = false; + OPLVoice *fourOpOther = nullptr; + + bool on = false; + bool justChanged = false; // true after note on/off, false after generating at least 1 sample + uint8_t note = 0; + uint8_t velocity = 0; + + // block and F number, calculated from note and channel pitch + uint16_t freq = 0; + + // how long has this note been playing (incremented each midi update) + uint32_t duration = UINT_MAX; +}; + +class OPLPlayer +{ + public: + enum MIDIType + { + GeneralMIDI, + RolandGS, + YamahaXG, + GeneralMIDI2 + }; + + OPLPlayer(int frequency); + virtual ~OPLPlayer(); + + // load instrument patches from a block of memory + bool loadPatches(const uint8_t *data, size_t size); + void loadDefaultPatches(); + + void generate(int16_t *data, unsigned numSamples); + + // reset OPL and midi file + void reset(); + + // helpers for midiEvent + void midiNoteOn(uint8_t channel, uint8_t note, uint8_t velocity); + void midiNoteOff(uint8_t channel, uint8_t note); + void midiPitchControl(uint8_t channel, double pitch); // range is -1.0 to 1.0 + void midiProgramChange(uint8_t channel, uint8_t patchNum); + void midiControlChange(uint8_t channel, uint8_t control, uint8_t value); + // sysex data (data and length *don't* include the opening 0xF0) + void midiSysEx(const uint8_t *data, uint32_t length); + void midiRawOPL(uint16_t addr, uint8_t data); + + // helper for pitch bend and finetune + static double midiCalcBend(double semitones); + + private: + static const unsigned masterClock = 14318181; + + enum + { + REG_TEST = 0x01, + + REG_OP_MODE = 0x20, + REG_OP_LEVEL = 0x40, + REG_OP_AD = 0x60, + REG_OP_SR = 0x80, + REG_VOICE_FREQL = 0xA0, + REG_VOICE_FREQH = 0xB0, + REG_VOICE_CNT = 0xC0, + REG_OP_WAVEFORM = 0xE0, + + REG_4OP = 0x104, + REG_NEW = 0x105, + }; + + void updateMIDI(); + + void runSamples(unsigned count); + + void write(uint16_t addr, uint8_t data); + + // find a voice with the oldest note, or the same patch & note + // if no "off" voices are found, steal one using the same patch or MIDI channel + OPLVoice *findVoice(uint8_t channel, const OPLPatch *patch, uint8_t note); + // find a voice that's playing a specific note on a specific channel + OPLVoice *findVoice(uint8_t channel, uint8_t note, bool justChanged = false); + + // find the patch to use for a specific MIDI channel and note + const OPLPatch *findPatch(uint8_t channel, uint8_t note) const; + + // determine whether this patch should be configured as 4op + bool useFourOp(const OPLPatch *patch) const; + + // determine which operator(s) to scale based on the current operator settings + std::pair activeCarriers(const OPLVoice &voice) const; + + // update a property of all currently playing voices on a MIDI channel + void updateChannelVoices(uint8_t channel, void (OPLPlayer::*func)(OPLVoice &)); + + // update the patch parameters for a voice + void updatePatch(OPLVoice &voice, const OPLPatch *newPatch, uint8_t numVoice = 0); + + // update the volume level for a voice + void updateVolume(OPLVoice &voice); + + // update the pan position for a voice + void updatePanning(OPLVoice &voice); + + // update the block and F-number for a voice (also key on/off) + void updateFrequency(OPLVoice &voice); + + // silence a voice immediately + void silenceVoice(OPLVoice &voice); + + Opal *m_opl3 = nullptr; + int rate; + + std::pair m_output; // output sample data + // if we need to clock one of the OPLs between register writes, save the resulting sample + std::queue> m_sampleFIFO; + + MIDIChannel m_channels[16]; + std::vector m_voices; + MIDIType m_midiType; + + OPLPatchSet m_patches; +}; + +#endif // __RADMIDI_H diff --git a/libraries/opalmidi/patches.cpp b/libraries/opalmidi/patches.cpp new file mode 100644 index 000000000..4a9f68f0d --- /dev/null +++ b/libraries/opalmidi/patches.cpp @@ -0,0 +1,1310 @@ +#include "patches.h" + +#include +#include + +#include "opalmidi.h" + +const char* OPLPatch::names[256] = +{ + "Acoustic Grand Piano", + "Bright Acoustic Piano", + "Electric Grand Piano", + "Honky-tonk Piano", + "Electric Piano 1", + "Electric Piano 2", + "Harpsichord", + "Clavi", + "Celesta", + "Glockenspiel", + "Music Box", + "Vibraphone", + "Marimba", + "Xylophone", + "Tubular Bells", + "Dulcimer", + "Drawbar Organ", + "Percussive Organ", + "Rock Organ", + "Church Organ", + "Reed Organ", + "Accordion", + "Harmonica", + "Tango Accordion", + "Acoustic Guitar (nylon)", + "Acoustic Guitar (steel)", + "Electric Guitar (jazz)", + "Electric Guitar (clean)", + "Electric Guitar (muted)", + "Overdriven Guitar", + "Distortion Guitar", + "Guitar Harmonics", + "Acoustic Bass", + "Electric Bass (finger)", + "Electric Bass (pick)", + "Fretless Bass", + "Slap Bass 1", + "Slap Bass 2", + "Synth Bass 1", + "Synth Bass 2", + "Violin", + "Viola", + "Cello", + "Contrabass", + "Tremolo Strings", + "Pizzicato Strings", + "Orchestral Harp", + "Timpani", + "String Ensemble 1", + "String Ensemble 2", + "SynthStrings 1", + "SynthStrings 2", + "Choir Aahs", + "Voice Oohs", + "Synth Voice", + "Orchestra Hit", + "Trumpet", + "Trombone", + "Tuba", + "Muted Trumpet", + "French Horn", + "Brass Section", + "SynthBrass 1", + "SynthBrass 2", + "Soprano Sax", + "Alto Sax", + "Tenor Sax", + "Baritone Sax", + "Oboe", + "English Horn", + "Bassoon", + "Clarinet", + "Piccolo", + "Flute", + "Recorder", + "Pan Flute", + "Blown Bottle", + "Shakuhachi", + "Whistle", + "Ocarina", + "Lead 1 (square)", + "Lead 2 (sawtooth)", + "Lead 3 (calliope)", + "Lead 4 (chiff)", + "Lead 5 (charang)", + "Lead 6 (voice)", + "Lead 7 (fifths)", + "Lead 8 (bass + lead)", + "Pad 1 (new age)", + "Pad 2 (warm)", + "Pad 3 (polysynth)", + "Pad 4 (choir)", + "Pad 5 (bowed)", + "Pad 6 (metallic)", + "Pad 7 (halo)", + "Pad 8 (sweep)", + "FX 1 (rain)", + "FX 2 (soundtrack)", + "FX 3 (crystal)", + "FX 4 (atmosphere)", + "FX 5 (brightness)", + "FX 6 (goblins)", + "FX 7 (echoes)", + "FX 8 (sci-fi)", + "Sitar", + "Banjo", + "Shamisen", + "Koto", + "Kalimba", + "Bagpipe", + "Fiddle", + "Shanai", + "Tinkle Bell", + "Agogo", + "Steel Drums", + "Woodblock", + "Taiko Drum", + "Melodic Tom", + "Synth Drum", + "Reverse Cymbal", + "Guitar Fret Noise", + "Breath Noise", + "Seashore", + "Bird Tweet", + "Telephone Ring", + "Helicopter", + "Applause", + "Gunshot", + + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "Acoustic Bass Drum", + "Bass Drum 1", + "Side Stick", + "Acoustic Snare", + "Hand Clap", + "Electric Snare", + "Low Floor Tom", + "Closed Hi Hat", + "High Floor Tom", + "Pedal Hi-Hat", + "Low Tom", + "Open Hi-Hat", + "Low-Mid Tom", + "Hi-Mid Tom", + "Crash Cymbal 1", + "High Tom", + "Ride Cymbal 1", + "Chinese Cymbal", + "Ride Bell", + "Tambourine", + "Splash Cymbal", + "Cowbell", + "Crash Cymbal 2", + "Vibraslap", + "Ride Cymbal 2", + "Hi Bongo", + "Low Bongo", + "Mute Hi Conga", + "Open Hi Conga", + "Low Conga", + "High Timbale", + "Low Timbale", + "High Agogo", + "Low Agogo", + "Cabasa", + "Maracas", + "Short Whistle", + "Long Whistle", + "Short Guiro", + "Long Guiro", + "Claves", + "Hi Wood Block", + "Low Wood Block", + "Mute Cuica", + "Open Cuica", + "Mute Triangle", + "Open Triangle", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", +}; + +static const uint8_t default_instrument_bank[11908] = +{ + 0x23, 0x4f, 0x50, 0x4c, 0x5f, 0x49, 0x49, 0x23, 0x00, 0x00, 0x00, 0x00, 0x21, 0xf2, 0x45, 0x00, + 0x02, 0x0f, 0x08, 0x21, 0xf2, 0x76, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0xf2, 0x54, 0x00, 0x01, 0x0b, 0x08, 0x21, 0xf2, 0x56, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x31, 0xf2, 0x55, 0x00, 0x01, 0x09, 0x08, 0x21, 0xf2, 0x76, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0xf2, 0x3b, 0x00, 0x00, 0x0e, 0x06, 0x61, + 0xf3, 0x0b, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf1, 0x38, 0x00, + 0x01, 0x17, 0x00, 0x21, 0xf1, 0x28, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xf1, 0x38, 0x00, 0x02, 0x13, 0x00, 0x21, 0xf1, 0x28, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0xa2, 0x01, 0x00, 0x02, 0x00, 0x08, 0x36, 0xf1, 0xd5, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc2, 0xa8, 0x00, 0x02, 0x12, 0x0a, 0x01, + 0xc2, 0x58, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xf6, 0x54, 0x00, + 0x01, 0x1c, 0x00, 0x81, 0xf3, 0xb5, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0xf6, 0x32, 0x00, 0x02, 0x17, 0x02, 0x11, 0xf5, 0x11, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x17, 0x56, 0x04, 0x00, 0x00, 0x21, 0x02, 0x01, 0xf6, 0x04, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf3, 0xe6, 0x00, 0x01, 0x22, 0x00, 0x81, + 0xf2, 0xf6, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf7, 0x55, 0x00, + 0x00, 0x23, 0x00, 0x21, 0xe5, 0xd8, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x15, 0xf6, 0xa6, 0x00, 0x02, 0x11, 0x04, 0x01, 0xf6, 0xe6, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0xd3, 0x82, 0x00, 0x01, 0x19, 0x0c, 0x81, 0xa3, 0xe3, 0x00, 0x02, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x74, 0x55, 0x01, 0x01, 0x09, 0x04, 0x81, + 0xb3, 0x05, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0xf6, 0x14, 0x00, + 0x02, 0x12, 0x02, 0x31, 0xf1, 0x07, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x72, 0xc7, 0x58, 0x00, 0x00, 0x14, 0x02, 0x30, 0xc7, 0x08, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x70, 0xaa, 0x18, 0x00, 0x01, 0x04, 0x04, 0xb1, 0x8a, 0x08, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x97, 0x23, 0x01, 0x02, 0x13, 0x04, 0xb1, + 0x55, 0x14, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x97, 0x04, 0x01, + 0x00, 0x13, 0x00, 0xb1, 0x55, 0x04, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x98, 0x2a, 0x01, 0x01, 0x08, 0x0c, 0xb1, 0x46, 0x1a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x91, 0x06, 0x01, 0x00, 0x13, 0x0a, 0x21, 0x61, 0x07, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x71, 0x06, 0x00, 0x00, 0x13, 0x06, 0xa1, + 0x61, 0x07, 0x00, 0x02, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xf3, 0x94, 0x01, + 0x02, 0x1c, 0x0c, 0x41, 0xf3, 0xc8, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xf3, 0x9a, 0x01, 0x01, 0x14, 0x0c, 0x11, 0xf1, 0xe7, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x23, 0xf1, 0x3a, 0x00, 0x01, 0x1f, 0x00, 0x21, 0xf2, 0xf8, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf6, 0x22, 0x01, 0x02, 0x07, 0x06, 0x21, + 0xf3, 0xf8, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf9, 0x54, 0x00, + 0x01, 0x07, 0x00, 0x21, 0xf6, 0x3a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x91, 0x41, 0x01, 0x01, 0x0a, 0x08, 0x21, 0x84, 0x19, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x23, 0x95, 0x19, 0x01, 0x01, 0x0a, 0x08, 0x21, 0x94, 0x19, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x20, 0x4f, 0x00, 0x02, 0x21, 0x08, 0x84, + 0xd1, 0xf8, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x94, 0x06, 0x00, + 0x00, 0x1e, 0x02, 0xa2, 0xc3, 0xa6, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0xf1, 0x28, 0x00, 0x00, 0x12, 0x0a, 0x31, 0xf1, 0x18, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x31, 0xf1, 0xe8, 0x00, 0x02, 0x0d, 0x0a, 0x31, 0xf1, 0x78, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x51, 0x28, 0x00, 0x01, 0x1b, 0x0c, 0x32, + 0x71, 0x48, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xa1, 0x9a, 0x00, + 0x02, 0x0b, 0x08, 0x21, 0xf2, 0xdf, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0xa2, 0x16, 0x00, 0x02, 0x0b, 0x08, 0x21, 0xa1, 0xdf, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x31, 0xf4, 0xe8, 0x00, 0x02, 0x0b, 0x0a, 0x31, 0xf1, 0x78, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xf1, 0x28, 0x00, 0x00, 0x12, 0x0a, 0x31, + 0xf1, 0x18, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xdd, 0x13, 0x01, + 0x00, 0x15, 0x08, 0x21, 0x56, 0x26, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0xdd, 0x13, 0x01, 0x00, 0x16, 0x08, 0x21, 0x66, 0x06, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0xd1, 0x1c, 0x01, 0x01, 0x09, 0x08, 0x31, 0x61, 0x0c, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x71, 0x12, 0x01, 0x01, 0x0d, 0x02, 0x23, + 0x72, 0x06, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0xf1, 0x21, 0x01, + 0x01, 0x00, 0x02, 0xe1, 0x6f, 0x16, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xf5, 0x75, 0x01, 0x00, 0x1a, 0x00, 0x01, 0x85, 0x35, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0xf5, 0x75, 0x01, 0x00, 0x1d, 0x00, 0x01, 0xf3, 0xf4, 0x00, 0x02, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xf5, 0x05, 0x01, 0x01, 0x01, 0x02, 0x11, + 0xf2, 0xc3, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xb1, 0x25, 0x01, + 0x02, 0x1b, 0x0e, 0xa2, 0x72, 0x08, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa1, 0x7f, 0x03, 0x01, 0x02, 0x18, 0x00, 0x21, 0x3f, 0x07, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa1, 0xc1, 0x12, 0x00, 0x02, 0x13, 0x0a, 0x61, 0x4f, 0x05, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xc1, 0x22, 0x00, 0x00, 0x18, 0x0c, 0x61, + 0x4f, 0x05, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xf4, 0x15, 0x00, + 0x01, 0x1b, 0x00, 0x72, 0x8a, 0x05, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa1, 0x74, 0x39, 0x00, 0x02, 0x10, 0x00, 0x61, 0x71, 0x67, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x71, 0x54, 0x05, 0x00, 0x01, 0x17, 0x0c, 0x72, 0x7a, 0x05, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x54, 0x63, 0x00, 0x00, 0x00, 0x08, 0x41, + 0xa5, 0x45, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x85, 0x17, 0x00, + 0x02, 0x12, 0x0c, 0x21, 0x8f, 0x09, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x75, 0x17, 0x00, 0x02, 0x14, 0x0c, 0x21, 0x8f, 0x09, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x76, 0x15, 0x00, 0x02, 0x14, 0x0c, 0x61, 0x82, 0x37, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x9e, 0x17, 0x01, 0x01, 0x03, 0x02, 0x21, + 0x62, 0x2c, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x61, 0x6a, 0x00, + 0x02, 0x1b, 0x02, 0x21, 0x7f, 0x0a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x75, 0x1f, 0x00, 0x02, 0x0a, 0x08, 0x22, 0x74, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa1, 0x72, 0x55, 0x01, 0x02, 0x06, 0x00, 0x21, 0x71, 0x18, 0x00, 0x02, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x54, 0x3c, 0x00, 0x01, 0x0d, 0x08, 0x21, + 0xa6, 0x1c, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x93, 0x02, 0x01, + 0x02, 0x0f, 0x08, 0x61, 0x72, 0x0b, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x93, 0x03, 0x01, 0x02, 0x0e, 0x08, 0x61, 0x72, 0x09, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x31, 0x93, 0x03, 0x01, 0x02, 0x11, 0x0a, 0x61, 0x82, 0x09, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x93, 0x0f, 0x01, 0x02, 0x0e, 0x0a, 0x61, + 0x72, 0x0f, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xaa, 0x16, 0x01, + 0x01, 0x0b, 0x08, 0x21, 0x8f, 0x0a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x7e, 0x17, 0x01, 0x02, 0x10, 0x06, 0x21, 0x8b, 0x0c, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x31, 0x75, 0x19, 0x01, 0x02, 0x01, 0x00, 0x32, 0x61, 0x19, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x9b, 0x21, 0x00, 0x02, 0x10, 0x04, 0x21, + 0x72, 0x17, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0x85, 0x5f, 0x00, + 0x00, 0x1f, 0x00, 0xe1, 0x65, 0x1a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe1, 0x88, 0x5f, 0x00, 0x01, 0x06, 0x00, 0xe1, 0x65, 0x1a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xa1, 0x75, 0x1f, 0x00, 0x02, 0x1c, 0x02, 0x21, 0x75, 0x0a, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x84, 0x58, 0x00, 0x02, 0x0b, 0x00, 0x21, + 0x65, 0x1a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0x66, 0x56, 0x00, + 0x01, 0x0c, 0x00, 0xa1, 0x65, 0x26, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x62, 0x76, 0x46, 0x00, 0x03, 0x0b, 0x00, 0xa1, 0x55, 0x36, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x62, 0x57, 0x07, 0x00, 0x02, 0x22, 0x0b, 0xa1, 0x56, 0x07, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x77, 0x07, 0x00, 0x02, 0x1c, 0x0b, 0xa1, + 0x76, 0x07, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0x03, 0x02, + 0x01, 0x19, 0x00, 0x21, 0xff, 0x0f, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0xff, 0x0f, 0x01, 0x00, 0x0e, 0x00, 0x21, 0xff, 0x0f, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x22, 0x86, 0x55, 0x00, 0x01, 0x06, 0x00, 0x21, 0x64, 0x18, 0x00, 0x02, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x66, 0x12, 0x00, 0x01, 0x05, 0x00, 0xa1, + 0x96, 0x0a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x92, 0x2a, 0x01, + 0x02, 0x0b, 0x00, 0x22, 0x91, 0x2a, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa2, 0xdf, 0x05, 0x00, 0x02, 0x1e, 0x02, 0x61, 0x6f, 0x07, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0xef, 0x01, 0x00, 0x00, 0x1a, 0x00, 0x60, 0x8f, 0x06, 0x02, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xf1, 0x29, 0x00, 0x02, 0x0f, 0x0a, 0x21, + 0xf4, 0x09, 0x00, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x53, 0x94, 0x00, + 0x02, 0x25, 0x02, 0xa1, 0xa0, 0x05, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0xa8, 0x11, 0x00, 0x00, 0x1f, 0x0a, 0xb1, 0x25, 0x03, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x91, 0x34, 0x00, 0x00, 0x17, 0x0c, 0x61, 0x55, 0x16, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x54, 0x01, 0x00, 0x01, 0x1d, 0x00, 0x72, + 0x6a, 0x03, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x21, 0x43, 0x00, + 0x02, 0x17, 0x08, 0xa2, 0x42, 0x35, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa1, 0xa1, 0x77, 0x01, 0x00, 0x1c, 0x00, 0x21, 0x31, 0x47, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0x11, 0x33, 0x00, 0x02, 0x09, 0x0a, 0x61, 0x42, 0x25, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x11, 0x47, 0x01, 0x00, 0x15, 0x00, 0x21, + 0xcf, 0x07, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xf8, 0xf6, 0x00, + 0x03, 0x0e, 0x02, 0x51, 0x86, 0x02, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x21, 0x23, 0x01, 0x00, 0x15, 0x00, 0x21, 0x41, 0x13, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x74, 0x95, 0x00, 0x01, 0x1b, 0x00, 0x01, 0xa5, 0x72, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xb1, 0x81, 0x00, 0x02, 0x12, 0x0c, 0x61, + 0xf2, 0x26, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0xf1, 0x51, 0x01, + 0x01, 0x0d, 0x00, 0x42, 0xf2, 0xf5, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x11, 0x51, 0x01, 0x02, 0x14, 0x06, 0xa3, 0x11, 0x13, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x11, 0x31, 0x00, 0x02, 0x0c, 0x06, 0xa1, 0x1d, 0x03, 0x00, 0x02, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0xf3, 0x73, 0x01, 0x01, 0x0c, 0x04, 0x61, + 0x81, 0x23, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd2, 0x53, 0x00, + 0x02, 0x05, 0x00, 0x07, 0xf2, 0xf6, 0x01, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x11, 0xa3, 0x11, 0x01, 0x00, 0x0c, 0x00, 0x13, 0xa2, 0xe5, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0xf6, 0x41, 0x01, 0x00, 0x06, 0x04, 0x11, 0xf2, 0xe6, 0x02, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0xd4, 0x32, 0x00, 0x02, 0x11, 0x08, 0x91, + 0xeb, 0x11, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfa, 0x56, 0x00, + 0x01, 0x0f, 0x0c, 0x01, 0xc2, 0x05, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x7c, 0x20, 0x00, 0x01, 0x09, 0x06, 0x22, 0x6f, 0x0c, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x31, 0xdd, 0x33, 0x01, 0x02, 0x05, 0x0a, 0x21, 0x56, 0x16, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xda, 0x05, 0x02, 0x00, 0x04, 0x06, 0x21, + 0x8f, 0x0b, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xf1, 0xe5, 0x00, + 0x01, 0x2a, 0x06, 0x03, 0xc3, 0xe5, 0x00, 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0xec, 0x26, 0x00, 0x00, 0x15, 0x0a, 0x02, 0xf8, 0x16, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x67, 0x35, 0x00, 0x02, 0x1d, 0x08, 0x01, 0xdf, 0x05, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfa, 0x28, 0x00, 0x02, 0x16, 0x0a, 0x12, + 0xf8, 0xe5, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa8, 0x07, 0x00, + 0x02, 0x06, 0x06, 0x00, 0xfa, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x11, 0xf8, 0x47, 0x02, 0x01, 0x01, 0x04, 0x10, 0xf3, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xf1, 0x06, 0x02, 0x02, 0x0e, 0x0e, 0x10, 0xf3, 0x02, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xc0, + 0x1f, 0xff, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xf8, 0x24, 0x00, + 0x02, 0x00, 0x0e, 0x03, 0x56, 0x84, 0x02, 0x02, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd0, 0x34, 0x04, 0x03, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0e, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xc0, 0x1f, 0x02, 0x03, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd5, 0x37, 0xa3, 0x00, 0x02, 0x15, 0x00, 0xda, + 0x56, 0x37, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0xb2, 0x61, 0x02, + 0x01, 0x1c, 0x0a, 0x14, 0xf4, 0x15, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd0, 0x4f, 0xf5, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x26, 0xff, 0x01, 0x00, 0x00, 0x00, 0x0e, 0xe4, 0x12, 0x16, 0x01, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0xf0, 0x00, 0x00, 0x00, 0x0e, 0x00, + 0xf6, 0xc9, 0x02, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x26, 0x00, 0xf9, 0x57, 0x02, + 0x00, 0x00, 0x00, 0x00, 0xfb, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x19, + 0x00, 0xfa, 0x47, 0x00, 0x00, 0x00, 0x06, 0x00, 0xf9, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x53, 0x02, 0xfd, 0x67, 0x00, 0x80, 0x00, 0x06, 0x03, 0xf7, 0x78, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x20, 0x0f, 0xf7, 0x14, 0x02, 0x00, 0x05, 0x0e, 0x00, + 0xf9, 0x47, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x3c, 0xe1, 0x88, 0xfb, 0x03, + 0x00, 0x00, 0x0f, 0xff, 0xa6, 0xa8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x80, 0x24, + 0x06, 0xaa, 0xff, 0x00, 0x00, 0x00, 0x0e, 0x00, 0xf7, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, + 0x01, 0x00, 0x80, 0x0f, 0x02, 0xf5, 0x6c, 0x00, 0x00, 0x00, 0x07, 0x03, 0xf7, 0x38, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x58, 0x0c, 0x98, 0x5e, 0x02, 0x00, 0x00, 0x0f, 0x0f, + 0xfb, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x13, 0x02, 0xf5, 0x78, 0x00, + 0x00, 0x00, 0x07, 0x00, 0xf7, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x58, + 0x0c, 0x78, 0x5e, 0x02, 0x00, 0x00, 0x0f, 0x0a, 0x8a, 0x2b, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x15, 0x02, 0xf5, 0x37, 0x00, 0x00, 0x00, 0x03, 0x02, 0xf7, 0x37, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x4f, 0x00, 0xc7, 0x01, 0x02, 0x40, 0x05, 0x0e, 0x0b, + 0xf9, 0x33, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x1a, 0x02, 0xf5, 0x37, 0x00, + 0x00, 0x00, 0x03, 0x02, 0xf7, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x1c, + 0x02, 0xf5, 0x37, 0x00, 0x00, 0x00, 0x03, 0x02, 0xf7, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x3c, 0x04, 0xc2, 0xe6, 0x00, 0x00, 0x10, 0x0e, 0x00, 0xe8, 0x43, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x20, 0x02, 0xf5, 0x37, 0x00, 0x00, 0x00, 0x03, 0x02, + 0xf7, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x3c, 0x03, 0xfd, 0x12, 0x02, + 0x80, 0x00, 0x0a, 0x02, 0xfd, 0x05, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x60, + 0x00, 0xe4, 0x85, 0x00, 0x80, 0x00, 0x0e, 0xc0, 0xd7, 0x34, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x48, 0x04, 0xe2, 0xe6, 0x00, 0x80, 0x10, 0x0e, 0x01, 0xb8, 0x44, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x4f, 0x02, 0x76, 0x77, 0x02, 0x80, 0x07, 0x0f, 0x01, + 0x98, 0x67, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x45, 0x04, 0x94, 0x70, 0x02, + 0x80, 0x01, 0x0e, 0x07, 0xc6, 0xa3, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x47, + 0x00, 0xfd, 0x67, 0x00, 0x00, 0x00, 0x06, 0x01, 0xf6, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x3c, 0x04, 0xc2, 0xe6, 0x00, 0x00, 0x10, 0x0e, 0x00, 0xe8, 0x43, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf9, 0xb5, 0x00, 0x00, 0x07, 0x0b, 0xbf, + 0xd4, 0x50, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x3c, 0x03, 0xfd, 0x12, 0x02, + 0x80, 0x00, 0x0a, 0x02, 0xfd, 0x05, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x3c, + 0x00, 0xfb, 0x56, 0x02, 0x00, 0x00, 0x04, 0x00, 0xfa, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x36, 0x00, 0xfb, 0x56, 0x02, 0x00, 0x00, 0x04, 0x00, 0xfa, 0x26, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x48, 0x00, 0xfb, 0x56, 0x02, 0x80, 0x00, 0x00, 0x00, + 0xf7, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x43, 0x00, 0xfb, 0x56, 0x02, + 0x80, 0x00, 0x00, 0x00, 0xf7, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x3c, + 0x00, 0xfb, 0x56, 0x02, 0x80, 0x00, 0x00, 0x00, 0xf7, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x37, 0x03, 0xfb, 0x56, 0x00, 0x80, 0x01, 0x00, 0x00, 0xf7, 0x17, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x30, 0x03, 0xfb, 0x56, 0x00, 0x80, 0x01, 0x00, 0x00, + 0xf7, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x4d, 0x01, 0xfd, 0x67, 0x03, + 0x00, 0x00, 0x08, 0x01, 0xf6, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x48, + 0x01, 0xfd, 0x67, 0x03, 0x00, 0x00, 0x08, 0x01, 0xf6, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x58, 0x0c, 0x78, 0x5e, 0x02, 0x00, 0x00, 0x0f, 0x0a, 0x8a, 0x2b, 0x03, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x5a, 0xd6, 0x02, 0x00, 0x0e, 0x0a, 0xbf, + 0xff, 0xff, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x31, 0x00, 0xf9, 0xc7, 0x01, + 0x00, 0x07, 0x0a, 0x80, 0xff, 0xff, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x31, + 0x00, 0xf9, 0xc7, 0x01, 0x00, 0x07, 0x0a, 0x80, 0xff, 0xff, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x31, 0x00, 0xf9, 0xc7, 0x01, 0x00, 0x07, 0x0a, 0x80, 0xff, 0xff, 0x00, 0xc0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x31, 0x00, 0xf9, 0xc7, 0x01, 0x00, 0x07, 0x0a, 0x80, + 0xff, 0xff, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x49, 0x13, 0xf8, 0xd1, 0x01, + 0x40, 0x04, 0x06, 0x12, 0xf5, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x44, + 0x13, 0xf8, 0xd1, 0x01, 0x40, 0x04, 0x06, 0x12, 0xf5, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x3d, 0x13, 0xf8, 0xd1, 0x01, 0x40, 0x04, 0x06, 0x12, 0xf5, 0x78, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x01, 0x5e, 0xdc, 0x01, 0x00, 0x0b, 0x0a, 0xbf, + 0xff, 0xff, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x31, 0x00, 0xf9, 0xc7, 0x01, + 0x00, 0x07, 0x0a, 0x80, 0xff, 0xff, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x80, 0x5a, + 0xc5, 0xf2, 0x60, 0x00, 0x40, 0x0f, 0x08, 0xd4, 0xf4, 0x7a, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x80, 0x5a, 0x85, 0xf2, 0x60, 0x01, 0x40, 0x0f, 0x08, 0x94, 0xf2, 0xb7, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x6f, 0x75, 0x73, 0x74, 0x69, 0x63, 0x20, 0x47, 0x72, 0x61, + 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x41, 0x63, 0x6f, 0x75, 0x73, + 0x74, 0x69, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x47, 0x72, 0x61, + 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x6f, 0x6e, 0x6b, 0x79, 0x2d, 0x54, 0x6f, 0x6e, 0x6b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x50, 0x69, 0x61, + 0x6e, 0x6f, 0x20, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x50, 0x69, 0x61, + 0x6e, 0x6f, 0x20, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x61, 0x72, 0x70, 0x73, 0x69, 0x63, 0x68, 0x6f, 0x72, 0x64, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x6c, 0x61, 0x76, 0x69, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x6e, 0x73, 0x70, 0x69, 0x65, 0x6c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x75, 0x73, 0x69, 0x63, 0x20, 0x42, 0x6f, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x62, 0x72, 0x61, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x61, 0x72, 0x69, 0x6d, 0x62, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x58, 0x79, 0x6c, 0x6f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x75, 0x62, 0x75, 0x6c, 0x61, 0x72, 0x20, 0x42, 0x65, 0x6c, 0x6c, + 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x75, 0x6c, 0x63, 0x69, 0x6d, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x72, 0x61, 0x77, 0x62, 0x61, 0x72, 0x20, 0x4f, 0x72, 0x67, 0x61, + 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x65, 0x72, 0x63, 0x75, 0x73, 0x73, 0x69, 0x76, 0x65, 0x20, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x6f, 0x63, 0x6b, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x68, 0x75, 0x72, 0x63, 0x68, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x65, 0x65, 0x64, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x63, 0x6f, 0x72, 0x69, 0x64, 0x61, 0x6e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x61, 0x72, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x61, 0x6e, 0x67, 0x6f, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x72, 0x64, + 0x69, 0x61, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x6f, 0x75, 0x73, 0x74, 0x69, 0x63, 0x20, 0x47, 0x75, 0x69, + 0x74, 0x61, 0x72, 0x28, 0x6e, 0x79, 0x6c, 0x6f, 0x6e, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x6f, 0x75, 0x73, 0x74, 0x69, 0x63, 0x20, 0x47, 0x75, 0x69, + 0x74, 0x61, 0x72, 0x28, 0x73, 0x74, 0x65, 0x65, 0x6c, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x47, 0x75, 0x69, + 0x74, 0x61, 0x72, 0x28, 0x6a, 0x61, 0x7a, 0x7a, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x47, 0x75, 0x69, + 0x74, 0x61, 0x72, 0x28, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x47, 0x75, 0x69, + 0x74, 0x61, 0x72, 0x28, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x76, 0x65, 0x72, 0x64, 0x72, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x47, + 0x75, 0x69, 0x74, 0x61, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x47, + 0x75, 0x69, 0x74, 0x61, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x75, 0x69, 0x74, 0x61, 0x72, 0x20, 0x48, 0x61, 0x72, 0x6d, 0x6f, + 0x6e, 0x69, 0x63, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x6f, 0x75, 0x73, 0x74, 0x69, 0x63, 0x20, 0x42, 0x61, 0x73, + 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x42, 0x61, 0x73, + 0x73, 0x28, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x42, 0x61, 0x73, + 0x73, 0x28, 0x70, 0x69, 0x63, 0x6b, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x72, 0x65, 0x74, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x42, 0x61, 0x73, + 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x6c, 0x61, 0x70, 0x20, 0x42, 0x61, 0x73, 0x73, 0x20, 0x31, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x6c, 0x61, 0x70, 0x20, 0x42, 0x61, 0x73, 0x73, 0x20, 0x32, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x20, 0x42, 0x61, 0x73, 0x73, 0x20, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x20, 0x42, 0x61, 0x73, 0x73, 0x20, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x6f, 0x6c, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x62, 0x61, 0x73, 0x73, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x72, 0x65, 0x6d, 0x6f, 0x6c, 0x6f, 0x20, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x69, 0x7a, 0x7a, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x20, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x20, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x69, 0x6d, 0x70, 0x61, 0x6e, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x45, 0x6e, 0x73, 0x65, 0x6d, + 0x62, 0x6c, 0x65, 0x20, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x45, 0x6e, 0x73, 0x65, 0x6d, + 0x62, 0x6c, 0x65, 0x20, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, + 0x20, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, + 0x20, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x68, 0x6f, 0x69, 0x72, 0x20, 0x41, 0x61, 0x68, 0x73, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x6f, 0x69, 0x63, 0x65, 0x20, 0x4f, 0x6f, 0x68, 0x73, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x20, 0x56, 0x6f, 0x69, 0x63, 0x65, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x72, 0x63, 0x68, 0x65, 0x73, 0x74, 0x72, 0x61, 0x20, 0x48, 0x69, + 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x72, 0x75, 0x6d, 0x70, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x72, 0x6f, 0x6d, 0x62, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x75, 0x62, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x75, 0x74, 0x65, 0x64, 0x20, 0x54, 0x72, 0x75, 0x6d, 0x70, 0x65, + 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x72, 0x65, 0x6e, 0x63, 0x68, 0x20, 0x48, 0x6f, 0x72, 0x6e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x72, 0x61, 0x73, 0x73, 0x20, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x42, 0x72, 0x61, 0x73, 0x73, 0x20, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x42, 0x72, 0x61, 0x73, 0x73, 0x20, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x6f, 0x70, 0x72, 0x61, 0x6e, 0x6f, 0x20, 0x53, 0x61, 0x78, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x6c, 0x74, 0x6f, 0x20, 0x53, 0x61, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x65, 0x6e, 0x6f, 0x72, 0x20, 0x53, 0x61, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x61, 0x72, 0x69, 0x74, 0x6f, 0x6e, 0x65, 0x20, 0x53, 0x61, 0x78, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x62, 0x6f, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x20, 0x48, 0x6f, 0x72, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x61, 0x73, 0x73, 0x6f, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x6c, 0x61, 0x72, 0x69, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x69, 0x63, 0x63, 0x6f, 0x6c, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x6c, 0x75, 0x74, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x6e, 0x20, 0x46, 0x6c, 0x75, 0x74, 0x65, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x6c, 0x6f, 0x77, 0x6e, 0x20, 0x42, 0x6f, 0x74, 0x74, 0x6c, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x6b, 0x61, 0x6b, 0x75, 0x68, 0x61, 0x63, 0x68, 0x69, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x68, 0x69, 0x73, 0x74, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x63, 0x61, 0x72, 0x69, 0x6e, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x61, 0x64, 0x20, 0x31, 0x20, 0x28, 0x53, 0x71, 0x75, 0x61, + 0x72, 0x65, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x61, 0x64, 0x20, 0x32, 0x20, 0x28, 0x53, 0x61, 0x77, 0x74, + 0x6f, 0x6f, 0x74, 0x68, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x61, 0x64, 0x20, 0x33, 0x20, 0x28, 0x43, 0x61, 0x6c, 0x6c, + 0x69, 0x6f, 0x70, 0x65, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x61, 0x64, 0x20, 0x34, 0x20, 0x28, 0x43, 0x68, 0x69, 0x66, + 0x66, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x61, 0x64, 0x20, 0x35, 0x20, 0x28, 0x43, 0x68, 0x61, 0x72, + 0x61, 0x6e, 0x67, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x61, 0x64, 0x20, 0x36, 0x20, 0x28, 0x56, 0x6f, 0x69, 0x63, + 0x65, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x61, 0x64, 0x20, 0x37, 0x20, 0x28, 0x46, 0x69, 0x66, 0x74, + 0x68, 0x73, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x61, 0x64, 0x20, 0x38, 0x20, 0x28, 0x42, 0x61, 0x73, 0x73, + 0x2b, 0x6c, 0x65, 0x61, 0x64, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x64, 0x20, 0x31, 0x20, 0x28, 0x4e, 0x65, 0x77, 0x20, 0x61, + 0x67, 0x65, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x64, 0x20, 0x32, 0x20, 0x28, 0x57, 0x61, 0x72, 0x6d, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x64, 0x20, 0x33, 0x20, 0x28, 0x50, 0x6f, 0x6c, 0x79, 0x73, + 0x79, 0x6e, 0x74, 0x68, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x64, 0x20, 0x34, 0x20, 0x28, 0x43, 0x68, 0x6f, 0x69, 0x72, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x64, 0x20, 0x35, 0x20, 0x28, 0x42, 0x6f, 0x77, 0x65, 0x64, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x64, 0x20, 0x36, 0x20, 0x28, 0x4d, 0x65, 0x74, 0x61, 0x6c, + 0x6c, 0x69, 0x63, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x64, 0x20, 0x37, 0x20, 0x28, 0x48, 0x61, 0x6c, 0x6f, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x61, 0x64, 0x20, 0x38, 0x20, 0x28, 0x53, 0x77, 0x65, 0x65, 0x70, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x58, 0x20, 0x31, 0x20, 0x28, 0x52, 0x61, 0x69, 0x6e, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x58, 0x20, 0x32, 0x20, 0x28, 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x74, + 0x72, 0x61, 0x63, 0x6b, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x58, 0x20, 0x33, 0x20, 0x28, 0x43, 0x72, 0x79, 0x73, 0x74, 0x61, + 0x6c, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x58, 0x20, 0x34, 0x20, 0x28, 0x41, 0x74, 0x6d, 0x6f, 0x73, 0x70, + 0x68, 0x65, 0x72, 0x65, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x58, 0x20, 0x35, 0x20, 0x28, 0x42, 0x72, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x65, 0x73, 0x73, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x58, 0x20, 0x36, 0x20, 0x28, 0x47, 0x6f, 0x62, 0x6c, 0x69, 0x6e, + 0x73, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x58, 0x20, 0x37, 0x20, 0x28, 0x45, 0x63, 0x68, 0x6f, 0x65, 0x73, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x58, 0x20, 0x38, 0x20, 0x28, 0x53, 0x63, 0x69, 0x2d, 0x66, 0x69, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x69, 0x74, 0x61, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x61, 0x6e, 0x6a, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x68, 0x61, 0x6d, 0x69, 0x73, 0x65, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4b, 0x6f, 0x74, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4b, 0x61, 0x6c, 0x69, 0x6d, 0x62, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x61, 0x67, 0x70, 0x69, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x68, 0x61, 0x6e, 0x61, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x69, 0x6e, 0x6b, 0x6c, 0x65, 0x20, 0x42, 0x65, 0x6c, 0x6c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x67, 0x6f, 0x67, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x74, 0x65, 0x65, 0x6c, 0x20, 0x44, 0x72, 0x75, 0x6d, 0x73, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x57, 0x6f, 0x6f, 0x64, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x61, 0x69, 0x6b, 0x6f, 0x20, 0x44, 0x72, 0x75, 0x6d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x65, 0x6c, 0x6f, 0x64, 0x69, 0x63, 0x20, 0x54, 0x6f, 0x6d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x20, 0x44, 0x72, 0x75, 0x6d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x20, 0x43, 0x79, 0x6d, 0x62, + 0x61, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x75, 0x69, 0x74, 0x61, 0x72, 0x20, 0x46, 0x72, 0x65, 0x74, 0x20, + 0x4e, 0x6f, 0x69, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x72, 0x65, 0x61, 0x74, 0x68, 0x20, 0x4e, 0x6f, 0x69, 0x73, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x65, 0x61, 0x73, 0x68, 0x6f, 0x72, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x69, 0x72, 0x64, 0x20, 0x54, 0x77, 0x65, 0x65, 0x74, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x20, 0x72, 0x69, + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x69, 0x63, 0x6f, 0x70, 0x74, 0x65, 0x72, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x70, 0x70, 0x6c, 0x61, 0x75, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x75, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x6f, 0x75, 0x73, 0x74, 0x69, 0x63, 0x20, 0x42, 0x61, 0x73, + 0x73, 0x20, 0x44, 0x72, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x6f, 0x75, 0x73, 0x74, 0x69, 0x63, 0x20, 0x42, 0x61, 0x73, + 0x73, 0x20, 0x44, 0x72, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x20, 0x53, 0x74, 0x69, 0x63, 0x6b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x6f, 0x75, 0x73, 0x74, 0x69, 0x63, 0x20, 0x53, 0x6e, 0x61, + 0x72, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x61, 0x6e, 0x64, 0x20, 0x43, 0x6c, 0x61, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x69, 0x63, 0x20, 0x53, 0x6e, 0x61, + 0x72, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x77, 0x20, 0x46, 0x6c, 0x6f, 0x6f, 0x72, 0x20, 0x54, 0x6f, + 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x20, 0x48, 0x69, 0x67, 0x68, 0x2d, + 0x48, 0x61, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x69, 0x67, 0x68, 0x20, 0x46, 0x6c, 0x6f, 0x6f, 0x72, 0x20, 0x54, + 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x65, 0x64, 0x61, 0x6c, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x48, + 0x61, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x77, 0x20, 0x54, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x48, 0x61, + 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x77, 0x2d, 0x4d, 0x69, 0x64, 0x20, 0x54, 0x6f, 0x6d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x69, 0x67, 0x68, 0x2d, 0x4d, 0x69, 0x64, 0x20, 0x54, 0x6f, 0x6d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x72, 0x61, 0x73, 0x68, 0x20, 0x43, 0x79, 0x6d, 0x62, 0x61, 0x6c, + 0x20, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x69, 0x67, 0x68, 0x20, 0x54, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x69, 0x64, 0x65, 0x20, 0x43, 0x79, 0x6d, 0x62, 0x61, 0x6c, 0x20, + 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x68, 0x69, 0x6e, 0x73, 0x65, 0x73, 0x20, 0x43, 0x79, 0x6d, 0x62, + 0x61, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x69, 0x64, 0x65, 0x20, 0x42, 0x65, 0x6c, 0x6c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x54, 0x61, 0x6d, 0x62, 0x6f, 0x75, 0x72, 0x69, 0x6e, 0x65, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x70, 0x6c, 0x61, 0x73, 0x68, 0x20, 0x43, 0x79, 0x6d, 0x62, 0x61, + 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x77, 0x62, 0x65, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x72, 0x61, 0x73, 0x68, 0x20, 0x43, 0x79, 0x6d, 0x62, 0x61, 0x6c, + 0x20, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x62, 0x72, 0x61, 0x73, 0x6c, 0x61, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x69, 0x64, 0x65, 0x20, 0x43, 0x79, 0x6d, 0x62, 0x61, 0x6c, 0x20, + 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x69, 0x67, 0x68, 0x20, 0x42, 0x6f, 0x6e, 0x67, 0x6f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x77, 0x20, 0x42, 0x61, 0x6e, 0x67, 0x6f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x75, 0x74, 0x65, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x43, 0x6f, + 0x6e, 0x67, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x43, 0x6f, + 0x6e, 0x67, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x77, 0x20, 0x43, 0x6f, 0x6e, 0x67, 0x61, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x69, 0x67, 0x68, 0x20, 0x54, 0x69, 0x6d, 0x62, 0x61, 0x6c, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x77, 0x20, 0x54, 0x69, 0x6d, 0x62, 0x61, 0x6c, 0x65, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, 0x67, 0x6f, 0x67, 0x6f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x77, 0x20, 0x41, 0x67, 0x6f, 0x67, 0x6f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x61, 0x62, 0x61, 0x73, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x61, 0x72, 0x61, 0x63, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x20, 0x57, 0x68, 0x69, 0x73, 0x74, 0x6c, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x6e, 0x67, 0x20, 0x57, 0x68, 0x69, 0x73, 0x74, 0x6c, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x20, 0x47, 0x75, 0x69, 0x72, 0x6f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x6e, 0x67, 0x20, 0x47, 0x75, 0x69, 0x72, 0x6f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x6c, 0x61, 0x76, 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x69, 0x67, 0x68, 0x20, 0x57, 0x6f, 0x6f, 0x64, 0x20, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x6f, 0x77, 0x20, 0x57, 0x6f, 0x6f, 0x64, 0x20, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x75, 0x74, 0x65, 0x20, 0x43, 0x75, 0x69, 0x63, 0x61, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x43, 0x75, 0x69, 0x63, 0x61, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x75, 0x74, 0x65, 0x20, 0x54, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x6c, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x54, 0x72, 0x69, 0x61, 0x6e, 0x67, 0x6c, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +// ---------------------------------------------------------------------------- +bool OPLPatch::load(OPLPatchSet& patches, const uint8_t *data, size_t size) +{ + return loadWOPL(patches, data, size) + || loadOP2(patches, data, size) + || loadAIL(patches, data, size) + || loadTMB(patches, data, size); +} + +// ---------------------------------------------------------------------------- +void OPLPatch::loadDefault(OPLPatchSet& patches) +{ + loadOP2(patches, default_instrument_bank, 11908); +} + +// ---------------------------------------------------------------------------- +bool OPLPatch::loadWOPL(OPLPatchSet& patches, const uint8_t *data, size_t size) +{ + if (size < 19) + return false; + if (strcmp((const char*)data, "WOPL3-BANK")) + return false; + + // mixed endianness? why??? + uint16_t version = data[11] | (data[12] << 8); + uint16_t numMelody = (data[13] << 8) | data[14]; + uint16_t numPerc = (data[15] << 8) | data[16]; + + if (version > 3) + return false; + + // currently not supported: global LFO flags, volume model options + + const uint32_t bankOffset = 19; + const uint32_t patchOffset = bankOffset + 34 * (numMelody + numPerc); + + const unsigned instSize = (version >= 3) ? 66 : 62; + const unsigned bankInfoSize = (version >= 2) ? 34 : 0; + + if (size < (numMelody + numPerc) * (128 * instSize + bankInfoSize)) + return false; + + for (unsigned i = 0; i < 128 * (numMelody + numPerc); i++) + { + const uint8_t *bytes; + unsigned key = i & 0x7f; + if (version >= 2) + { + const unsigned bank = i >> 7; + bytes = data + bankOffset + 34 * bank; + + if (bank >= numMelody) // percussion banks (use LSB) + key |= (bytes[32] << 8) | 0x80; + else if (bytes[32]) // bank LSB set (XG) + key |= (bytes[32] << 8); + else if (bytes[33]) // bank MSB set (GS) + key |= (bytes[33] << 8); + } + + bytes = data + patchOffset + instSize * i; + + // ignore other data for this patch if it's a blank instrument + // *or* if one of the rhythm mode bits is set (not supported here) + if (bytes[39] & 0x3c) + continue; + + OPLPatch &patch = patches[key]; + // clear patch data + patch = OPLPatch(); + + // patch names + if (bytes[0]) + patch.name = std::string((const char*)bytes, 31); + else + patch.name = names[key & 0xff]; + + // patch global settings + patch.voice[0].tune = (int8_t)bytes[33] - 12; + patch.voice[1].tune = (int8_t)bytes[35] - 12; + patch.velocity = (int8_t)bytes[36]; + patch.voice[1].finetune = OPLPlayer::midiCalcBend((int8_t)bytes[37] / 64.0); + patch.fixedNote = bytes[38]; + patch.fourOp = (bytes[39] & 3) == 1; + patch.dualTwoOp = (bytes[39] & 3) == 3; + patch.voice[0].conn = bytes[40]; + patch.voice[1].conn = bytes[41]; + + // patch operator settings + unsigned pos = 42; + for (unsigned op = 0; op < 4; op++) + { + PatchVoice &voice = patch.voice[op/2]; + + const unsigned n = (op % 2) ^ 1; + + voice.op_mode[n] = bytes[pos++]; + voice.op_ksr[n] = bytes[pos] & 0xc0; + voice.op_level[n] = bytes[pos++] & 0x3f; + voice.op_ad[n] = bytes[pos++]; + voice.op_sr[n] = bytes[pos++]; + voice.op_wave[n] = bytes[pos++]; + } + } + + return true; +} + +// ---------------------------------------------------------------------------- +bool OPLPatch::loadOP2(OPLPatchSet& patches, const uint8_t *data, size_t size) +{ + if (size < 175 * (36 + 32) + 8) + return false; + + if (strncmp((const char*)data, "#OPL_II#", 8)) + return false; + + // read data for all patches (128 melodic + 47 percussion) + for (int i = 0; i < 128+47; i++) + { + // patches 0-127 are melodic; the rest are for percussion notes 35 thru 81 + unsigned key = (i < 128) ? i : (i + 35); + + OPLPatch &patch = patches[key]; + // clear patch data + patch = OPLPatch(); + + // seek to patch data + const uint8_t *bytes = data + (36*i) + 8; + + // read the common data for both 2op voices + // flag bit 0 is "fixed pitch" (for drums), but it's seemingly only used for drum patches anyway, so ignore it? + patch.dualTwoOp = (bytes[0] & 4); + // second voice detune + patch.voice[1].finetune = OPLPlayer::midiCalcBend((int8_t)(bytes[2] - 128) / 64.0); + + patch.fixedNote = bytes[3]; + + // read data for both 2op voices + unsigned pos = 4; + for (int j = 0; j < 2; j++) + { + PatchVoice &voice = patch.voice[j]; + + for (int op = 0; op < 2; op++) + { + // operator mode + voice.op_mode[op] = bytes[pos++]; + // operator envelope + voice.op_ad[op] = bytes[pos++]; + voice.op_sr[op] = bytes[pos++]; + // operator waveform + voice.op_wave[op] = bytes[pos++]; + // KSR & output level + voice.op_ksr[op] = bytes[pos++] & 0xc0; + voice.op_level[op] = bytes[pos++] & 0x3f; + + // feedback/connection (first op only) + if (op == 0) + voice.conn = bytes[pos]; + pos++; + } + + // midi note offset (int16, but only really need the LSB) + voice.tune = (int8_t)bytes[pos]; + pos += 2; + } + + // fix for some bugged DMX patches (e.g. Doom II electric snare) + if (!(patch.voice[1].op_ad[0] | patch.voice[1].op_ad[1])) + patch.dualTwoOp = false; + + // seek to patch name + bytes = data + (32*i) + (36*175) + 8; + if (bytes[0]) + patch.name = std::string((const char*)bytes, 31); + else + patch.name = names[key]; + } + + return true; +} + +// ---------------------------------------------------------------------------- +bool OPLPatch::loadAIL(OPLPatchSet& patches, const uint8_t *data, size_t size) +{ + size_t index = 0; + + while (true) + { + if (size < index * 6) + return false; + + const uint8_t *entry = data + (index * 6); + uint16_t key; + + if (entry[0] == 0xff && entry[1] == 0xff) + return true; // end of patches + else if (entry[1] == 0x7f) + key = entry[0] | 0x80; + else + key = (entry[0] | (entry[1] << 8)) & 0x7f7f; + + OPLPatch &patch = patches[key]; + // clear patch data + patch = OPLPatch(); + patch.name = names[key & 0xff]; + + uint32_t patchPos = entry[2] | (entry[3] << 8) | (entry[4] << 16) | (entry[5] << 24); + if (size < patchPos) + return false; + + const uint8_t *bytes = data + patchPos; + + if (size < patchPos + bytes[0]) + return false; + else if (bytes[0] == 0x0e) + patch.fourOp = false; + else if (bytes[0] == 0x19) + patch.fourOp = true; + else + return false; + index++; + + patch.voice[0].tune = patch.voice[1].tune = (int8_t)bytes[2] - 12; + patch.voice[0].conn = bytes[8] & 0x0f; + patch.voice[1].conn = bytes[8] >> 7; + + unsigned pos = 3; + for (int i = 0; i < (patch.fourOp ? 2 : 1); i++) + { + PatchVoice &voice = patch.voice[i]; + + for (int op = 0; op < 2; op++) + { + // operator mode + voice.op_mode[op] = bytes[pos++]; + // KSR & output level + voice.op_ksr[op] = bytes[pos] & 0xc0; + voice.op_level[op] = bytes[pos++] & 0x3f; + // operator envelope + voice.op_ad[op] = bytes[pos++]; + voice.op_sr[op] = bytes[pos++]; + // operator waveform + voice.op_wave[op] = bytes[pos++]; + + // already handled the feedback/connection byte + if (op == 0) + pos++; + } + } + } +} + +// ---------------------------------------------------------------------------- +bool OPLPatch::loadTMB(OPLPatchSet& patches, const uint8_t *data, size_t size) +{ + if (size < 256 * 13) + return false; + + for (uint16_t key = 0; key < 256; key++) + { + OPLPatch &patch = patches[key]; + // clear patch data + patch = OPLPatch(); + patch.name = names[key]; + + const uint8_t *bytes = data + (key * 13); + + // since this format has no identifying info, we can only really reject it + // if it has invalid values in a few spots + if ((bytes[8] | bytes[9] | bytes[10]) & 0xf0) + return false; + + PatchVoice &voice = patch.voice[0]; + voice.op_mode[0] = bytes[0]; + voice.op_mode[1] = bytes[1]; + voice.op_ksr[0] = bytes[2] & 0xc0; + voice.op_level[0] = bytes[2] & 0x3f; + voice.op_ksr[1] = bytes[3] & 0xc0; + voice.op_level[1] = bytes[3] & 0x3f; + voice.op_ad[0] = bytes[4]; + voice.op_ad[1] = bytes[5]; + voice.op_sr[0] = bytes[6]; + voice.op_sr[1] = bytes[7]; + voice.op_wave[0] = bytes[8]; + voice.op_wave[1] = bytes[9]; + voice.conn = bytes[10]; + voice.tune = (int8_t)bytes[11] - 12; + patch.velocity = (int8_t)bytes[12]; + } + + return true; +} \ No newline at end of file diff --git a/libraries/opalmidi/patches.h b/libraries/opalmidi/patches.h new file mode 100644 index 000000000..0128df6e0 --- /dev/null +++ b/libraries/opalmidi/patches.h @@ -0,0 +1,51 @@ +#ifndef __PATCHES_H +#define __PATCHES_H + +#include + +#include +#include +#include + +// one carrier/modulator pair in a patch, out of a possible two +struct PatchVoice +{ + uint8_t op_mode[2] = {0}; // regs 0x20+ + uint8_t op_ksr[2] = {0}; // regs 0x40+ (upper bits) + uint8_t op_level[2] = {0}; // regs 0x40+ (lower bits) + uint8_t op_ad[2] = {0}; // regs 0x60+ + uint8_t op_sr[2] = {0}; // regs 0x80+ + uint8_t conn = 0; // regs 0xC0+ + uint8_t op_wave[2] = {0}; // regs 0xE0+ + + int8_t tune = 0; // MIDI note offset + double finetune = 1.0; // frequency multiplier +}; + +typedef std::unordered_map OPLPatchSet; + +struct OPLPatch +{ + std::string name; + bool fourOp = false; // true 4op + bool dualTwoOp = false; // only valid if fourOp = false + uint8_t fixedNote = 0; + int8_t velocity = 0; // MIDI velocity offset + + PatchVoice voice[2]; + + // default names + static const char* names[256]; + + static bool load(OPLPatchSet& patches, const uint8_t *data, size_t size); + static void loadDefault(OPLPatchSet& patches); + +private: + // individual format loaders + static bool loadOP2(OPLPatchSet& patches, const uint8_t *data, size_t size); + static bool loadAIL(OPLPatchSet& patches, const uint8_t *data, size_t size); + static bool loadTMB(OPLPatchSet& patches, const uint8_t *data, size_t size); + static bool loadWOPL(OPLPatchSet& patches, const uint8_t *data, size_t size); +}; + +#endif // __PATCHES_H diff --git a/source_files/edge/CMakeLists.txt b/source_files/edge/CMakeLists.txt index ab3c013a1..f5cfde0fc 100644 --- a/source_files/edge/CMakeLists.txt +++ b/source_files/edge/CMakeLists.txt @@ -80,7 +80,7 @@ set (EDGE_SOURCE_FILES s_blit.cc s_cache.cc s_sound.cc - s_fluid.cc + s_midi.cc s_music.cc s_ogg.cc sv_chunk.cc @@ -219,8 +219,8 @@ if (EDGE_DEHACKED_SUPPORT) set (EDGE_LINK_LIBRARIES ${EDGE_LINK_LIBRARIES} edge_deh) endif() -if (EDGE_IMF_SUPPORT) - set (EDGE_LINK_LIBRARIES ${EDGE_LINK_LIBRARIES} opal) +if (EDGE_IMF_SUPPORT OR EDGE_OPL_SUPPORT) + set (EDGE_LINK_LIBRARIES ${EDGE_LINK_LIBRARIES} opalmidi) endif() if (EDGE_SID_SUPPORT) diff --git a/source_files/edge/i_sound.cc b/source_files/edge/i_sound.cc index d2dfb2d76..77e7a6e69 100644 --- a/source_files/edge/i_sound.cc +++ b/source_files/edge/i_sound.cc @@ -32,7 +32,7 @@ #include "miniaudio.h" #include "s_blit.h" #include "s_cache.h" -#include "s_fluid.h" +#include "s_midi.h" #include "s_sound.h" #include "w_wad.h" @@ -99,7 +99,12 @@ void StartupMusic(void) std::vector sfd; std::string soundfont_dir = epi::PathAppend(home_directory, "soundfont"); + // Add our built-in options first so they take precedence over a soundfont that might + // somehow have the same file stem available_soundfonts.emplace("Default"); +#ifdef EDGE_OPL_SUPPORT + available_soundfonts.emplace("OPL Emulation"); +#endif // Create home directory soundfont folder if it doesn't aleady exist if (!epi::IsDirectory(soundfont_dir)) @@ -180,8 +185,8 @@ void StartupMusic(void) } } - if (!StartupFluid()) - fluid_disabled = true; + if (!StartupMIDI()) + midi_disabled = true; return; } diff --git a/source_files/edge/m_option.cc b/source_files/edge/m_option.cc index 13960704e..deb742dd1 100644 --- a/source_files/edge/m_option.cc +++ b/source_files/edge/m_option.cc @@ -109,7 +109,7 @@ #include "r_wipe.h" #include "s_blit.h" #include "s_cache.h" -#include "s_fluid.h" +#include "s_midi.h" #include "s_music.h" #include "s_sound.h" #include "stb_sprintf.h" @@ -543,7 +543,7 @@ static OptionMenuItem soundoptions[] = { {kOptionMenuItemTypeSlider, "Movie/Music Volume", nullptr, 0, &music_volume.f_, OptionMenuUpdateConsoleVariableFromFloat, nullptr, &music_volume, 0.05f, 0.0f, 1.0f, "%0.2f"}, {kOptionMenuItemTypePlain, "", nullptr, 0, nullptr, nullptr, nullptr, nullptr, 0, 0, 0, ""}, - {kOptionMenuItemTypeFunction, "MIDI Instrument Bank", nullptr, 0, nullptr, OptionMenuChangeSoundfont, nullptr, + {kOptionMenuItemTypeFunction, "MIDI Instrument Set", nullptr, 0, nullptr, OptionMenuChangeSoundfont, nullptr, nullptr, 0, 0, 0, ""}, #if EDGE_DOOM_SFX_SUPPORT {kOptionMenuItemTypeBoolean, "PC Speaker Mode", YesNo, 2, &pc_speaker_mode, OptionMenuChangePCSpeakerMode, @@ -2080,7 +2080,7 @@ static void OptionMenuChangePCSpeakerMode(int key_pressed, ConsoleVariable *cons // Clear SFX cache and restart music StopAllSoundEffects(); SoundCacheClearAll(); - RestartFluid(); + RestartMIDI(); } #endif @@ -2167,7 +2167,7 @@ static void OptionMenuChangeSoundfont(int key_pressed, ConsoleVariable *console_ // update console_variable midi_soundfont = *sf_pos; - RestartFluid(); + RestartMIDI(); } // diff --git a/source_files/edge/s_fluid.h b/source_files/edge/s_fluid.h deleted file mode 100644 index 97e9b6635..000000000 --- a/source_files/edge/s_fluid.h +++ /dev/null @@ -1,32 +0,0 @@ -//---------------------------------------------------------------------------- -// EDGE Fluidlite Music Player -//---------------------------------------------------------------------------- -// -// Copyright (c) 2022-2024 The EDGE Team. -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 3 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -//---------------------------------------------------------------------------- - -#pragma once - -#include "s_music.h" - -extern bool fluid_disabled; - -bool StartupFluid(void); - -void RestartFluid(void); - -AbstractMusicPlayer *PlayFluidMusic(uint8_t *data, int length, bool loop); - -//--- editor settings --- -// vi:ts=4:sw=4:noexpandtab diff --git a/source_files/edge/s_imf.cc b/source_files/edge/s_imf.cc index 4020de021..77d3bd2e8 100644 --- a/source_files/edge/s_imf.cc +++ b/source_files/edge/s_imf.cc @@ -34,7 +34,7 @@ #define MidiFraction IMFFraction #define MidiSequencer IMFSequencer typedef struct MidiRealTimeInterface IMFInterface; -#include "s_midi.h" +#include "s_midi_seq.h" // clang-format on #include "s_music.h" #include "snd_types.h" @@ -131,9 +131,9 @@ static void rtRawOPL(void *userdata, uint8_t reg, uint8_t value) static void playSynth(void *userdata, uint8_t *stream, size_t length) { Opal *imf_opl = (Opal *)userdata; - size_t real_length = length / sizeof(float); + size_t real_length = length / sizeof(int16_t); for (size_t i = 0; i < real_length; i += 2) - imf_opl->SampleFloat((float *)stream + i, (float *)stream + i + 1); + imf_opl->Sample((int16_t *)stream + i, (int16_t *)stream + i + 1); } typedef struct @@ -171,7 +171,7 @@ static void IMFSequencerInit(ma_imf *synth) synth->imf_interface->onPcmRender_userdata = synth->imf_opl; synth->imf_interface->pcmSampleRate = sound_device_frequency; - synth->imf_interface->pcmFrameSize = 2 /*channels*/ * sizeof(float) /*size of one sample*/; + synth->imf_interface->pcmFrameSize = 2 /*channels*/ * sizeof(int16_t) /*size of one sample*/; synth->imf_interface->rt_deviceSwitch = rtDeviceSwitch; synth->imf_interface->rt_currentDevice = rtCurrentDevice; @@ -240,7 +240,7 @@ static ma_result ma_imf_init_internal(const ma_decoding_backend_config *pConfig, } EPI_CLEAR_MEMORY(pIMF, ma_imf, 1); - pIMF->format = ma_format_f32; /* Only supporting f32. */ + pIMF->format = ma_format_s16; dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_ma_imf_ds_vtable; @@ -368,10 +368,10 @@ static ma_result ma_imf_read_pcm_frames(ma_imf *pIMF, void *pFramesOut, ma_uint6 ma_imf_get_data_format(pIMF, &format, &channels, NULL, NULL, 0); - if (format == ma_format_f32) + if (format == ma_format_s16) { totalFramesRead = - pIMF->imf_sequencer->PlayStream((uint8_t *)pFramesOut, frameCount * 2 * sizeof(float)) / 2 / sizeof(float); + pIMF->imf_sequencer->PlayStream((uint8_t *)pFramesOut, frameCount * 2 * sizeof(int16_t)) / 2 / sizeof(int16_t); } else { @@ -608,7 +608,7 @@ class IMFPlayer : public AbstractMusicPlayer Close(); ma_decoder_config decode_config = ma_decoder_config_init_default(); - decode_config.format = ma_format_f32; + decode_config.format = ma_format_s16; decode_config.customBackendCount = 1; decode_config.ppCustomBackendVTables = &imf_custom_vtable; @@ -699,7 +699,7 @@ class IMFPlayer : public AbstractMusicPlayer void Ticker(void) { - ma_engine_set_volume(&music_engine, music_volume.f_ * 0.25f); + ma_engine_set_volume(&music_engine, music_volume.f_); if (status_ == kPlaying) { diff --git a/source_files/edge/s_m4p.cc b/source_files/edge/s_m4p.cc index f5a32860e..506981c2b 100644 --- a/source_files/edge/s_m4p.cc +++ b/source_files/edge/s_m4p.cc @@ -107,7 +107,7 @@ static ma_result ma_m4p_init_internal(const ma_decoding_backend_config *pConfig, } EPI_CLEAR_MEMORY(pM4P, ma_m4p, 1); - pM4P->format = ma_format_f32; /* Only supporting f32. */ + pM4P->format = ma_format_s16; dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_ma_m4p_ds_vtable; @@ -126,7 +126,7 @@ static ma_result ma_m4p_post_init(ma_m4p *pM4P) EPI_ASSERT(pM4P != NULL); pM4P->channels = 2; - pM4P->sampleRate = sound_device_frequency; + pM4P->sampleRate = HMM_MIN(64000, sound_device_frequency); m4p_PlaySong(); @@ -173,7 +173,7 @@ static ma_result ma_m4p_init_memory(const void *pData, size_t dataSize, const ma EPI_UNUSED(pAllocationCallbacks); - if (!m4p_LoadFromData((uint8_t *)pData, dataSize, sound_device_frequency, kMusicBuffer)) + if (!m4p_LoadFromData((uint8_t *)pData, dataSize, HMM_MIN(64000, sound_device_frequency), kMusicBuffer)) { LogWarning("M4P: failure to load song!\n"); return MA_INVALID_DATA; @@ -216,7 +216,6 @@ static ma_result ma_m4p_read_pcm_frames(ma_m4p *pM4P, void *pFramesOut, ma_uint6 return MA_INVALID_ARGS; } - /* We always use floating point format. */ ma_result result = MA_SUCCESS; /* Must be initialized to MA_SUCCESS. */ ma_uint64 totalFramesRead = 0; ma_format format; @@ -224,9 +223,9 @@ static ma_result ma_m4p_read_pcm_frames(ma_m4p *pM4P, void *pFramesOut, ma_uint6 ma_m4p_get_data_format(pM4P, &format, &channels, NULL, NULL, 0); - if (format == ma_format_f32) + if (format == ma_format_s16) { - m4p_GenerateFloatSamples((float *)pFramesOut, frameCount); + m4p_GenerateSamples((int16_t *)pFramesOut, frameCount); totalFramesRead = frameCount; } else @@ -486,7 +485,7 @@ bool M4PPlayer::OpenMemory(uint8_t *data, int length) Close(); ma_decoder_config decode_config = ma_decoder_config_init_default(); - decode_config.format = ma_format_f32; + decode_config.format = ma_format_s16; decode_config.customBackendCount = 1; decode_config.pCustomBackendUserData = NULL; decode_config.ppCustomBackendVTables = &custom_vtable; diff --git a/source_files/edge/s_fluid.cc b/source_files/edge/s_midi.cc similarity index 51% rename from source_files/edge/s_fluid.cc rename to source_files/edge/s_midi.cc index c85a85a4e..d87fee08f 100644 --- a/source_files/edge/s_fluid.cc +++ b/source_files/edge/s_midi.cc @@ -1,5 +1,5 @@ //---------------------------------------------------------------------------- -// EDGE Fluidlite Music Player +// EDGE MIDI Music Player //---------------------------------------------------------------------------- // // Copyright (c) 2022-2024 The EDGE Team. @@ -16,7 +16,7 @@ // //---------------------------------------------------------------------------- -#include "s_fluid.h" +#include "s_midi.h" #include @@ -34,27 +34,35 @@ #include "i_sound.h" #include "i_system.h" #include "m_misc.h" +#ifdef EDGE_OPL_SUPPORT +#include "opalmidi.h" +#endif #include "s_blit.h" // clang-format off -#define MidiFraction FluidFraction -#define MidiSequencer FluidSequencer -typedef struct MidiRealTimeInterface FluidInterface; -#include "s_midi.h" +#define MidiFraction MIDIFraction +#define MidiSequencer MIDISequencer +typedef struct MidiRealTimeInterface MIDIInterface; +#include "s_midi_seq.h" // clang-format on #include "s_music.h" #include "w_files.h" extern int sound_device_frequency; -bool fluid_disabled = false; +bool midi_disabled = false; fluid_synth_t *edge_fluid = nullptr; fluid_settings_t *edge_fluid_settings = nullptr; fluid_sfloader_t *edge_fluid_sf2_loader = nullptr; +#ifdef EDGE_OPL_SUPPORT +OPLPlayer *edge_opl = nullptr; +static bool opl_playback = false; +#endif + EDGE_DEFINE_CONSOLE_VARIABLE(midi_soundfont, "Default", kConsoleVariableFlagArchive) -EDGE_DEFINE_CONSOLE_VARIABLE(fluid_player_gain, "0.6", kConsoleVariableFlagArchive) +EDGE_DEFINE_CONSOLE_VARIABLE(fluidlite_gain, "0.6", kConsoleVariableFlagArchive) extern std::set available_soundfonts; @@ -180,49 +188,83 @@ static int edge_fluid_fseek(void *handle, long offset, int origin) void rtNoteOn(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity) { EPI_UNUSED(userdata); - fluid_synth_noteon(edge_fluid, channel, note, velocity); +#ifdef EDGE_OPL_SUPPORT + if (opl_playback) + edge_opl->midiNoteOn(channel, note, velocity); + else +#endif + fluid_synth_noteon(edge_fluid, channel, note, velocity); } void rtNoteOff(void *userdata, uint8_t channel, uint8_t note) { EPI_UNUSED(userdata); - fluid_synth_noteoff(edge_fluid, channel, note); +#ifdef EDGE_OPL_SUPPORT + if (opl_playback) + edge_opl->midiNoteOff(channel, note); + else +#endif + fluid_synth_noteoff(edge_fluid, channel, note); } void rtNoteAfterTouch(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal) { EPI_UNUSED(userdata); - fluid_synth_key_pressure(edge_fluid, channel, note, atVal); +#ifdef EDGE_OPL_SUPPORT + if (!opl_playback) +#endif + fluid_synth_key_pressure(edge_fluid, channel, note, atVal); } void rtChannelAfterTouch(void *userdata, uint8_t channel, uint8_t atVal) { EPI_UNUSED(userdata); - fluid_synth_channel_pressure(edge_fluid, channel, atVal); +#ifdef EDGE_OPL_SUPPORT + if (!opl_playback) +#endif + fluid_synth_channel_pressure(edge_fluid, channel, atVal); } void rtControllerChange(void *userdata, uint8_t channel, uint8_t type, uint8_t value) { EPI_UNUSED(userdata); - fluid_synth_cc(edge_fluid, channel, type, value); +#ifdef EDGE_OPL_SUPPORT + if (opl_playback) + edge_opl->midiControlChange(channel, type, value); + else +#endif + fluid_synth_cc(edge_fluid, channel, type, value); } void rtPatchChange(void *userdata, uint8_t channel, uint8_t patch) { EPI_UNUSED(userdata); - fluid_synth_program_change(edge_fluid, channel, patch); +#ifdef EDGE_OPL_SUPPORT + if (opl_playback) + edge_opl->midiProgramChange(channel, patch); + else +#endif + fluid_synth_program_change(edge_fluid, channel, patch); } void rtPitchBend(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb) { EPI_UNUSED(userdata); - fluid_synth_pitch_bend(edge_fluid, channel, (msb << 7) | lsb); +#ifdef EDGE_OPL_SUPPORT + if (opl_playback) + edge_opl->midiPitchControl(channel, (msb - 64) / 127.0); + else +#endif + fluid_synth_pitch_bend(edge_fluid, channel, (msb << 7) | lsb); } void rtSysEx(void *userdata, const uint8_t *msg, size_t size) { EPI_UNUSED(userdata); - fluid_synth_sysex(edge_fluid, (const char *)msg, (int)size, nullptr, nullptr, nullptr, 0); +#ifdef EDGE_OPL_SUPPORT + if (!opl_playback) +#endif + fluid_synth_sysex(edge_fluid, (const char *)msg, (int)size, nullptr, nullptr, nullptr, 0); } void rtDeviceSwitch(void *userdata, size_t track, const char *data, size_t length) @@ -243,7 +285,12 @@ size_t rtCurrentDevice(void *userdata, size_t track) void playSynth(void *userdata, uint8_t *stream, size_t length) { EPI_UNUSED(userdata); - fluid_synth_write_float(edge_fluid, (int)length / 2 / sizeof(float), stream, 0, 2, stream + sizeof(float), 0, 2); +#ifdef EDGE_OPL_SUPPORT + if (opl_playback) + edge_opl->generate((int16_t *)(stream), length / (2 * sizeof(int16_t))); + else +#endif + fluid_synth_write_float(edge_fluid, (int)length / 2 / sizeof(float), stream, 0, 2, stream + sizeof(float), 0, 2); } typedef struct @@ -258,104 +305,114 @@ typedef struct ma_uint32 channels; ma_uint32 sampleRate; ma_uint64 cursor; - FluidInterface *fluid_interface; - FluidSequencer *fluid_sequencer; -} ma_fluid; + MIDIInterface *midi_interface; + MIDISequencer *midi_sequencer; +} ma_midi; -static void FluidSequencerInit(ma_fluid *synth) +static void MIDISequencerInit(ma_midi *synth) { - EPI_CLEAR_MEMORY(synth->fluid_interface, FluidInterface, 1); - synth->fluid_interface->rtUserData = NULL; - synth->fluid_interface->rt_noteOn = rtNoteOn; - synth->fluid_interface->rt_noteOff = rtNoteOff; - synth->fluid_interface->rt_noteAfterTouch = rtNoteAfterTouch; - synth->fluid_interface->rt_channelAfterTouch = rtChannelAfterTouch; - synth->fluid_interface->rt_controllerChange = rtControllerChange; - synth->fluid_interface->rt_patchChange = rtPatchChange; - synth->fluid_interface->rt_pitchBend = rtPitchBend; - synth->fluid_interface->rt_systemExclusive = rtSysEx; - - synth->fluid_interface->onPcmRender = playSynth; - synth->fluid_interface->onPcmRender_userdata = NULL; - - synth->fluid_interface->pcmSampleRate = sound_device_frequency; - synth->fluid_interface->pcmFrameSize = 2 /*channels*/ * sizeof(float) /*size of one sample*/; - - synth->fluid_interface->rt_deviceSwitch = rtDeviceSwitch; - synth->fluid_interface->rt_currentDevice = rtCurrentDevice; - - synth->fluid_sequencer->SetInterface(synth->fluid_interface); + EPI_CLEAR_MEMORY(synth->midi_interface, MIDIInterface, 1); + synth->midi_interface->rtUserData = NULL; + synth->midi_interface->rt_noteOn = rtNoteOn; + synth->midi_interface->rt_noteOff = rtNoteOff; + synth->midi_interface->rt_noteAfterTouch = rtNoteAfterTouch; + synth->midi_interface->rt_channelAfterTouch = rtChannelAfterTouch; + synth->midi_interface->rt_controllerChange = rtControllerChange; + synth->midi_interface->rt_patchChange = rtPatchChange; + synth->midi_interface->rt_pitchBend = rtPitchBend; + synth->midi_interface->rt_systemExclusive = rtSysEx; + + synth->midi_interface->onPcmRender = playSynth; + synth->midi_interface->onPcmRender_userdata = NULL; + + synth->midi_interface->pcmSampleRate = sound_device_frequency; +#ifdef EDGE_OPL_SUPPORT + synth->midi_interface->pcmFrameSize = 2 /*channels*/ * (opl_playback ? sizeof(int16_t) : sizeof(float)); /*size of one sample*/; +#else + synth->midi_interface->pcmFrameSize = 2 /*channels*/ * sizeof(float); /*size of one sample*/; +#endif + + synth->midi_interface->rt_deviceSwitch = rtDeviceSwitch; + synth->midi_interface->rt_currentDevice = rtCurrentDevice; + + synth->midi_sequencer->SetInterface(synth->midi_interface); } -static ma_result ma_fluid_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, +static ma_result ma_midi_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData, const ma_decoding_backend_config *pConfig, - const ma_allocation_callbacks *pAllocationCallbacks, ma_fluid *pFluid); -static ma_result ma_fluid_init_memory(const void *pData, size_t dataSize, const ma_decoding_backend_config *pConfig, - const ma_allocation_callbacks *pAllocationCallbacks, ma_fluid *pFluid); -static void ma_fluid_uninit(ma_fluid *pFluid, const ma_allocation_callbacks *pAllocationCallbacks); -static ma_result ma_fluid_read_pcm_frames(ma_fluid *pFluid, void *pFramesOut, ma_uint64 frameCount, + const ma_allocation_callbacks *pAllocationCallbacks, ma_midi *pMIDI); +static ma_result ma_midi_init_memory(const void *pData, size_t dataSize, const ma_decoding_backend_config *pConfig, + const ma_allocation_callbacks *pAllocationCallbacks, ma_midi *pMIDI); +static void ma_midi_uninit(ma_midi *pMIDI, const ma_allocation_callbacks *pAllocationCallbacks); +static ma_result ma_midi_read_pcm_frames(ma_midi *pMIDI, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead); -static ma_result ma_fluid_seek_to_pcm_frame(ma_fluid *pFluid, ma_uint64 frameIndex); -static ma_result ma_fluid_get_data_format(ma_fluid *pFluid, ma_format *pFormat, ma_uint32 *pChannels, +static ma_result ma_midi_seek_to_pcm_frame(ma_midi *pMIDI, ma_uint64 frameIndex); +static ma_result ma_midi_get_data_format(ma_midi *pMIDI, ma_format *pFormat, ma_uint32 *pChannels, ma_uint32 *pSampleRate, ma_channel *pChannelMap, size_t channelMapCap); -static ma_result ma_fluid_get_cursor_in_pcm_frames(ma_fluid *pFluid, ma_uint64 *pCursor); -static ma_result ma_fluid_get_length_in_pcm_frames(ma_fluid *pFluid, ma_uint64 *pLength); +static ma_result ma_midi_get_cursor_in_pcm_frames(ma_midi *pMIDI, ma_uint64 *pCursor); +static ma_result ma_midi_get_length_in_pcm_frames(ma_midi *pMIDI, ma_uint64 *pLength); -static ma_result ma_fluid_ds_read(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, +static ma_result ma_midi_ds_read(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) { - return ma_fluid_read_pcm_frames((ma_fluid *)pDataSource, pFramesOut, frameCount, pFramesRead); + return ma_midi_read_pcm_frames((ma_midi *)pDataSource, pFramesOut, frameCount, pFramesRead); } -static ma_result ma_fluid_ds_seek(ma_data_source *pDataSource, ma_uint64 frameIndex) +static ma_result ma_midi_ds_seek(ma_data_source *pDataSource, ma_uint64 frameIndex) { - return ma_fluid_seek_to_pcm_frame((ma_fluid *)pDataSource, frameIndex); + return ma_midi_seek_to_pcm_frame((ma_midi *)pDataSource, frameIndex); } -static ma_result ma_fluid_ds_get_data_format(ma_data_source *pDataSource, ma_format *pFormat, ma_uint32 *pChannels, +static ma_result ma_midi_ds_get_data_format(ma_data_source *pDataSource, ma_format *pFormat, ma_uint32 *pChannels, ma_uint32 *pSampleRate, ma_channel *pChannelMap, size_t channelMapCap) { - return ma_fluid_get_data_format((ma_fluid *)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, + return ma_midi_get_data_format((ma_midi *)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } -static ma_result ma_fluid_ds_get_cursor(ma_data_source *pDataSource, ma_uint64 *pCursor) +static ma_result ma_midi_ds_get_cursor(ma_data_source *pDataSource, ma_uint64 *pCursor) { - return ma_fluid_get_cursor_in_pcm_frames((ma_fluid *)pDataSource, pCursor); + return ma_midi_get_cursor_in_pcm_frames((ma_midi *)pDataSource, pCursor); } -static ma_result ma_fluid_ds_get_length(ma_data_source *pDataSource, ma_uint64 *pLength) +static ma_result ma_midi_ds_get_length(ma_data_source *pDataSource, ma_uint64 *pLength) { - return ma_fluid_get_length_in_pcm_frames((ma_fluid *)pDataSource, pLength); + return ma_midi_get_length_in_pcm_frames((ma_midi *)pDataSource, pLength); } -static ma_data_source_vtable g_ma_fluid_ds_vtable = {ma_fluid_ds_read, - ma_fluid_ds_seek, - ma_fluid_ds_get_data_format, - ma_fluid_ds_get_cursor, - ma_fluid_ds_get_length, +static ma_data_source_vtable g_ma_midi_ds_vtable = {ma_midi_ds_read, + ma_midi_ds_seek, + ma_midi_ds_get_data_format, + ma_midi_ds_get_cursor, + ma_midi_ds_get_length, NULL, /* onSetLooping */ 0}; -static ma_result ma_fluid_init_internal(const ma_decoding_backend_config *pConfig, ma_fluid *pFluid) +static ma_result ma_midi_init_internal(const ma_decoding_backend_config *pConfig, ma_midi *pMIDI) { ma_result result; ma_data_source_config dataSourceConfig; EPI_UNUSED(pConfig); - if (pFluid == NULL) + if (pMIDI == NULL) { return MA_INVALID_ARGS; } - EPI_CLEAR_MEMORY(pFluid, ma_fluid, 1); - pFluid->format = ma_format_f32; /* Only supporting f32. */ + EPI_CLEAR_MEMORY(pMIDI, ma_midi, 1); + +#ifdef EDGE_OPL_SUPPORT + if (opl_playback) + pMIDI->format = ma_format_s16; + else +#endif + pMIDI->format = ma_format_f32; dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_fluid_ds_vtable; + dataSourceConfig.vtable = &g_ma_midi_ds_vtable; - result = ma_data_source_init(&dataSourceConfig, &pFluid->ds); + result = ma_data_source_init(&dataSourceConfig, &pMIDI->ds); if (result != MA_SUCCESS) { return result; /* Failed to initialize the base data source. */ @@ -364,28 +421,33 @@ static ma_result ma_fluid_init_internal(const ma_decoding_backend_config *pConfi return MA_SUCCESS; } -static ma_result ma_fluid_post_init(ma_fluid *pFluid) +static ma_result ma_midi_post_init(ma_midi *pMIDI) { - EPI_ASSERT(pFluid != NULL); + EPI_ASSERT(pMIDI != NULL); - pFluid->channels = 2; - pFluid->sampleRate = sound_device_frequency; + pMIDI->channels = 2; + pMIDI->sampleRate = sound_device_frequency; return MA_SUCCESS; } -static ma_result ma_fluid_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, +static ma_result ma_midi_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData, const ma_decoding_backend_config *pConfig, - const ma_allocation_callbacks *pAllocationCallbacks, ma_fluid *pFluid) + const ma_allocation_callbacks *pAllocationCallbacks, ma_midi *pMIDI) { - if (fluid_disabled || edge_fluid == NULL) + if (midi_disabled || edge_fluid == NULL) + return MA_ERROR; + +#ifdef EDGE_OPL_SUPPORT + if (edge_opl == NULL) return MA_ERROR; +#endif EPI_UNUSED(pAllocationCallbacks); ma_result result; - result = ma_fluid_init_internal(pConfig, pFluid); + result = ma_midi_init_internal(pConfig, pMIDI); if (result != MA_SUCCESS) { return result; @@ -396,20 +458,20 @@ static ma_result ma_fluid_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */ } - pFluid->onRead = onRead; - pFluid->onSeek = onSeek; - pFluid->onTell = onTell; - pFluid->pReadSeekTellUserData = pReadSeekTellUserData; + pMIDI->onRead = onRead; + pMIDI->onSeek = onSeek; + pMIDI->onTell = onTell; + pMIDI->pReadSeekTellUserData = pReadSeekTellUserData; return MA_SUCCESS; } -static ma_result ma_fluid_init_memory(const void *pData, size_t dataSize, const ma_decoding_backend_config *pConfig, - const ma_allocation_callbacks *pAllocationCallbacks, ma_fluid *pFluid) +static ma_result ma_midi_init_memory(const void *pData, size_t dataSize, const ma_decoding_backend_config *pConfig, + const ma_allocation_callbacks *pAllocationCallbacks, ma_midi *pMIDI) { ma_result result; - result = ma_fluid_init_internal(pConfig, pFluid); + result = ma_midi_init_internal(pConfig, pMIDI); if (result != MA_SUCCESS) { return result; @@ -417,17 +479,17 @@ static ma_result ma_fluid_init_memory(const void *pData, size_t dataSize, const EPI_UNUSED(pAllocationCallbacks); - pFluid->fluid_sequencer = new FluidSequencer; - pFluid->fluid_interface = new FluidInterface; + pMIDI->midi_sequencer = new MIDISequencer; + pMIDI->midi_interface = new MIDIInterface; - FluidSequencerInit(pFluid); + MIDISequencerInit(pMIDI); - if (!pFluid->fluid_sequencer->LoadMidi((const uint8_t *)pData, dataSize)) + if (!pMIDI->midi_sequencer->LoadMidi((const uint8_t *)pData, dataSize)) { return MA_INVALID_FILE; } - result = ma_fluid_post_init(pFluid); + result = ma_midi_post_init(pMIDI); if (result != MA_SUCCESS) { return result; @@ -436,24 +498,24 @@ static ma_result ma_fluid_init_memory(const void *pData, size_t dataSize, const return MA_SUCCESS; } -static void ma_fluid_uninit(ma_fluid *pFluid, const ma_allocation_callbacks *pAllocationCallbacks) +static void ma_midi_uninit(ma_midi *pMIDI, const ma_allocation_callbacks *pAllocationCallbacks) { EPI_UNUSED(pAllocationCallbacks); - if (pFluid == NULL) + if (pMIDI == NULL) { return; } - delete pFluid->fluid_interface; - pFluid->fluid_interface = NULL; - delete pFluid->fluid_sequencer; - pFluid->fluid_sequencer = NULL; + delete pMIDI->midi_interface; + pMIDI->midi_interface = NULL; + delete pMIDI->midi_sequencer; + pMIDI->midi_sequencer = NULL; - ma_data_source_uninit(&pFluid->ds); + ma_data_source_uninit(&pMIDI->ds); } -static ma_result ma_fluid_read_pcm_frames(ma_fluid *pFluid, void *pFramesOut, ma_uint64 frameCount, +static ma_result ma_midi_read_pcm_frames(ma_midi *pMIDI, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) { if (pFramesRead != NULL) @@ -466,7 +528,7 @@ static ma_result ma_fluid_read_pcm_frames(ma_fluid *pFluid, void *pFramesOut, ma return MA_INVALID_ARGS; } - if (pFluid == NULL || pFluid->fluid_sequencer == NULL) + if (pMIDI == NULL || pMIDI->midi_sequencer == NULL) { return MA_INVALID_ARGS; } @@ -477,26 +539,31 @@ static ma_result ma_fluid_read_pcm_frames(ma_fluid *pFluid, void *pFramesOut, ma ma_format format; ma_uint32 channels; - ma_fluid_get_data_format(pFluid, &format, &channels, NULL, NULL, 0); + ma_midi_get_data_format(pMIDI, &format, &channels, NULL, NULL, 0); if (format == ma_format_f32) { - totalFramesRead = pFluid->fluid_sequencer->PlayStream((uint8_t *)pFramesOut, frameCount * 2 * sizeof(float)) / + totalFramesRead = pMIDI->midi_sequencer->PlayStream((uint8_t *)pFramesOut, frameCount * 2 * sizeof(float)) / 2 / sizeof(float); } + else if (format == ma_format_s16) + { + totalFramesRead = pMIDI->midi_sequencer->PlayStream((uint8_t *)pFramesOut, frameCount * 2 * sizeof(int16_t)) / + 2 / sizeof(int16_t); + } else { result = MA_INVALID_ARGS; } - pFluid->cursor += totalFramesRead; + pMIDI->cursor += totalFramesRead; if (pFramesRead != NULL) { *pFramesRead = totalFramesRead; } - if (result == MA_SUCCESS && pFluid->fluid_sequencer->PositionAtEnd()) + if (result == MA_SUCCESS && pMIDI->midi_sequencer->PositionAtEnd()) { result = MA_AT_END; } @@ -504,21 +571,21 @@ static ma_result ma_fluid_read_pcm_frames(ma_fluid *pFluid, void *pFramesOut, ma return result; } -static ma_result ma_fluid_seek_to_pcm_frame(ma_fluid *pFluid, ma_uint64 frameIndex) +static ma_result ma_midi_seek_to_pcm_frame(ma_midi *pMIDI, ma_uint64 frameIndex) { - if (pFluid == NULL || frameIndex != 0 || pFluid->fluid_sequencer == NULL) + if (pMIDI == NULL || frameIndex != 0 || pMIDI->midi_sequencer == NULL) { return MA_INVALID_ARGS; } - pFluid->fluid_sequencer->Rewind(); + pMIDI->midi_sequencer->Rewind(); - pFluid->cursor = frameIndex; + pMIDI->cursor = frameIndex; return MA_SUCCESS; } -static ma_result ma_fluid_get_data_format(ma_fluid *pFluid, ma_format *pFormat, ma_uint32 *pChannels, +static ma_result ma_midi_get_data_format(ma_midi *pMIDI, ma_format *pFormat, ma_uint32 *pChannels, ma_uint32 *pSampleRate, ma_channel *pChannelMap, size_t channelMapCap) { /* Defaults for safety. */ @@ -539,35 +606,35 @@ static ma_result ma_fluid_get_data_format(ma_fluid *pFluid, ma_format *pFormat, EPI_CLEAR_MEMORY(pChannelMap, ma_channel, channelMapCap); } - if (pFluid == NULL) + if (pMIDI == NULL) { return MA_INVALID_OPERATION; } if (pFormat != NULL) { - *pFormat = pFluid->format; + *pFormat = pMIDI->format; } if (pChannels != NULL) { - *pChannels = pFluid->channels; + *pChannels = pMIDI->channels; } if (pSampleRate != NULL) { - *pSampleRate = pFluid->sampleRate; + *pSampleRate = pMIDI->sampleRate; } if (pChannelMap != NULL) { - ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pFluid->channels); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pMIDI->channels); } return MA_SUCCESS; } -static ma_result ma_fluid_get_cursor_in_pcm_frames(ma_fluid *pFluid, ma_uint64 *pCursor) +static ma_result ma_midi_get_cursor_in_pcm_frames(ma_midi *pMIDI, ma_uint64 *pCursor) { if (pCursor == NULL) { @@ -576,17 +643,17 @@ static ma_result ma_fluid_get_cursor_in_pcm_frames(ma_fluid *pFluid, ma_uint64 * *pCursor = 0; /* Safety. */ - if (pFluid == NULL) + if (pMIDI == NULL) { return MA_INVALID_ARGS; } - *pCursor = pFluid->cursor; + *pCursor = pMIDI->cursor; return MA_SUCCESS; } -static ma_result ma_fluid_get_length_in_pcm_frames(ma_fluid *pFluid, ma_uint64 *pLength) +static ma_result ma_midi_get_length_in_pcm_frames(ma_midi *pMIDI, ma_uint64 *pLength) { if (pLength == NULL) { @@ -595,7 +662,7 @@ static ma_result ma_fluid_get_length_in_pcm_frames(ma_fluid *pFluid, ma_uint64 * *pLength = 0; /* Safety. */ - if (pFluid == NULL) + if (pMIDI == NULL) { return MA_INVALID_ARGS; } @@ -603,160 +670,211 @@ static ma_result ma_fluid_get_length_in_pcm_frames(ma_fluid *pFluid, ma_uint64 * return MA_SUCCESS; } -static ma_result ma_decoding_backend_init__fluid(void *pUserData, ma_read_proc onRead, ma_seek_proc onSeek, +static ma_result ma_decoding_backend_init__midi(void *pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData, const ma_decoding_backend_config *pConfig, const ma_allocation_callbacks *pAllocationCallbacks, ma_data_source **ppBackend) { ma_result result; - ma_fluid *pFluid; + ma_midi *pMIDI; EPI_UNUSED(pUserData); /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ /* For now we're just allocating the decoder backend on the heap. */ - pFluid = (ma_fluid *)ma_malloc(sizeof(*pFluid), pAllocationCallbacks); - if (pFluid == NULL) + pMIDI = (ma_midi *)ma_malloc(sizeof(*pMIDI), pAllocationCallbacks); + if (pMIDI == NULL) { return MA_OUT_OF_MEMORY; } - result = ma_fluid_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pFluid); + result = ma_midi_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pMIDI); if (result != MA_SUCCESS) { - ma_free(pFluid, pAllocationCallbacks); + ma_free(pMIDI, pAllocationCallbacks); return result; } - *ppBackend = pFluid; + *ppBackend = pMIDI; return MA_SUCCESS; } -static ma_result ma_decoding_backend_init_memory__fluid(void *pUserData, const void *pData, size_t dataSize, +static ma_result ma_decoding_backend_init_memory__midi(void *pUserData, const void *pData, size_t dataSize, const ma_decoding_backend_config *pConfig, const ma_allocation_callbacks *pAllocationCallbacks, ma_data_source **ppBackend) { ma_result result; - ma_fluid *pFluid; + ma_midi *pMIDI; EPI_UNUSED(pUserData); /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ /* For now we're just allocating the decoder backend on the heap. */ - pFluid = (ma_fluid *)ma_malloc(sizeof(*pFluid), pAllocationCallbacks); - if (pFluid == NULL) + pMIDI = (ma_midi *)ma_malloc(sizeof(*pMIDI), pAllocationCallbacks); + if (pMIDI == NULL) { return MA_OUT_OF_MEMORY; } - result = ma_fluid_init_memory(pData, dataSize, pConfig, pAllocationCallbacks, pFluid); + result = ma_midi_init_memory(pData, dataSize, pConfig, pAllocationCallbacks, pMIDI); if (result != MA_SUCCESS) { - ma_free(pFluid, pAllocationCallbacks); + ma_free(pMIDI, pAllocationCallbacks); return result; } - *ppBackend = pFluid; + *ppBackend = pMIDI; return MA_SUCCESS; } -static void ma_decoding_backend_uninit__fluid(void *pUserData, ma_data_source *pBackend, +static void ma_decoding_backend_uninit__midi(void *pUserData, ma_data_source *pBackend, const ma_allocation_callbacks *pAllocationCallbacks) { - ma_fluid *pFluid = (ma_fluid *)pBackend; + ma_midi *pMIDI = (ma_midi *)pBackend; EPI_UNUSED(pUserData); - ma_fluid_uninit(pFluid, pAllocationCallbacks); - ma_free(pFluid, pAllocationCallbacks); + ma_midi_uninit(pMIDI, pAllocationCallbacks); + ma_free(pMIDI, pAllocationCallbacks); } -static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_fluid = {ma_decoding_backend_init__fluid, +static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_midi = {ma_decoding_backend_init__midi, NULL, // onInitFile() NULL, // onInitFileW() - ma_decoding_backend_init_memory__fluid, - ma_decoding_backend_uninit__fluid}; + ma_decoding_backend_init_memory__midi, + ma_decoding_backend_uninit__midi}; -static ma_decoding_backend_vtable *fluid_custom_vtable = &g_ma_decoding_backend_vtable_fluid; +static ma_decoding_backend_vtable *midi_custom_vtable = &g_ma_decoding_backend_vtable_midi; -bool StartupFluid(void) +bool StartupMIDI(void) { - LogPrint("Initializing Fluidlite...\n"); + LogPrint("Initializing MIDI...\n"); // Check for presence of previous CVAR value's file if (!available_soundfonts.count(midi_soundfont.s_)) { - LogWarning("Cannot find previously used soundfont %s, falling back to " + LogWarning("MIDI: Cannot find previously used soundfont %s, falling back to " "default!\n", midi_soundfont.c_str()); midi_soundfont = "Default"; } - // Initialize settings and change values from default if needed - fluid_set_log_function(FLUID_PANIC, FluidError, nullptr); - fluid_set_log_function(FLUID_ERR, nullptr, nullptr); - fluid_set_log_function(FLUID_WARN, nullptr, nullptr); - fluid_set_log_function(FLUID_DBG, nullptr, nullptr); - edge_fluid_settings = new_fluid_settings(); - fluid_settings_setstr(edge_fluid_settings, "synth.reverb.active", "no"); - fluid_settings_setstr(edge_fluid_settings, "synth.chorus.active", "no"); - fluid_settings_setnum(edge_fluid_settings, "synth.gain", fluid_player_gain.f_); - fluid_settings_setnum(edge_fluid_settings, "synth.sample-rate", sound_device_frequency); - fluid_settings_setnum(edge_fluid_settings, "synth.audio-channels", 2); - fluid_settings_setnum(edge_fluid_settings, "synth.polyphony", 64); + if (!edge_fluid_settings) + { + // Initialize settings and change values from default if needed + fluid_set_log_function(FLUID_PANIC, FluidError, nullptr); + fluid_set_log_function(FLUID_ERR, nullptr, nullptr); + fluid_set_log_function(FLUID_WARN, nullptr, nullptr); + fluid_set_log_function(FLUID_DBG, nullptr, nullptr); + edge_fluid_settings = new_fluid_settings(); + fluid_settings_setstr(edge_fluid_settings, "synth.reverb.active", "no"); + fluid_settings_setstr(edge_fluid_settings, "synth.chorus.active", "no"); + fluid_settings_setnum(edge_fluid_settings, "synth.gain", fluidlite_gain.f_); + fluid_settings_setnum(edge_fluid_settings, "synth.sample-rate", sound_device_frequency); + fluid_settings_setnum(edge_fluid_settings, "synth.audio-channels", 2); + fluid_settings_setnum(edge_fluid_settings, "synth.polyphony", 64); + } + edge_fluid = new_fluid_synth(edge_fluid_settings); // Register loader that uses our custom function to provide // a FILE pointer - edge_fluid_sf2_loader = new_fluid_defsfloader(); - edge_fluid_sf2_loader->fileapi = new fluid_fileapi_t; - fluid_init_default_fileapi(edge_fluid_sf2_loader->fileapi); - edge_fluid_sf2_loader->fileapi->fopen = edge_fluid_fopen; - edge_fluid_sf2_loader->fileapi->fclose = edge_fluid_fclose; - edge_fluid_sf2_loader->fileapi->ftell = edge_fluid_ftell; - edge_fluid_sf2_loader->fileapi->fseek = edge_fluid_fseek; - edge_fluid_sf2_loader->fileapi->fread = edge_fluid_fread; - edge_fluid_sf2_loader->fileapi->free= edge_fluid_free; + if (!edge_fluid_sf2_loader) + { + edge_fluid_sf2_loader = new_fluid_defsfloader(); + edge_fluid_sf2_loader->fileapi = new fluid_fileapi_t; + fluid_init_default_fileapi(edge_fluid_sf2_loader->fileapi); + edge_fluid_sf2_loader->fileapi->fopen = edge_fluid_fopen; + edge_fluid_sf2_loader->fileapi->fclose = edge_fluid_fclose; + edge_fluid_sf2_loader->fileapi->ftell = edge_fluid_ftell; + edge_fluid_sf2_loader->fileapi->fseek = edge_fluid_fseek; + edge_fluid_sf2_loader->fileapi->fread = edge_fluid_fread; + edge_fluid_sf2_loader->fileapi->free= edge_fluid_free; + } + fluid_synth_add_sfloader(edge_fluid, edge_fluid_sf2_loader); +#ifdef EDGE_OPL_SUPPORT + if (epi::StringCompare(midi_soundfont.s_, "OPL Emulation") != 0) + { + if (fluid_synth_sfload(edge_fluid, midi_soundfont.c_str(), 1) == -1) + { + LogWarning("MIDI: Initialization failure.\n"); + delete_fluid_synth(edge_fluid); + return false; + } + + fluid_synth_program_reset(edge_fluid); + } + + if (!edge_opl) + { + edge_opl = new OPLPlayer(sound_device_frequency); + + if (!edge_opl) + { + LogWarning("MIDI: Initialization failure.\n"); + delete_fluid_synth(edge_fluid); + return false; + } + + // Check for GENMIDI bank; this is not a failure if absent as OpalMIDI has + // built-in instruments + + int raw_length = 0; + uint8_t *raw_bank = OpenPackOrLumpInMemory("GENMIDI", {".wopl", ".op2", ".ad", ".opl", ".tmb"}, &raw_length); + if (raw_bank) + { + if (!edge_opl->loadPatches((const uint8_t *)raw_bank, (size_t)raw_length)) + { + LogWarning("MIDI: Error loading external OPL instruments! Falling back to default!\n"); + edge_opl->loadDefaultPatches(); + } + delete[] raw_bank; + } + else + edge_opl->loadDefaultPatches(); + } +#else if (fluid_synth_sfload(edge_fluid, midi_soundfont.c_str(), 1) == -1) { - LogWarning("FluidLite: Initialization failure.\n"); + LogWarning("MIDI: Initialization failure.\n"); delete_fluid_synth(edge_fluid); - delete_fluid_settings(edge_fluid_settings); return false; } fluid_synth_program_reset(edge_fluid); - +#endif return true; // OK! } // Should only be invoked when switching soundfonts -void RestartFluid(void) +void RestartMIDI(void) { - if (fluid_disabled) + if (midi_disabled) return; - LogPrint("Restarting Fluidlite...\n"); + LogPrint("Restarting MIDI...\n"); int old_entry = entry_playing; StopMusic(); - +#ifdef EDGE_OPL_SUPPORT + // We only delete the Fluidlite stuff, OPL instruments are determined once on startup, + // no need to reload; just reset it + edge_opl->reset(); +#endif delete_fluid_synth(edge_fluid); - delete_fluid_settings(edge_fluid_settings); edge_fluid = nullptr; - edge_fluid_settings = nullptr; edge_fluid_sf2_loader = nullptr; // This is already deleted upon invoking delete_fluid_synth - if (!StartupFluid()) + if (!StartupMIDI()) { - fluid_disabled = true; + midi_disabled = true; return; } @@ -766,7 +884,7 @@ void RestartFluid(void) return; // OK! } -class FluidPlayer : public AbstractMusicPlayer +class MIDIPlayer : public AbstractMusicPlayer { private: enum Status @@ -780,17 +898,17 @@ class FluidPlayer : public AbstractMusicPlayer int status_; bool looping_; - ma_decoder fluid_decoder_; - ma_sound fluid_stream_; + ma_decoder midi_decoder_; + ma_sound midi_stream_; public: - FluidPlayer(bool looping) : status_(kNotLoaded), looping_(looping) + MIDIPlayer(bool looping) : status_(kNotLoaded), looping_(looping) { - EPI_CLEAR_MEMORY(&fluid_decoder_, ma_decoder, 1); - EPI_CLEAR_MEMORY(&fluid_stream_, ma_sound, 1); + EPI_CLEAR_MEMORY(&midi_decoder_, ma_decoder, 1); + EPI_CLEAR_MEMORY(&midi_stream_, ma_sound, 1); } - ~FluidPlayer() + ~MIDIPlayer() { Close(); } @@ -800,25 +918,34 @@ class FluidPlayer : public AbstractMusicPlayer { if (status_ != kNotLoaded) Close(); +#ifdef EDGE_OPL_SUPPORT + opl_playback = (epi::StringCompare(midi_soundfont.s_, "OPL Emulation") == 0); + if (opl_playback) + edge_opl->reset(); +#endif ma_decoder_config decode_config = ma_decoder_config_init_default(); +#ifdef EDGE_OPL_SUPPORT + decode_config.format = opl_playback ? ma_format_s16 : ma_format_f32; +#else decode_config.format = ma_format_f32; +#endif decode_config.customBackendCount = 1; decode_config.pCustomBackendUserData = NULL; - decode_config.ppCustomBackendVTables = &fluid_custom_vtable; + decode_config.ppCustomBackendVTables = &midi_custom_vtable; - if (ma_decoder_init_memory(data, length, &decode_config, &fluid_decoder_) != MA_SUCCESS) + if (ma_decoder_init_memory(data, length, &decode_config, &midi_decoder_) != MA_SUCCESS) { LogWarning("Failed to load MIDI music\n"); return false; } - if (ma_sound_init_from_data_source(&music_engine, &fluid_decoder_, + if (ma_sound_init_from_data_source(&music_engine, &midi_decoder_, MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_STREAM | MA_SOUND_FLAG_UNKNOWN_LENGTH | MA_SOUND_FLAG_NO_SPATIALIZATION, - NULL, &fluid_stream_) != MA_SUCCESS) + NULL, &midi_stream_) != MA_SUCCESS) { - ma_decoder_uninit(&fluid_decoder_); + ma_decoder_uninit(&midi_decoder_); LogWarning("Failed to load MIDI music\n"); return false; } @@ -837,9 +964,9 @@ class FluidPlayer : public AbstractMusicPlayer // Stop playback Stop(); - ma_decoder_uninit(&fluid_decoder_); + ma_decoder_uninit(&midi_decoder_); - ma_sound_uninit(&fluid_stream_); + ma_sound_uninit(&midi_stream_); status_ = kNotLoaded; } @@ -848,7 +975,7 @@ class FluidPlayer : public AbstractMusicPlayer { looping_ = loop; - ma_sound_set_looping(&fluid_stream_, looping_ ? MA_TRUE : MA_FALSE); + ma_sound_set_looping(&midi_stream_, looping_ ? MA_TRUE : MA_FALSE); // Let 'er rip (maybe) if (playing_movie) @@ -856,7 +983,7 @@ class FluidPlayer : public AbstractMusicPlayer else { status_ = kPlaying; - ma_sound_start(&fluid_stream_); + ma_sound_start(&midi_stream_); } } @@ -865,9 +992,13 @@ class FluidPlayer : public AbstractMusicPlayer if (status_ != kPlaying && status_ != kPaused) return; - ma_sound_stop(&fluid_stream_); - - fluid_synth_all_voices_stop(edge_fluid); + ma_sound_stop(&midi_stream_); +#ifdef EDGE_OPL_SUPPORT + if (opl_playback) + edge_opl->reset(); + else +#endif + fluid_synth_all_voices_stop(edge_fluid); status_ = kStopped; } @@ -877,9 +1008,11 @@ class FluidPlayer : public AbstractMusicPlayer if (status_ != kPlaying) return; - ma_sound_stop(&fluid_stream_); - - fluid_synth_all_voices_pause(edge_fluid); + ma_sound_stop(&midi_stream_); +#ifdef EDGE_OPL_SUPPORT + if (!opl_playback) +#endif + fluid_synth_all_voices_pause(edge_fluid); status_ = kPaused; } @@ -889,45 +1022,49 @@ class FluidPlayer : public AbstractMusicPlayer if (status_ != kPaused) return; - ma_sound_start(&fluid_stream_); + ma_sound_start(&midi_stream_); status_ = kPlaying; } void Ticker(void) { +#ifdef EDGE_OPL_SUPPORT + ma_engine_set_volume(&music_engine, music_volume.f_ * (opl_playback ? 0.75f : 0.25f)); +#else ma_engine_set_volume(&music_engine, music_volume.f_ * 0.25f); +#endif - if (fluid_player_gain.CheckModified()) + if (fluidlite_gain.CheckModified()) { - fluid_player_gain.f_ = HMM_Clamp(0.0, fluid_player_gain.f_, 2.0f); - fluid_player_gain = fluid_player_gain.f_; - fluid_synth_set_gain(edge_fluid, fluid_player_gain.f_); + fluidlite_gain.f_ = HMM_Clamp(0.0, fluidlite_gain.f_, 2.0f); + fluidlite_gain = fluidlite_gain.f_; + fluid_synth_set_gain(edge_fluid, fluidlite_gain.f_); } if (status_ == kPlaying) { if (pc_speaker_mode) Stop(); - if (ma_sound_at_end(&fluid_stream_)) // This should only be true if finished and not set to looping + if (ma_sound_at_end(&midi_stream_)) // This should only be true if finished and not set to looping Stop(); } } }; -AbstractMusicPlayer *PlayFluidMusic(uint8_t *data, int length, bool loop) +AbstractMusicPlayer *PlayMIDIMusic(uint8_t *data, int length, bool loop) { - if (fluid_disabled) + if (midi_disabled) { delete[] data; return nullptr; } - FluidPlayer *player = new FluidPlayer(loop); + MIDIPlayer *player = new MIDIPlayer(loop); if (!player) { - LogDebug("Fluidlite player: error initializing!\n"); + LogDebug("MIDI player: error initializing!\n"); delete[] data; return nullptr; } @@ -935,7 +1072,7 @@ AbstractMusicPlayer *PlayFluidMusic(uint8_t *data, int length, bool loop) if (!player->OpenMemory(data, length)) // Lobo: quietly log it instead of completely exiting EDGE { - LogDebug("Fluidlite player: failed to load MIDI file!\n"); + LogDebug("MIDI player: failed to load MIDI file!\n"); delete[] data; delete player; return nullptr; diff --git a/source_files/edge/s_midi.h b/source_files/edge/s_midi.h index fc4498378..deef17358 100644 --- a/source_files/edge/s_midi.h +++ b/source_files/edge/s_midi.h @@ -1,5521 +1,32 @@ -/* - * BW_Midi_Sequencer - MIDI Sequencer for C++ - * - * Copyright (c) 2015-2022 Vitaly Novichkov - * Copyright (c) 2024 The EDGE Team. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE - * OR OTHER DEALINGS IN THE SOFTWARE. - */ +//---------------------------------------------------------------------------- +// EDGE MIDI Music Player +//---------------------------------------------------------------------------- +// +// Copyright (c) 2022-2024 The EDGE Team. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//---------------------------------------------------------------------------- #pragma once -#include -#include -#include -#include +#include "s_music.h" -#include // std::copy -#include // std::back_inserter -#include -#include -#include +extern bool midi_disabled; -#include "epi.h" -#include "stb_sprintf.h" +bool StartupMIDI(void); -#if EDGE_MUS_SUPPORT -static constexpr uint8_t kMusFrequency = 140; -static constexpr int kMusTempo = 0x00068A1B; /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ - /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ -static constexpr uint16_t kMusDivision = 0x0101; /* 257 for 140Hz files with a 140MPQN */ - /* 0x0088 -> 136 for 70Hz files with a 140MPQN */ - /* 0x010B -> 267 for 70hz files with a 70MPQN */ - /* 0x01F9 -> 505 for 140hz files with a 70MPQN */ +void RestartMIDI(void); -/* New - * QLS: MPQN/1000000 = 0.428571 - * TDPS: QLS/PPQN = 0.428571/136 = 0.003151257 - * PPQN: 136 - * - * QLS: MPQN/1000000 = 0.428571 - * TDPS: QLS/PPQN = 0.428571/257 = 0.001667591 - * PPQN: 257 - * - * QLS: MPQN/1000000 = 0.857142 - * TDPS: QLS/PPQN = 0.857142/267 = 0.00321027 - * PPQN: 267 - * - * QLS: MPQN/1000000 = 0.857142 - * TDPS: QLS/PPQN = 0.857142/505 = 0.001697311 - * PPQN: 505 - * - * Old - * QLS: MPQN/1000000 = 1.745673 - * TDPS: QLS/PPQN = 1.745673 / 89 = 0.019614303 (seconds per tick) - * PPQN: (TDPS = QLS/PPQN) (0.019614303 = 1.745673/PPQN) (0.019614303*PPQN - * = 1.745673) (PPQN = 89.000001682) - * - */ +AbstractMusicPlayer *PlayMIDIMusic(uint8_t *data, int length, bool loop); -enum MusEvent -{ - kMusEventKeyOff = 0, - kMusEventKeyOn = 1, - kMusEventPitchWheel = 2, - kMusEventChannelMode = 3, - kMusEventControllerChange = 4, - kMusEventEnd = 6, -}; - -static constexpr uint8_t kMusMidiMaxChannels = 16; - -static constexpr char kMusHeader[] = {'M', 'U', 'S', 0x1A}; - -static constexpr uint8_t kMusToMidiMap[] = { - /* MIDI Number Description */ - 0, /* 0 program change */ - 0, /* 1 bank selection */ - 0x01, /* 2 Modulation pot (frequency vibrato depth) */ - 0x07, /* 3 Volume: 0-silent, ~100-normal, 127-loud */ - 0x0A, /* 4 Pan (balance) pot: 0-left, 64-center (default), 127-right */ - 0x0B, /* 5 Expression pot */ - 0x5B, /* 6 Reverb depth */ - 0x5D, /* 7 Chorus depth */ - 0x40, /* 8 Sustain pedal */ - 0x43, /* 9 Soft pedal */ - 0x78, /* 10 All sounds off */ - 0x7B, /* 11 All notes off */ - 0x7E, /* 12 Mono (use numchannels + 1) */ - 0x7F, /* 13 Poly */ - 0x79, /* 14 reset all controllers */ -}; - -struct MusHeader -{ - char ID[4]; /* identifier: "MUS" 0x1A */ - uint16_t scoreLen; - uint16_t scoreStart; - uint16_t channels; /* count of primary channels */ - uint16_t sec_channels; /* count of secondary channels */ - uint16_t instrCnt; -}; - -struct MidiHeaderChunk -{ - char name[4]; - int32_t length; - int16_t format; /* make 0 */ - int16_t ntracks; /* make 1 */ - int16_t division; /* 0xe250 ?? */ -}; - -struct MidiTrackChunk -{ - char name[4]; - int32_t length; -}; - -struct MusConversionContext -{ - uint8_t *src, *src_ptr; - uint32_t srcsize; - uint32_t datastart; - uint8_t *dst, *dst_ptr; - uint32_t dstsize, dstrem; -}; - -static constexpr uint16_t kMusDestinationChunkSize = 8192; -static void MusToMidiResizeDestination(struct MusConversionContext *ctx) -{ - uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + kMusDestinationChunkSize); - ctx->dstsize += kMusDestinationChunkSize; - ctx->dstrem += kMusDestinationChunkSize; - ctx->dst_ptr = ctx->dst + pos; -} - -static void MusToMidiWrite1(struct MusConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 1) - MusToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem--; -} - -static void MusToMidiWrite2(struct MusConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 2) - MusToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = (val >> 8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 2; -} - -static void MusToMidiWrite4(struct MusConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 4) - MusToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = (uint8_t)((val >> 24) & 0xff); - *ctx->dst_ptr++ = (uint8_t)((val >> 16) & 0xff); - *ctx->dst_ptr++ = (uint8_t)((val >> 8) & 0xff); - *ctx->dst_ptr++ = (uint8_t)((val & 0xff)); - ctx->dstrem -= 4; -} - -static void MusToMidiSeekDestination(struct MusConversionContext *ctx, uint32_t pos) -{ - ctx->dst_ptr = ctx->dst + pos; - while (ctx->dstsize < pos) - MusToMidiResizeDestination(ctx); - ctx->dstrem = ctx->dstsize - pos; -} - -static void MusToMidiSkipDestination(struct MusConversionContext *ctx, int32_t pos) -{ - size_t newpos; - ctx->dst_ptr += pos; - newpos = ctx->dst_ptr - ctx->dst; - while (ctx->dstsize < newpos) - MusToMidiResizeDestination(ctx); - ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); -} - -static uint32_t MusToMidiGetDestinationPosition(struct MusConversionContext *ctx) -{ - return (uint32_t)(ctx->dst_ptr - ctx->dst); -} - -/* writes a variable length integer to a buffer, and returns bytes written */ -static int32_t MusToMidiWriteVariableLength(int32_t value, uint8_t *out) -{ - int32_t buffer, count = 0; - - buffer = value & 0x7f; - while ((value >>= 7) > 0) - { - buffer <<= 8; - buffer += 0x80; - buffer += (value & 0x7f); - } - - while (1) - { - ++count; - *out = (uint8_t)buffer; - ++out; - if (buffer & 0x80) - buffer >>= 8; - else - break; - } - return (count); -} - -#define EDGE_MUS_READ_SHORT(b) ((b)[0] | ((b)[1] << 8)) -#define EDGE_MUS_READ_INT(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24)) - -static int ConvertMusToMidi(uint8_t *in, uint32_t insize, uint8_t **out, uint32_t *outsize, uint16_t frequency) -{ - struct MusConversionContext ctx; - MusHeader header; - uint8_t *cur, *end; - uint32_t track_size_pos, begin_track_pos, current_pos; - int32_t delta_time; /* Delta time for midi event */ - int temp, ret = -1; - int channel_volume[kMusMidiMaxChannels]; - int channelMap[kMusMidiMaxChannels], currentChannel; - - if (insize < sizeof(MusHeader)) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", - * 0);*/ - return (-1); - } - - if (!frequency) - frequency = kMusFrequency; - - /* read the MUS header and set our location */ - memcpy(header.ID, in, 4); - header.scoreLen = EDGE_MUS_READ_SHORT(&in[4]); - header.scoreStart = EDGE_MUS_READ_SHORT(&in[6]); - header.channels = EDGE_MUS_READ_SHORT(&in[8]); - header.sec_channels = EDGE_MUS_READ_SHORT(&in[10]); - header.instrCnt = EDGE_MUS_READ_SHORT(&in[12]); - - if (memcmp(header.ID, kMusHeader, 4)) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, nullptr, - * 0);*/ - return (-1); - } - if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", - * 0);*/ - return (-1); - } - /* channel #15 should be excluded in the numchannels field: */ - if (header.channels > kMusMidiMaxChannels - 1) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, nullptr, - * 0);*/ - return (-1); - } - - EPI_CLEAR_MEMORY(&ctx, MusConversionContext, 1); - ctx.src = ctx.src_ptr = in; - ctx.srcsize = insize; - - ctx.dst = (uint8_t *)calloc(kMusDestinationChunkSize, sizeof(uint8_t)); - ctx.dst_ptr = ctx.dst; - ctx.dstsize = kMusDestinationChunkSize; - ctx.dstrem = kMusDestinationChunkSize; - - /* Map channel 15 to 9 (percussions) */ - for (temp = 0; temp < kMusMidiMaxChannels; ++temp) - { - channelMap[temp] = -1; - channel_volume[temp] = 0x40; - } - channelMap[15] = 9; - - /* Header is 14 bytes long and add the rest as well */ - MusToMidiWrite1(&ctx, 'M'); - MusToMidiWrite1(&ctx, 'T'); - MusToMidiWrite1(&ctx, 'h'); - MusToMidiWrite1(&ctx, 'd'); - MusToMidiWrite4(&ctx, 6); /* length of header */ - MusToMidiWrite2(&ctx, 0); /* MIDI type (always 0) */ - MusToMidiWrite2(&ctx, 1); /* MUS files only have 1 track */ - MusToMidiWrite2(&ctx, kMusDivision); /* division */ - - /* Write out track header and track length position for later */ - begin_track_pos = MusToMidiGetDestinationPosition(&ctx); - MusToMidiWrite1(&ctx, 'M'); - MusToMidiWrite1(&ctx, 'T'); - MusToMidiWrite1(&ctx, 'r'); - MusToMidiWrite1(&ctx, 'k'); - track_size_pos = MusToMidiGetDestinationPosition(&ctx); - MusToMidiSkipDestination(&ctx, 4); - - /* write tempo: microseconds per quarter note */ - MusToMidiWrite1(&ctx, 0x00); /* delta time */ - MusToMidiWrite1(&ctx, 0xff); /* sys command */ - MusToMidiWrite2(&ctx, 0x5103); /* command - set tempo */ - MusToMidiWrite1(&ctx, kMusTempo & 0x000000ff); - MusToMidiWrite1(&ctx, (kMusTempo & 0x0000ff00) >> 8); - MusToMidiWrite1(&ctx, (kMusTempo & 0x00ff0000) >> 16); - - /* Percussions channel starts out at volume 100 */ - MusToMidiWrite1(&ctx, 0x00); - MusToMidiWrite1(&ctx, 0xB9); - MusToMidiWrite1(&ctx, 0x07); - MusToMidiWrite1(&ctx, 100); - - /* get current position in source, and end of position */ - cur = in + header.scoreStart; - end = cur + header.scoreLen; - - currentChannel = 0; - delta_time = 0; - - /* main loop */ - while (cur < end) - { - /*printf("LOOP DEBUG: %d\r\n",iterator++);*/ - uint8_t channel; - uint8_t event; - uint8_t temp_buffer[32]; /* temp buffer for current iterator */ - uint8_t *out_local = temp_buffer; - uint8_t status, bit1, bit2, bitc = 2; - - /* read in current bit */ - event = *cur++; - channel = (event & 15); /* current channel */ - - /* write variable length delta time */ - out_local += MusToMidiWriteVariableLength(delta_time, out_local); - - /* set all channels to 127 (max) volume */ - if (channelMap[channel] < 0) - { - *out_local++ = 0xB0 + currentChannel; - *out_local++ = 0x07; - *out_local++ = 100; - *out_local++ = 0x00; - channelMap[channel] = currentChannel++; - if (currentChannel == 9) - ++currentChannel; - } - status = channelMap[channel]; - - /* handle events */ - switch ((event & 122) >> 4) - { - case kMusEventKeyOff: - status |= 0x80; - bit1 = *cur++; - bit2 = 0x40; - break; - case kMusEventKeyOn: - status |= 0x90; - bit1 = *cur & 127; - if (*cur++ & 128) /* volume bit? */ - channel_volume[channelMap[channel]] = *cur++; - bit2 = channel_volume[channelMap[channel]]; - break; - case kMusEventPitchWheel: - status |= 0xE0; - bit1 = (*cur & 1) >> 6; - bit2 = (*cur++ >> 1) & 127; - break; - case kMusEventChannelMode: - status |= 0xB0; - if (*cur >= sizeof(kMusToMidiMap) / sizeof(kMusToMidiMap[0])) - { - /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", - __FUNCTION__, __LINE__, *cur);*/ - goto _end; - } - bit1 = kMusToMidiMap[*cur++]; - bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; - break; - case kMusEventControllerChange: - if (*cur == 0) - { - cur++; - status |= 0xC0; - bit1 = *cur++; - bit2 = 0; /* silence bogus warnings */ - bitc = 1; - } - else - { - status |= 0xB0; - if (*cur >= sizeof(kMusToMidiMap) / sizeof(kMusToMidiMap[0])) - { - /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", - __FUNCTION__, __LINE__, *cur);*/ - goto _end; - } - bit1 = kMusToMidiMap[*cur++]; - bit2 = *cur++; - } - break; - case kMusEventEnd: /* End */ - status = 0xff; - bit1 = 0x2f; - bit2 = 0x00; - if (cur != end) - { /* should we error here or report-only? */ - /*_WM_DEBUG_MSG("%s:%i: MUS buffer off by %ld bytes", - __FUNCTION__, __LINE__, (long)(cur - end));*/ - } - break; - case 5: /* Unknown */ - case 7: /* Unknown */ - default: /* shouldn't happen */ - /*_WM_ERROR_NEW("%s:%i: unrecognized event (%u)", - __FUNCTION__, __LINE__, event);*/ - goto _end; - } - - /* write it out */ - *out_local++ = status; - *out_local++ = bit1; - if (bitc == 2) - *out_local++ = bit2; - - /* write out our temp buffer */ - if (out_local != temp_buffer) - { - if (ctx.dstrem < sizeof(temp_buffer)) - MusToMidiResizeDestination(&ctx); - - memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer); - ctx.dst_ptr += out_local - temp_buffer; - ctx.dstrem -= (uint32_t)(out_local - temp_buffer); - } - - if (event & 128) - { - delta_time = 0; - do - { - delta_time = (int32_t)((delta_time * 128 + (*cur & 127)) * (140.0 / (double)frequency)); - } while ((*cur++ & 128)); - } - else - { - delta_time = 0; - } - } - - /* write out track length */ - current_pos = MusToMidiGetDestinationPosition(&ctx); - MusToMidiSeekDestination(&ctx, track_size_pos); - MusToMidiWrite4(&ctx, current_pos - begin_track_pos - sizeof(MidiTrackChunk)); - MusToMidiSeekDestination(&ctx, current_pos); /* reseek to end position */ - - *out = ctx.dst; - *outsize = ctx.dstsize - ctx.dstrem; - ret = 0; - -_end: /* cleanup */ - if (ret < 0) - { - free(ctx.dst); - *out = nullptr; - *outsize = 0; - } - - return (ret); -} -#endif - -#if EDGE_XMI_SUPPORT -enum XMIConversionType -{ - kXMINoConversion = 0x00, - kXMIConvertMt32ToGm = 0x01, - kXMIConvertMt32ToGs = 0x02, - kXMIConvertMt32ToGs127 = 0x03, - kXMIConvertMt32ToGs127Drum = 0x04, - kXMIConvertGs127ToGs = 0x05 -}; - -enum XMIStatusByte -{ - kXMIStatusNoteOff = 0x8, - kXMIStatusNoteOn = 0x9, - kXMIStatusAftertouch = 0xA, - kXMIStatusController = 0xB, - kXMIStatusProgramChange = 0xC, - kXMIStatusPressure = 0xD, - kXMIStatusPitchWheel = 0xE, - kXMIStatusSysex = 0xF -}; - -struct XMIToMidiEvent -{ - int32_t time; - uint8_t status; - uint8_t data[2]; - uint32_t len; - uint8_t *buffer; - XMIToMidiEvent *next; -}; - -struct MidiDescriptor -{ - uint16_t type; - uint16_t tracks; -}; - -struct XMIToMidiConversionContext -{ - uint8_t *src, *src_ptr, *src_end; - uint32_t srcsize; - uint32_t datastart; - uint8_t *dst, *dst_ptr; - uint32_t dstsize, dstrem; - uint32_t convert_type; - MidiDescriptor info; - int bank127[16]; - XMIToMidiEvent **events; - int16_t *timing; - XMIToMidiEvent *list; - XMIToMidiEvent *current; - std::vector> *dyn_out; - std::vector *dyn_out_cur; -}; - -struct XMIToMidiBranch -{ - unsigned count; - uint8_t id[128]; - uint32_t offset[128]; -}; - -/* forward declarations of private functions */ -static void XMIToMidiDeleteEventList(XMIToMidiEvent *mlist); -static void XMIToMidiCreateNewEvent(struct XMIToMidiConversionContext *ctx, int32_t time); /* List manipulation */ -static int XMIToMidiGetVlq(struct XMIToMidiConversionContext *ctx, uint32_t *quant); /* Variable length quantity */ -static int XMIToMidiGetVlq2(struct XMIToMidiConversionContext *ctx, uint32_t *quant); /* Variable length quantity */ -static int XMIToMidiPutVlq(struct XMIToMidiConversionContext *ctx, uint32_t value); /* Variable length quantity */ -static int XMIToMidiConvertEvent(struct XMIToMidiConversionContext *ctx, const int32_t time, const uint8_t status, - const int size); -static int32_t XMIToMidiConvertSystemMessage(struct XMIToMidiConversionContext *ctx, const int32_t time, - const uint8_t status); -static int32_t XMIToMidiConvertFiletoList(struct XMIToMidiConversionContext *ctx, const XMIToMidiBranch *rbrn); -static uint32_t XMIToMidiConvertListToMidiTrack(struct XMIToMidiConversionContext *ctx, XMIToMidiEvent *mlist); -static int XMIToMidiParseXMI(struct XMIToMidiConversionContext *ctx); -static int XMIToMidiExtractTracks(struct XMIToMidiConversionContext *ctx, int32_t dstTrackNumber); -static uint32_t XMIToMidiExtractTracksFromXMI(struct XMIToMidiConversionContext *ctx); - -static uint32_t XMIToMidiRead1(struct XMIToMidiConversionContext *ctx) -{ - uint8_t b0; - EPI_ASSERT(ctx->src_ptr + 1 < ctx->src_end); - b0 = *ctx->src_ptr++; - return (b0); -} - -static uint32_t XMIToMidiRead2(struct XMIToMidiConversionContext *ctx) -{ - uint8_t b0, b1; - EPI_ASSERT(ctx->src_ptr + 2 < ctx->src_end); - b0 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - return (b0 + ((uint32_t)b1 << 8)); -} - -static uint32_t XMIToMidiRead4(struct XMIToMidiConversionContext *ctx) -{ - uint8_t b0, b1, b2, b3; - EPI_ASSERT(ctx->src_ptr + 4 < ctx->src_end); - b3 = *ctx->src_ptr++; - b2 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - b0 = *ctx->src_ptr++; - return (b0 + ((uint32_t)b1 << 8) + ((uint32_t)b2 << 16) + ((uint32_t)b3 << 24)); -} - -static uint32_t XMIToMidiRead4LittleEndian(struct XMIToMidiConversionContext *ctx) -{ - uint8_t b0, b1, b2, b3; - EPI_ASSERT(ctx->src_ptr + 4 < ctx->src_end); - b3 = *ctx->src_ptr++; - b2 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - b0 = *ctx->src_ptr++; - return (b3 + ((uint32_t)b2 << 8) + ((uint32_t)b1 << 16) + ((uint32_t)b0 << 24)); -} - -static void XMIToMidiCopy(struct XMIToMidiConversionContext *ctx, char *b, uint32_t len) -{ - EPI_ASSERT(ctx->src_ptr + len < ctx->src_end); - memcpy(b, ctx->src_ptr, len); - ctx->src_ptr += len; -} - -static constexpr uint16_t kDestinationChunkSize = 8192; -static void XMIToMidiResizeDestination(struct XMIToMidiConversionContext *ctx) -{ - uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - if (ctx->dyn_out && ctx->dyn_out_cur) - { - ctx->dyn_out_cur->resize(ctx->dstsize + kDestinationChunkSize); - ctx->dst = ctx->dyn_out_cur->data(); - } - else - ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + kDestinationChunkSize); - ctx->dstsize += kDestinationChunkSize; - ctx->dstrem += kDestinationChunkSize; - ctx->dst_ptr = ctx->dst + pos; -} - -static void XMIToMidiWrite1(struct XMIToMidiConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 1) - XMIToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem--; -} - -static void XMIToMidiWrite2(struct XMIToMidiConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 2) - XMIToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = (val >> 8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 2; -} - -static void XMIToMidiWrite4(struct XMIToMidiConversionContext *ctx, uint32_t val) -{ - if (ctx->dstrem < 4) - XMIToMidiResizeDestination(ctx); - *ctx->dst_ptr++ = (val >> 24) & 0xff; - *ctx->dst_ptr++ = (val >> 16) & 0xff; - *ctx->dst_ptr++ = (val >> 8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 4; -} - -static void XMIToMidiSeekSource(struct XMIToMidiConversionContext *ctx, uint32_t pos) -{ - ctx->src_ptr = ctx->src + pos; -} - -static void XMIToMidiSeekDestination(struct XMIToMidiConversionContext *ctx, uint32_t pos) -{ - ctx->dst_ptr = ctx->dst + pos; - while (ctx->dstsize < pos) - XMIToMidiResizeDestination(ctx); - ctx->dstrem = ctx->dstsize - pos; -} - -static void XMIToMidiSkipSource(struct XMIToMidiConversionContext *ctx, int32_t pos) -{ - ctx->src_ptr += pos; -} - -static void XMIToMidiSkipDestination(struct XMIToMidiConversionContext *ctx, int32_t pos) -{ - size_t newpos; - ctx->dst_ptr += pos; - newpos = ctx->dst_ptr - ctx->dst; - while (ctx->dstsize < newpos) - XMIToMidiResizeDestination(ctx); - ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); -} - -static uint32_t XMIToMidiGetSourceSize(struct XMIToMidiConversionContext *ctx) -{ - return (ctx->srcsize); -} - -static uint32_t XMIToMidiGetSourcePosition(struct XMIToMidiConversionContext *ctx) -{ - return (uint32_t)(ctx->src_ptr - ctx->src); -} - -static uint32_t XMIToMidiGetDestinationPosition(struct XMIToMidiConversionContext *ctx) -{ - return (uint32_t)(ctx->dst_ptr - ctx->dst); -} - -/* This is a default set of patches to convert from MT32 to GM - * The index is the MT32 Patch number and the value is the GM Patch - * This is only suitable for music that doesn't do timbre changes - * XMIDIs that contain Timbre changes will not convert properly. - */ -static constexpr char Mt32ToGmMap[128] = { - 0, /* 0 Piano 1 */ - 1, /* 1 Piano 2 */ - 2, /* 2 Piano 3 (synth) */ - 4, /* 3 EPiano 1 */ - 4, /* 4 EPiano 2 */ - 5, /* 5 EPiano 3 */ - 5, /* 6 EPiano 4 */ - 3, /* 7 Honkytonk */ - 16, /* 8 Organ 1 */ - 17, /* 9 Organ 2 */ - 18, /* 10 Organ 3 */ - 16, /* 11 Organ 4 */ - 19, /* 12 Pipe Organ 1 */ - 19, /* 13 Pipe Organ 2 */ - 19, /* 14 Pipe Organ 3 */ - 21, /* 15 Accordion */ - 6, /* 16 Harpsichord 1 */ - 6, /* 17 Harpsichord 2 */ - 6, /* 18 Harpsichord 3 */ - 7, /* 19 Clavinet 1 */ - 7, /* 20 Clavinet 2 */ - 7, /* 21 Clavinet 3 */ - 8, /* 22 Celesta 1 */ - 8, /* 23 Celesta 2 */ - 62, /* 24 Synthbrass 1 (62) */ - 63, /* 25 Synthbrass 2 (63) */ - 62, /* 26 Synthbrass 3 Bank 8 */ - 63, /* 27 Synthbrass 4 Bank 8 */ - 38, /* 28 Synthbass 1 */ - 39, /* 29 Synthbass 2 */ - 38, /* 30 Synthbass 3 Bank 8 */ - 39, /* 31 Synthbass 4 Bank 8 */ - 88, /* 32 Fantasy */ - 90, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ - 52, /* 34 Choral ?? Currently set to SynthVox(54). Should it be - ChoirAhhs(52)??? */ - 92, /* 35 Glass */ - 97, /* 36 Soundtrack */ - 99, /* 37 Atmosphere */ - 14, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular - Bells(14) would be better. It is! */ - 54, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ - 98, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ - 96, /* 41 IceRain */ - 68, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ - 95, /* 43 EchoPans, no equiv, setting to SweepPad */ - 81, /* 44 DoctorSolo Bank 8 */ - 87, /* 45 SchoolDaze, no real equiv */ - 112, /* 46 Bell Singer */ - 80, /* 47 SquareWave */ - 48, /* 48 Strings 1 */ - 48, /* 49 Strings 2 - should be 49 */ - 44, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should - be 50 */ - 45, /* 51 Pizzicato Strings */ - 40, /* 52 Violin 1 */ - 40, /* 53 Violin 2 ? Viola */ - 42, /* 54 Cello 1 */ - 42, /* 55 Cello 2 */ - 43, /* 56 Contrabass */ - 46, /* 57 Harp 1 */ - 46, /* 58 Harp 2 */ - 24, /* 59 Guitar 1 (Nylon) */ - 25, /* 60 Guitar 2 (Steel) */ - 26, /* 61 Elec Guitar 1 */ - 27, /* 62 Elec Guitar 2 */ - 104, /* 63 Sitar */ - 32, /* 64 Acou Bass 1 */ - 32, /* 65 Acou Bass 2 */ - 33, /* 66 Elec Bass 1 */ - 34, /* 67 Elec Bass 2 */ - 36, /* 68 Slap Bass 1 */ - 37, /* 69 Slap Bass 2 */ - 35, /* 70 Fretless Bass 1 */ - 35, /* 71 Fretless Bass 2 */ - 73, /* 72 Flute 1 */ - 73, /* 73 Flute 2 */ - 72, /* 74 Piccolo 1 */ - 72, /* 75 Piccolo 2 */ - 74, /* 76 Recorder */ - 75, /* 77 Pan Pipes */ - 64, /* 78 Sax 1 */ - 65, /* 79 Sax 2 */ - 66, /* 80 Sax 3 */ - 67, /* 81 Sax 4 */ - 71, /* 82 Clarinet 1 */ - 71, /* 83 Clarinet 2 */ - 68, /* 84 Oboe */ - 69, /* 85 English Horn (Cor Anglais) */ - 70, /* 86 Bassoon */ - 22, /* 87 Harmonica */ - 56, /* 88 Trumpet 1 */ - 56, /* 89 Trumpet 2 */ - 57, /* 90 Trombone 1 */ - 57, /* 91 Trombone 2 */ - 60, /* 92 French Horn 1 */ - 60, /* 93 French Horn 2 */ - 58, /* 94 Tuba */ - 61, /* 95 Brass Section 1 */ - 61, /* 96 Brass Section 2 */ - 11, /* 97 Vibes 1 */ - 11, /* 98 Vibes 2 */ - 99, /* 99 Syn Mallet Bank 1 */ - 112, /* 100 WindBell no real equiv Set to TinkleBell(112) */ - 9, /* 101 Glockenspiel */ - 14, /* 102 Tubular Bells */ - 13, /* 103 Xylophone */ - 12, /* 104 Marimba */ - 107, /* 105 Koto */ - 111, /* 106 Sho?? set to Shanai(111) */ - 77, /* 107 Shakauhachi */ - 78, /* 108 Whistle 1 */ - 78, /* 109 Whistle 2 */ - 76, /* 110 Bottle Blow */ - 76, /* 111 Breathpipe no real equiv set to bottle blow(76) */ - 47, /* 112 Timpani */ - 117, /* 113 Melodic Tom */ - 116, /* 114 Deap Snare no equiv, set to Taiko(116) */ - 118, /* 115 Electric Perc 1 */ - 118, /* 116 Electric Perc 2 */ - 116, /* 117 Taiko */ - 115, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ - 119, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ - 115, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ - 112, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ - 55, /* 122 Orchestral Hit */ - 124, /* 123 Telephone */ - 123, /* 124 BirdTweet */ - 94, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ - 98, /* 126 Water Bell set to Crystal Pad(98) */ - 121 /* 127 Jungle Tune set to Breath Noise */ -}; - -/* Same as above, except include patch changes - * so GS instruments can be used */ -static constexpr char Mt32ToGsMap[256] = { - 0, - 0, /* 0 Piano 1 */ - 1, - 0, /* 1 Piano 2 */ - 2, - 0, /* 2 Piano 3 (synth) */ - 4, - 0, /* 3 EPiano 1 */ - 4, - 0, /* 4 EPiano 2 */ - 5, - 0, /* 5 EPiano 3 */ - 5, - 0, /* 6 EPiano 4 */ - 3, - 0, /* 7 Honkytonk */ - 16, - 0, /* 8 Organ 1 */ - 17, - 0, /* 9 Organ 2 */ - 18, - 0, /* 10 Organ 3 */ - 16, - 0, /* 11 Organ 4 */ - 19, - 0, /* 12 Pipe Organ 1 */ - 19, - 0, /* 13 Pipe Organ 2 */ - 19, - 0, /* 14 Pipe Organ 3 */ - 21, - 0, /* 15 Accordion */ - 6, - 0, /* 16 Harpsichord 1 */ - 6, - 0, /* 17 Harpsichord 2 */ - 6, - 0, /* 18 Harpsichord 3 */ - 7, - 0, /* 19 Clavinet 1 */ - 7, - 0, /* 20 Clavinet 2 */ - 7, - 0, /* 21 Clavinet 3 */ - 8, - 0, /* 22 Celesta 1 */ - 8, - 0, /* 23 Celesta 2 */ - 62, - 0, /* 24 Synthbrass 1 (62) */ - 63, - 0, /* 25 Synthbrass 2 (63) */ - 62, - 0, /* 26 Synthbrass 3 Bank 8 */ - 63, - 0, /* 27 Synthbrass 4 Bank 8 */ - 38, - 0, /* 28 Synthbass 1 */ - 39, - 0, /* 29 Synthbass 2 */ - 38, - 0, /* 30 Synthbass 3 Bank 8 */ - 39, - 0, /* 31 Synthbass 4 Bank 8 */ - 88, - 0, /* 32 Fantasy */ - 90, - 0, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ - 52, - 0, /* 34 Choral ?? Currently set to SynthVox(54). Should it be - ChoirAhhs(52)??? */ - 92, - 0, /* 35 Glass */ - 97, - 0, /* 36 Soundtrack */ - 99, - 0, /* 37 Atmosphere */ - 14, - 0, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular - Bells(14) would be better. It is! */ - 54, - 0, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ - 98, - 0, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ - 96, - 0, /* 41 IceRain */ - 68, - 0, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ - 95, - 0, /* 43 EchoPans, no equiv, setting to SweepPad */ - 81, - 0, /* 44 DoctorSolo Bank 8 */ - 87, - 0, /* 45 SchoolDaze, no real equiv */ - 112, - 0, /* 46 Bell Singer */ - 80, - 0, /* 47 SquareWave */ - 48, - 0, /* 48 Strings 1 */ - 48, - 0, /* 49 Strings 2 - should be 49 */ - 44, - 0, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should - be 50 */ - 45, - 0, /* 51 Pizzicato Strings */ - 40, - 0, /* 52 Violin 1 */ - 40, - 0, /* 53 Violin 2 ? Viola */ - 42, - 0, /* 54 Cello 1 */ - 42, - 0, /* 55 Cello 2 */ - 43, - 0, /* 56 Contrabass */ - 46, - 0, /* 57 Harp 1 */ - 46, - 0, /* 58 Harp 2 */ - 24, - 0, /* 59 Guitar 1 (Nylon) */ - 25, - 0, /* 60 Guitar 2 (Steel) */ - 26, - 0, /* 61 Elec Guitar 1 */ - 27, - 0, /* 62 Elec Guitar 2 */ - 104, - 0, /* 63 Sitar */ - 32, - 0, /* 64 Acou Bass 1 */ - 32, - 0, /* 65 Acou Bass 2 */ - 33, - 0, /* 66 Elec Bass 1 */ - 34, - 0, /* 67 Elec Bass 2 */ - 36, - 0, /* 68 Slap Bass 1 */ - 37, - 0, /* 69 Slap Bass 2 */ - 35, - 0, /* 70 Fretless Bass 1 */ - 35, - 0, /* 71 Fretless Bass 2 */ - 73, - 0, /* 72 Flute 1 */ - 73, - 0, /* 73 Flute 2 */ - 72, - 0, /* 74 Piccolo 1 */ - 72, - 0, /* 75 Piccolo 2 */ - 74, - 0, /* 76 Recorder */ - 75, - 0, /* 77 Pan Pipes */ - 64, - 0, /* 78 Sax 1 */ - 65, - 0, /* 79 Sax 2 */ - 66, - 0, /* 80 Sax 3 */ - 67, - 0, /* 81 Sax 4 */ - 71, - 0, /* 82 Clarinet 1 */ - 71, - 0, /* 83 Clarinet 2 */ - 68, - 0, /* 84 Oboe */ - 69, - 0, /* 85 English Horn (Cor Anglais) */ - 70, - 0, /* 86 Bassoon */ - 22, - 0, /* 87 Harmonica */ - 56, - 0, /* 88 Trumpet 1 */ - 56, - 0, /* 89 Trumpet 2 */ - 57, - 0, /* 90 Trombone 1 */ - 57, - 0, /* 91 Trombone 2 */ - 60, - 0, /* 92 French Horn 1 */ - 60, - 0, /* 93 French Horn 2 */ - 58, - 0, /* 94 Tuba */ - 61, - 0, /* 95 Brass Section 1 */ - 61, - 0, /* 96 Brass Section 2 */ - 11, - 0, /* 97 Vibes 1 */ - 11, - 0, /* 98 Vibes 2 */ - 99, - 0, /* 99 Syn Mallet Bank 1 */ - 112, - 0, /* 100 WindBell no real equiv Set to TinkleBell(112) */ - 9, - 0, /* 101 Glockenspiel */ - 14, - 0, /* 102 Tubular Bells */ - 13, - 0, /* 103 Xylophone */ - 12, - 0, /* 104 Marimba */ - 107, - 0, /* 105 Koto */ - 111, - 0, /* 106 Sho?? set to Shanai(111) */ - 77, - 0, /* 107 Shakauhachi */ - 78, - 0, /* 108 Whistle 1 */ - 78, - 0, /* 109 Whistle 2 */ - 76, - 0, /* 110 Bottle Blow */ - 76, - 0, /* 111 Breathpipe no real equiv set to bottle blow(76) */ - 47, - 0, /* 112 Timpani */ - 117, - 0, /* 113 Melodic Tom */ - 116, - 0, /* 114 Deap Snare no equiv, set to Taiko(116) */ - 118, - 0, /* 115 Electric Perc 1 */ - 118, - 0, /* 116 Electric Perc 2 */ - 116, - 0, /* 117 Taiko */ - 115, - 0, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ - 119, - 0, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ - 115, - 0, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ - 112, - 0, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ - 55, - 0, /* 122 Orchestral Hit */ - 124, - 0, /* 123 Telephone */ - 123, - 0, /* 124 BirdTweet */ - 94, - 0, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ - 98, - 0, /* 126 Water Bell set to Crystal Pad(98) */ - 121, - 0 /* 127 Jungle Tune set to Breath Noise */ -}; - -static int ConvertXMIToMidi(uint8_t *in, uint32_t insize, std::vector> &out, uint32_t convert_type) -{ - struct XMIToMidiConversionContext ctx; - unsigned int i; - int ret = -1; - - if (convert_type > kXMIConvertMt32ToGs) - { - /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", - * __FUNCTION__, __LINE__, convert_type);*/ - return (ret); - } - - EPI_CLEAR_MEMORY(&ctx, XMIToMidiConversionContext, 1); - ctx.src = ctx.src_ptr = in; - ctx.srcsize = insize; - ctx.src_end = ctx.src + insize; - ctx.convert_type = convert_type; - ctx.dyn_out = &out; - - if (XMIToMidiParseXMI(&ctx) < 0) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, nullptr, - * 0);*/ - goto _end; - } - - if (XMIToMidiExtractTracks(&ctx, 0) < 0) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, nullptr, - * 0);*/ - goto _end; - } - - for (i = 0; i < ctx.info.tracks; i++) - { - out.push_back(std::vector()); - ctx.dyn_out_cur = &out.back(); - ctx.dyn_out_cur->resize(kDestinationChunkSize); - ctx.dst = ctx.dyn_out_cur->data(); - ctx.dst_ptr = ctx.dst; - ctx.dstsize = kDestinationChunkSize; - ctx.dstrem = kDestinationChunkSize; - - /* Header is 14 bytes long and add the rest as well */ - XMIToMidiWrite1(&ctx, 'M'); - XMIToMidiWrite1(&ctx, 'T'); - XMIToMidiWrite1(&ctx, 'h'); - XMIToMidiWrite1(&ctx, 'd'); - - XMIToMidiWrite4(&ctx, 6); - - XMIToMidiWrite2(&ctx, ctx.info.type); - XMIToMidiWrite2(&ctx, 1); - XMIToMidiWrite2(&ctx, ctx.timing[i]); /* write divisions from track0 */ - - XMIToMidiConvertListToMidiTrack(&ctx, ctx.events[i]); - ctx.dyn_out_cur->resize(ctx.dstsize - ctx.dstrem); - } - - ret = 0; - -_end: /* cleanup */ - if (ret < 0) - { - out.clear(); - } - if (ctx.events) - { - for (i = 0; i < ctx.info.tracks; i++) - XMIToMidiDeleteEventList(ctx.events[i]); - free(ctx.events); - } - free(ctx.timing); - - return (ret); -} - -static void XMIToMidiDeleteEventList(XMIToMidiEvent *mlist) -{ - XMIToMidiEvent *event; - XMIToMidiEvent *next; - - next = mlist; - - while ((event = next) != nullptr) - { - next = event->next; - free(event->buffer); - free(event); - } -} - -/* Sets current to the new event and updates list */ -static void XMIToMidiCreateNewEvent(struct XMIToMidiConversionContext *ctx, int32_t time) -{ - if (!ctx->list) - { - ctx->list = ctx->current = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); - ctx->current->time = (time < 0) ? 0 : time; - return; - } - - if (time < 0) - { - XMIToMidiEvent *event = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); - event->next = ctx->list; - ctx->list = ctx->current = event; - return; - } - - if (ctx->current->time > time) - ctx->current = ctx->list; - - while (ctx->current->next) - { - if (ctx->current->next->time > time) - { - XMIToMidiEvent *event = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); - event->next = ctx->current->next; - ctx->current->next = event; - ctx->current = event; - ctx->current->time = time; - return; - } - - ctx->current = ctx->current->next; - } - - ctx->current->next = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); - ctx->current = ctx->current->next; - ctx->current->time = time; -} - -/* Conventional Variable Length Quantity */ -static int XMIToMidiGetVlq(struct XMIToMidiConversionContext *ctx, uint32_t *quant) -{ - int i; - uint32_t data; - - *quant = 0; - for (i = 0; i < 4; i++) - { - if (ctx->src_ptr + 1 >= ctx->src + ctx->srcsize) - break; - data = XMIToMidiRead1(ctx); - *quant <<= 7; - *quant |= data & 0x7F; - - if (!(data & 0x80)) - { - i++; - break; - } - } - return (i); -} - -/* XMIDI Delta Variable Length Quantity */ -static int XMIToMidiGetVlq2(struct XMIToMidiConversionContext *ctx, uint32_t *quant) -{ - int i; - int32_t data; - - *quant = 0; - for (i = 0; XMIToMidiGetSourcePosition(ctx) != XMIToMidiGetSourceSize(ctx); ++i) - { - data = XMIToMidiRead1(ctx); - if (data & 0x80) - { - XMIToMidiSkipSource(ctx, -1); - break; - } - *quant += data; - } - return (i); -} - -static int XMIToMidiPutVlq(struct XMIToMidiConversionContext *ctx, uint32_t value) -{ - int32_t buffer; - int i = 1, j; - buffer = value & 0x7F; - while (value >>= 7) - { - buffer <<= 8; - buffer |= ((value & 0x7F) | 0x80); - i++; - } - for (j = 0; j < i; j++) - { - XMIToMidiWrite1(ctx, buffer & 0xFF); - buffer >>= 8; - } - - return (i); -} - -/* Converts Events - * - * Source is at the first data byte - * size 1 is single data byte - * size 2 is dual data byte - * size 3 is XMI Note on - * Returns bytes converted */ -static int XMIToMidiConvertEvent(struct XMIToMidiConversionContext *ctx, const int32_t time, const uint8_t status, - const int size) -{ - uint32_t delta = 0; - int32_t data; - XMIToMidiEvent *prev; - int i; - - data = XMIToMidiRead1(ctx); - - /*HACK!*/ - if (((status >> 4) == 0xB) && (status & 0xF) != 9 && (data == 114)) - { - data = 32; /*Change XMI 114 controller into XG bank*/ - } - - /* Bank changes are handled here */ - if ((status >> 4) == 0xB && data == 0) - { - data = XMIToMidiRead1(ctx); - - ctx->bank127[status & 0xF] = 0; - - if (ctx->convert_type == kXMIConvertMt32ToGm || ctx->convert_type == kXMIConvertMt32ToGs || - ctx->convert_type == kXMIConvertMt32ToGs127 || - (ctx->convert_type == kXMIConvertMt32ToGs127Drum && (status & 0xF) == 9)) - return (2); - - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = status; - ctx->current->data[0] = 0; - ctx->current->data[1] = data == 127 ? 0 : data; /*HACK:*/ - - if (ctx->convert_type == kXMIConvertGs127ToGs && data == 127) - ctx->bank127[status & 0xF] = 1; - - return (2); - } - - /* Handling for patch change mt32 conversion, probably should go elsewhere - */ - if ((status >> 4) == 0xC && (status & 0xF) != 9 && ctx->convert_type != kXMINoConversion) - { - if (ctx->convert_type == kXMIConvertMt32ToGm) - { - data = Mt32ToGmMap[data]; - } - else if ((ctx->convert_type == kXMIConvertGs127ToGs && ctx->bank127[status & 0xF]) || - ctx->convert_type == kXMIConvertMt32ToGs || ctx->convert_type == kXMIConvertMt32ToGs127Drum) - { - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = 0xB0 | (status & 0xF); - ctx->current->data[0] = 0; - ctx->current->data[1] = Mt32ToGsMap[data * 2 + 1]; - - data = Mt32ToGsMap[data * 2]; - } - else if (ctx->convert_type == kXMIConvertMt32ToGs127) - { - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = 0xB0 | (status & 0xF); - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - } - /* Drum track handling */ - else if ((status >> 4) == 0xC && (status & 0xF) == 9 && - (ctx->convert_type == kXMIConvertMt32ToGs127Drum || ctx->convert_type == kXMIConvertMt32ToGs127)) - { - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = 0xB9; - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = status; - - ctx->current->data[0] = data; - - if (size == 1) - return (1); - - ctx->current->data[1] = XMIToMidiRead1(ctx); - - if (size == 2) - return (2); - - /* XMI Note On handling */ - prev = ctx->current; - i = XMIToMidiGetVlq(ctx, &delta); - XMIToMidiCreateNewEvent(ctx, time + delta * 3); - - ctx->current->status = status; - ctx->current->data[0] = data; - ctx->current->data[1] = 0; - ctx->current = prev; - - return (i + 2); -} - -/* Simple routine to convert system messages */ -static int32_t XMIToMidiConvertSystemMessage(struct XMIToMidiConversionContext *ctx, const int32_t time, - const uint8_t status) -{ - int32_t i = 0; - - XMIToMidiCreateNewEvent(ctx, time); - ctx->current->status = status; - - /* Handling of Meta events */ - if (status == 0xFF) - { - ctx->current->data[0] = XMIToMidiRead1(ctx); - i++; - } - - i += XMIToMidiGetVlq(ctx, &ctx->current->len); - - if (!ctx->current->len) - return (i); - - ctx->current->buffer = (uint8_t *)malloc(sizeof(uint8_t) * ctx->current->len); - XMIToMidiCopy(ctx, (char *)ctx->current->buffer, ctx->current->len); - - return (i + ctx->current->len); -} - -/* XMIDI and Midi to List - * Returns XMIDI PPQN */ -static int32_t XMIToMidiConvertFiletoList(struct XMIToMidiConversionContext *ctx, const XMIToMidiBranch *rbrn) -{ - int32_t time = 0; - uint32_t data; - int32_t end = 0; - int32_t tempo = 500000; - int32_t tempo_set = 0; - uint32_t status = 0; - uint32_t file_size = XMIToMidiGetSourceSize(ctx); - uint32_t begin = XMIToMidiGetSourcePosition(ctx); - - /* Set Drum track to correct setting if required */ - if (ctx->convert_type == kXMIConvertMt32ToGs127) - { - XMIToMidiCreateNewEvent(ctx, 0); - ctx->current->status = 0xB9; - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - - while (!end && XMIToMidiGetSourcePosition(ctx) < file_size) - { - uint32_t offset = XMIToMidiGetSourcePosition(ctx) - begin; - - /* search for branch to this offset */ - for (unsigned i = 0, n = rbrn->count; i < n; ++i) - { - if (offset == rbrn->offset[i]) - { - unsigned id = rbrn->id[i]; - - XMIToMidiCreateNewEvent(ctx, time); - - uint8_t *marker = (uint8_t *)malloc(sizeof(uint8_t) * 8); - memcpy(marker, ":XBRN:", 6); - const char hex[] = "0123456789ABCDEF"; - marker[6] = hex[id >> 4]; - marker[7] = hex[id & 15]; - - ctx->current->status = 0xFF; - ctx->current->data[0] = 0x06; - ctx->current->len = 8; - - ctx->current->buffer = marker; - } - } - - XMIToMidiGetVlq2(ctx, &data); - time += data * 3; - - status = XMIToMidiRead1(ctx); - - switch (status >> 4) - { - case kXMIStatusNoteOn: - XMIToMidiConvertEvent(ctx, time, status, 3); - break; - - /* 2 byte data */ - case kXMIStatusNoteOff: - case kXMIStatusAftertouch: - case kXMIStatusController: - case kXMIStatusPitchWheel: - XMIToMidiConvertEvent(ctx, time, status, 2); - break; - - /* 1 byte data */ - case kXMIStatusProgramChange: - case kXMIStatusPressure: - XMIToMidiConvertEvent(ctx, time, status, 1); - break; - - case kXMIStatusSysex: - if (status == 0xFF) - { - int32_t pos = XMIToMidiGetSourcePosition(ctx); - uint32_t dat = XMIToMidiRead1(ctx); - - if (dat == 0x2F) /* End */ - end = 1; - else if (dat == 0x51 && !tempo_set) /* Tempo. Need it for PPQN */ - { - XMIToMidiSkipSource(ctx, 1); - tempo = XMIToMidiRead1(ctx) << 16; - tempo += XMIToMidiRead1(ctx) << 8; - tempo += XMIToMidiRead1(ctx); - tempo *= 3; - tempo_set = 1; - } - else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */ - { - XMIToMidiGetVlq(ctx, &dat); - XMIToMidiSkipSource(ctx, dat); - break; - } - - XMIToMidiSeekSource(ctx, pos); - } - XMIToMidiConvertSystemMessage(ctx, time, status); - break; - - default: - break; - } - } - return ((tempo * 3) / 25000); -} - -/* Converts and event list to a MidiTrack - * Returns bytes of the array - * buf can be nullptr */ -static uint32_t XMIToMidiConvertListToMidiTrack(struct XMIToMidiConversionContext *ctx, XMIToMidiEvent *mlist) -{ - int32_t time = 0; - XMIToMidiEvent *event; - uint32_t delta; - uint8_t last_status = 0; - uint32_t i = 8; - uint32_t j; - uint32_t size_pos, cur_pos; - int end = 0; - - XMIToMidiWrite1(ctx, 'M'); - XMIToMidiWrite1(ctx, 'T'); - XMIToMidiWrite1(ctx, 'r'); - XMIToMidiWrite1(ctx, 'k'); - - size_pos = XMIToMidiGetDestinationPosition(ctx); - XMIToMidiSkipDestination(ctx, 4); - - for (event = mlist; event && !end; event = event->next) - { - delta = (event->time - time); - time = event->time; - - i += XMIToMidiPutVlq(ctx, delta); - - if ((event->status != last_status) || (event->status >= 0xF0)) - { - XMIToMidiWrite1(ctx, event->status); - i++; - } - - last_status = event->status; - - switch (event->status >> 4) - { - /* 2 bytes data - * Note off, Note on, Aftertouch, Controller and Pitch Wheel */ - case 0x8: - case 0x9: - case 0xA: - case 0xB: - case 0xE: - XMIToMidiWrite1(ctx, event->data[0]); - XMIToMidiWrite1(ctx, event->data[1]); - i += 2; - break; - - /* 1 bytes data - * Program Change and Channel Pressure */ - case 0xC: - case 0xD: - XMIToMidiWrite1(ctx, event->data[0]); - i++; - break; - - /* Variable length - * SysEx */ - case 0xF: - if (event->status == 0xFF) - { - if (event->data[0] == 0x2f) - end = 1; - XMIToMidiWrite1(ctx, event->data[0]); - i++; - } - i += XMIToMidiPutVlq(ctx, event->len); - if (event->len) - { - for (j = 0; j < event->len; j++) - { - XMIToMidiWrite1(ctx, event->buffer[j]); - i++; - } - } - break; - - /* Never occur */ - default: - /*_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);*/ - break; - } - } - - cur_pos = XMIToMidiGetDestinationPosition(ctx); - XMIToMidiSeekDestination(ctx, size_pos); - XMIToMidiWrite4(ctx, i - 8); - XMIToMidiSeekDestination(ctx, cur_pos); - - return (i); -} - -/* Assumes correct xmidi */ -static uint32_t XMIToMidiExtractTracksFromXMI(struct XMIToMidiConversionContext *ctx) -{ - uint32_t num = 0; - signed short ppqn; - uint32_t len = 0; - int32_t begin; - char buf[32]; - uint32_t branch[128]; - - /* clear branch points */ - for (unsigned i = 0; i < 128; ++i) - branch[i] = ~0u; - - while (XMIToMidiGetSourcePosition(ctx) < XMIToMidiGetSourceSize(ctx) && num != ctx->info.tracks) - { - /* Read first 4 bytes of name */ - XMIToMidiCopy(ctx, buf, 4); - len = XMIToMidiRead4(ctx); - - /* Skip the FORM entries */ - if (!memcmp(buf, "FORM", 4)) - { - XMIToMidiSkipSource(ctx, 4); - XMIToMidiCopy(ctx, buf, 4); - len = XMIToMidiRead4(ctx); - } - - if (!memcmp(buf, "RBRN", 4)) - { - begin = XMIToMidiGetSourcePosition(ctx); - uint32_t count; - - if (len < 2) - { - /* insufficient data */ - goto rbrn_nodata; - } - - count = XMIToMidiRead2(ctx); - if (len - 2 < 6 * count) - { - /* insufficient data */ - goto rbrn_nodata; - } - - for (uint32_t i = 0; i < count; ++i) - { - /* read branch point as byte offset */ - uint32_t ctlvalue = XMIToMidiRead2(ctx); - uint32_t evtoffset = XMIToMidiRead4LittleEndian(ctx); - if (ctlvalue < 128) - branch[ctlvalue] = evtoffset; - } - - rbrn_nodata: - XMIToMidiSeekSource(ctx, begin + ((len + 1) & ~1)); - continue; - } - - if (memcmp(buf, "EVNT", 4)) - { - XMIToMidiSkipSource(ctx, (len + 1) & ~1); - continue; - } - - ctx->list = nullptr; - begin = XMIToMidiGetSourcePosition(ctx); - - /* Rearrange branches as structure */ - XMIToMidiBranch rbrn; - rbrn.count = 0; - for (unsigned i = 0; i < 128; ++i) - { - if (branch[i] != ~0u) - { - unsigned index = rbrn.count; - rbrn.id[index] = i; - rbrn.offset[index] = branch[i]; - rbrn.count = index + 1; - } - } - - /* Convert it */ - if ((ppqn = XMIToMidiConvertFiletoList(ctx, &rbrn)) == 0) - { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, nullptr, - * 0);*/ - break; - } - ctx->timing[num] = ppqn; - ctx->events[num] = ctx->list; - - /* Increment Counter */ - num++; - - /* go to start of next track */ - XMIToMidiSeekSource(ctx, begin + ((len + 1) & ~1)); - - /* clear branch points */ - for (unsigned i = 0; i < 128; ++i) - branch[i] = ~0u; - } - - /* Return how many were converted */ - return (num); -} - -static int XMIToMidiParseXMI(struct XMIToMidiConversionContext *ctx) -{ - uint32_t i; - uint32_t start; - uint32_t len; - uint32_t chunk_len; - uint32_t file_size; - char buf[32]; - - file_size = XMIToMidiGetSourceSize(ctx); - if (XMIToMidiGetSourcePosition(ctx) + 8 > file_size) - { - badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too - short)", 0);*/ - return (-1); - } - - /* Read first 4 bytes of header */ - XMIToMidiCopy(ctx, buf, 4); - - /* Could be XMIDI */ - if (!memcmp(buf, "FORM", 4)) - { - /* Read length of */ - len = XMIToMidiRead4(ctx); - - start = XMIToMidiGetSourcePosition(ctx); - if (start + 4 > file_size) - goto badfile; - - /* Read 4 bytes of type */ - XMIToMidiCopy(ctx, buf, 4); - - /* XDIRless XMIDI, we can handle them here. */ - if (!memcmp(buf, "XMID", 4)) - { - /*_WM_DEBUG_MSG("Warning: XMIDI without XDIR");*/ - ctx->info.tracks = 1; - } - /* Not an XMIDI that we recognise */ - else if (memcmp(buf, "XDIR", 4)) - { - goto badfile; - } - else - { /* Seems Valid */ - ctx->info.tracks = 0; - - for (i = 4; i < len; i++) - { - /* check too short files */ - if (XMIToMidiGetSourcePosition(ctx) + 10 > file_size) - break; - - /* Read 4 bytes of type */ - XMIToMidiCopy(ctx, buf, 4); - - /* Read length of chunk */ - chunk_len = XMIToMidiRead4(ctx); - - /* Add eight bytes */ - i += 8; - - if (memcmp(buf, "INFO", 4)) - { - /* Must align */ - XMIToMidiSkipSource(ctx, (chunk_len + 1) & ~1); - i += (chunk_len + 1) & ~1; - continue; - } - - /* Must be at least 2 bytes long */ - if (chunk_len < 2) - break; - - ctx->info.tracks = XMIToMidiRead2(ctx); - break; - } - - /* Didn't get to fill the header */ - if (ctx->info.tracks == 0) - { - goto badfile; - } - - /* Ok now to start part 2 - * Goto the right place */ - XMIToMidiSeekSource(ctx, start + ((len + 1) & ~1)); - if (XMIToMidiGetSourcePosition(ctx) + 12 > file_size) - goto badfile; - - /* Read 4 bytes of type */ - XMIToMidiCopy(ctx, buf, 4); - - if (memcmp(buf, "CAT ", 4)) - { - /*_WM_ERROR_NEW("XMI error: expected \"CAT \", found - \"%c%c%c%c\".", buf[0], buf[1], buf[2], buf[3]);*/ - return (-1); - } - - /* Now read length of this track */ - XMIToMidiRead4(ctx); - - /* Read 4 bytes of type */ - XMIToMidiCopy(ctx, buf, 4); - - if (memcmp(buf, "XMID", 4)) - { - /*_WM_ERROR_NEW("XMI error: expected \"XMID\", found - \"%c%c%c%c\".", buf[0], buf[1], buf[2], buf[3]);*/ - return (-1); - } - - /* Valid XMID */ - ctx->datastart = XMIToMidiGetSourcePosition(ctx); - return (0); - } - } - - return (-1); -} - -static int XMIToMidiExtractTracks(struct XMIToMidiConversionContext *ctx, int32_t dstTrackNumber) -{ - uint32_t i; - - ctx->events = (XMIToMidiEvent **)calloc(ctx->info.tracks, sizeof(XMIToMidiEvent *)); - ctx->timing = (int16_t *)calloc(ctx->info.tracks, sizeof(int16_t)); - /* type-2 for multi-tracks, type-0 otherwise */ - ctx->info.type = (ctx->info.tracks > 1 && (dstTrackNumber < 0 || ctx->info.tracks >= dstTrackNumber)) ? 2 : 0; - - XMIToMidiSeekSource(ctx, ctx->datastart); - i = XMIToMidiExtractTracksFromXMI(ctx); - - if (i != ctx->info.tracks) - { - /*_WM_ERROR_NEW("XMI error: extracted only %u out of %u tracks from - XMIDI", ctx->info.tracks, i);*/ - return (-1); - } - - return (0); -} -#endif - -/*! Raw MIDI event hook */ -typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, - size_t len); -/*! PCM render */ -typedef void (*PcmRender)(void *userdata, uint8_t *stream, size_t length); -/*! Library internal debug messages */ -typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); -/*! Loop Start event hook */ -typedef void (*LoopStartHook)(void *userdata); -/*! Loop Start event hook */ -typedef void (*LoopEndHook)(void *userdata); -typedef void (*SongStartHook)(void *userdata); - -/*! Note-On MIDI event */ -typedef void (*RtNoteOn)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); -/*! Note-Off MIDI event */ -typedef void (*RtNoteOff)(void *userdata, uint8_t channel, uint8_t note); -/*! Note-Off MIDI event with a velocity */ -typedef void (*RtNoteOffVel)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); -/*! Note aftertouch MIDI event */ -typedef void (*RtNoteAfterTouch)(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal); -/*! Channel aftertouch MIDI event */ -typedef void (*RtChannelAfterTouch)(void *userdata, uint8_t channel, uint8_t atVal); -/*! Controller change MIDI event */ -typedef void (*RtControllerChange)(void *userdata, uint8_t channel, uint8_t type, uint8_t value); -/*! Patch change MIDI event */ -typedef void (*RtPatchChange)(void *userdata, uint8_t channel, uint8_t patch); -/*! Pitch bend MIDI event */ -typedef void (*RtPitchBend)(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb); -/*! System Exclusive MIDI event */ -typedef void (*RtSysEx)(void *userdata, const uint8_t *msg, size_t size); -/*! Meta event hook */ -typedef void (*MetaEventHook)(void *userdata, uint8_t type, const uint8_t *data, size_t len); -/*! Device Switch MIDI event */ -typedef void (*RtDeviceSwitch)(void *userdata, size_t track, const char *data, size_t length); -/*! Get the channels offset for current MIDI device */ -typedef size_t (*RtCurrentDevice)(void *userdata, size_t track); -/*! [Non-Standard] Pass raw OPL3 data to the chip - */ -typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value); - -/** - \brief Real-Time MIDI interface between Sequencer and the Synthesizer - */ -struct MidiRealTimeInterface -{ - /*! MIDI event hook which catches all MIDI events */ - RawEventHook onEvent; - /*! User data which will be passed through On-Event hook */ - void *onEvent_userdata; - - /*! PCM render hook which catches passing of loop start point */ - PcmRender onPcmRender; - /*! User data which will be passed through On-PCM-render hook */ - void *onPcmRender_userdata; - - /*! Sample rate */ - uint32_t pcmSampleRate; - - /*! Size of one sample in bytes */ - uint32_t pcmFrameSize; - - /*! Debug message hook */ - DebugMessageHook onDebugMessage; - /*! User data which will be passed through Debug Message hook */ - void *onDebugMessage_userdata; - - /*! Loop start hook which catches passing of loop start point */ - LoopStartHook onloopStart; - /*! User data which will be passed through On-LoopStart hook */ - void *onloopStart_userdata; - - /*! Loop start hook which catches passing of loop start point */ - LoopEndHook onloopEnd; - /*! User data which will be passed through On-LoopStart hook */ - void *onloopEnd_userdata; - - /*! Song start hook which is calling when starting playing song at begin - */ - SongStartHook onSongStart; - /*! User data which will be passed through On-SongStart hook */ - void *onSongStart_userdata; - - /*! MIDI Run Time event calls user data */ - void *rtUserData; - - /*************************************************** - * Standard MIDI events. All of them are required! * - ***************************************************/ - - /*! Note-On MIDI event hook */ - RtNoteOn rt_noteOn; - /*! Note-Off MIDI event hook */ - RtNoteOff rt_noteOff; - - /*! Note-Off MIDI event hook with a velocity */ - RtNoteOffVel rt_noteOffVel; - - /*! Note aftertouch MIDI event hook */ - RtNoteAfterTouch rt_noteAfterTouch; - - /*! Channel aftertouch MIDI event hook */ - RtChannelAfterTouch rt_channelAfterTouch; - - /*! Controller change MIDI event hook */ - RtControllerChange rt_controllerChange; - - /*! Patch change MIDI event hook */ - RtPatchChange rt_patchChange; - - /*! Pitch bend MIDI event hook */ - RtPitchBend rt_pitchBend; - - /*! System Exclusive MIDI event hook */ - RtSysEx rt_systemExclusive; - - /******************* - * Optional events * - *******************/ - - /*! Meta event hook which catches all meta events */ - MetaEventHook rt_metaEvent; - - /*! Device Switch MIDI event hook */ - RtDeviceSwitch rt_deviceSwitch; - - /*! Get the channels offset for current MIDI device hook. Returms - * multiple to 16 value. */ - RtCurrentDevice rt_currentDevice; - - /****************************************** - * NonStandard events. There are optional * - ******************************************/ - /*! [Non-Standard] Pass raw OPL3 data to the chip hook */ - RtRawOPL rt_rawOPL; -}; - -// MidiFraction is a stripped down version of -// Bisqwit's Fraction class with the following -// copyright: -/* - * Fraction number handling. - * Copyright (C) 1992,2001 Bisqwit (http://iki.fi/bisqwit/) - * - * The license of this file is in Public Domain: - * https://bisqwit.iki.fi/src/index.html - * - * "... and orphan source code files are copyrighted public domain." - */ - -class MidiFraction -{ - uint64_t num1_, num2_; - void Optim(); - - public: - MidiFraction() : num1_(0), num2_(1) - { - } - MidiFraction(uint64_t value) : num1_(value), num2_(1) - { - } - MidiFraction(uint64_t n, uint64_t d) : num1_(n), num2_(d) - { - } - inline double Value() const - { - return Nom() / (double)Denom(); - } - MidiFraction &operator*=(const MidiFraction &b) - { - num1_ *= b.Nom(); - num2_ *= b.Denom(); - Optim(); - return *this; - } - MidiFraction operator*(const MidiFraction &b) const - { - MidiFraction tmp(*this); - tmp *= b; - return tmp; - } - const uint64_t &Nom() const - { - return num1_; - } - const uint64_t &Denom() const - { - return num2_; - } -}; - -void MidiFraction::Optim() -{ - /* Euclidean algorithm */ - uint64_t n1, n2, nn1, nn2; - - nn1 = num1_; - nn2 = num2_; - - if (nn1 < nn2) - n1 = num1_, n2 = num2_; - else - n1 = num2_, n2 = num1_; - - if (!num1_) - { - num2_ = 1; - return; - } - for (;;) - { - uint64_t tmp = n2 % n1; - if (!tmp) - break; - n2 = n1; - n1 = tmp; - } - num1_ /= n1; - num2_ /= n1; -} - -MidiFraction operator*(const uint64_t bla, const MidiFraction &b) -{ - return MidiFraction(bla) * b; -} - -class MidiSequencer -{ - /** - * @brief MIDI Event utility container - */ - struct MidiEvent - { - /** - * @brief Main MIDI event types - */ - enum Types - { - //! Unknown event - kUnknown = 0x00, - //! Note-Off event - kNoteOff = 0x08, // size == 2 - //! Note-On event - kNoteOn = 0x09, // size == 2 - //! Note After-Touch event - kNoteTouch = 0x0A, // size == 2 - //! Controller change event - kControlChange = 0x0B, // size == 2 - //! Patch change event - kPatchChange = 0x0C, // size == 1 - //! Channel After-Touch event - kChannelAftertouch = 0x0D, // size == 1 - //! Pitch-bend change event - kPitchWheel = 0x0E, // size == 2 - - //! System Exclusive message, type 1 - kSysex = 0xF0, // size == len - //! Sys Com Song Position Pntr [LSB, MSB] - kSysComSongPositionPointer = 0xF2, // size == 2 - //! Sys Com Song Select(Song #) [0-127] - kSysComSongSelect = 0xF3, // size == 1 - //! System Exclusive message, type 2 - kSysex2 = 0xF7, // size == len - //! Special event - kSpecial = 0xFF - }; - /** - * @brief Special MIDI event sub-types - */ - enum SubTypes - { - //! Sequension number - kSequensionNumber = 0x00, // size == 2 - //! Text label - kText = 0x01, // size == len - //! Copyright notice - kCopyright = 0x02, // size == len - //! Sequence track title - kSequenceTrackTitle = 0x03, // size == len - //! Instrument title - kInstrumentTitle = 0x04, // size == len - //! Lyrics text fragment - kLyrics = 0x05, // size == len - //! MIDI Marker - kMarker = 0x06, // size == len - //! Cue Point - kCuePoint = 0x07, // size == len - //! [Non-Standard] Device Switch - kDeviceSwitch = 0x09, // size == len - //! MIDI Channel prefix - kMidiChannelPrefix = 0x20, // size == 1 - - //! End of Track event - kEndTrack = 0x2F, // size == 0 - //! Tempo change event - kTempoChange = 0x51, // size == 3 - //! SMPTE offset - kSmpteOffset = 0x54, // size == 5 - //! Time signature - kTimeSignature = 0x55, // size == 4 - //! Key signature - kKeySignature = 0x59, // size == 2 - //! Sequencer specs - kSequencerSpec = 0x7F, // size == len - - /* Non-standard, internal ADLMIDI usage only */ - //! [Non-Standard] Loop Start point - kLoopStart = 0xE1, // size == 0 - //! [Non-Standard] Loop End point - kLoopEnd = 0xE2, // size == 0 - //! [Non-Standard] Raw OPL data - kRawOPL = 0xE3, // size == 0 - - //! [Non-Standard] Loop Start point with support of multi-loops - kLoopStackBegin = 0xE4, // size == 1 - //! [Non-Standard] Loop End point with - //! support of multi-loops - kLoopStackEnd = 0xE5, // size == 0 - //! [Non-Standard] Loop End point with - //! support of multi-loops - kLoopStackBreak = 0xE6, // size == 0 - //! [Non-Standard] Callback Trigger - kCallbackTrigger = 0xE7, // size == 1 - - // Built-in hooks - kSongBeginHook = 0x101 - }; - //! Main type of event - uint_fast16_t type = kUnknown; - //! Sub-type of the event - uint_fast16_t sub_type = kUnknown; - //! Targeted MIDI channel - uint_fast16_t channel = 0; - //! Is valid event - uint_fast16_t is_valid = 1; - //! Reserved 5 bytes padding - uint_fast16_t padding[4]; - //! Absolute tick position (Used for the tempo calculation only) - uint64_t absolute_tick_position = 0; - //! Raw data of this event - std::vector data; - }; - - /** - * @brief A track position event contains a chain of MIDI events until next - * delay value - * - * Created with purpose to sort events by type in the same position - * (for example, to keep controllers always first than note on events or - * lower than note-off events) - */ - class MidiTrackRow - { - public: - MidiTrackRow(); - //! Clear MIDI row data - void Clear(); - //! Absolute time position in seconds - double time_; - //! Delay to next event in ticks - uint64_t delay_; - //! Absolute position in ticks - uint64_t absolute_position_; - //! Delay to next event in seconds - double time_delay_; - //! List of MIDI events in the current row - std::vector events_; - /** - * @brief Sort events in this position - * @param note_states Buffer of currently pressed/released note keys in - * the track - */ - void SortEvents(bool *note_states = nullptr); - }; - - /** - * @brief Tempo change point entry. Used in the MIDI data building function - * only. - */ - struct TempoChangePoint - { - uint64_t absolute_position; - MidiFraction tempo; - }; - - /** - * @brief Song position context - */ - struct Position - { - //! Was track began playing - bool began = false; - //! Reserved - char padding[7] = {0, 0, 0, 0, 0, 0, 0}; - //! Waiting time before next event in seconds - double wait = 0.0; - //! Absolute time position on the track in seconds - double absolute_time_position = 0.0; - //! Track information - struct TrackInfo - { - //! Delay to next event in a track - uint64_t delay = 0; - //! Last handled event type - int32_t lastHandledEvent = 0; - //! Reserved - char padding2[4]; - //! MIDI Events queue position iterator - std::list::iterator pos; - }; - std::vector track; - }; - - //! MIDI Output interface context - const MidiRealTimeInterface *midi_output_interface_; - - /** - * @brief Prepare internal events storage for track data building - * @param track_count Count of tracks - */ - void BuildSMFSetupReset(size_t track_count); - - /** - * @brief Build MIDI track data from the raw track data storage - * @return true if everything successfully processed, or false on any error - */ - bool BuildSMFTrackData(const std::vector> &track_data); - - /** - * @brief Build the time line from off loaded events - * @param tempos Pre-collected list of tempo events - * @param loop_start_ticks Global loop start tick (give zero if no global - * loop presented) - * @param loop_end_ticks Global loop end tick (give zero if no global loop - * presented) - */ - void BuildTimeLine(const std::vector &tempos, uint64_t loop_start_ticks = 0, - uint64_t loop_end_ticks = 0); - - /** - * @brief Parse one event from raw MIDI track stream - * @param [_inout] ptr pointer to pointer to current position on the raw - * data track - * @param [_in] end address to end of raw track data, needed to validate - * position and size - * @param [_inout] status status of the track processing - * @return Parsed MIDI event entry - */ - MidiEvent ParseEvent(const uint8_t **ptr, const uint8_t *end, int &status); - - /** - * @brief Process MIDI events on the current tick moment - * @param is_seek is a seeking process - * @return returns false on reaching end of the song - */ - bool ProcessEvents(bool is_seek = false); - - /** - * @brief Handle one event from the chain - * @param tk MIDI track - * @param evt MIDI event entry - * @param status Recent event type, -1 returned when end of track event was - * handled. - */ - void handleEvent(size_t tk, const MidiEvent &evt, int32_t &status); - - public: - /** - * @brief MIDI marker entry - */ - struct MidiMarkerEntry - { - //! Label - std::string label; - //! Position time in seconds - double position_time; - //! Position time in MIDI ticks - uint64_t position_ticks; - }; - - /** - * @brief The FileFormat enum - */ - enum FileFormat - { - //! MIDI format - kFormatMidi, -#if EDGE_IMF_SUPPORT - //! Id-Software Music File - kFormatIMF, -#endif - //! EA-MUS format - kFormatRSXX, -#if EDGE_XMI_SUPPORT - //! AIL's XMIDI format (act same as MIDI, but with exceptions) - kFormatXMidi -#endif - }; - - /** - * @brief Format of loop points implemented by CC events - */ - enum LoopFormat - { - kLoopDefault, - kLoopRpgMaker = 1, - kLoopEMidi, - kLoopHmi - }; - - private: - //! Music file format type. MIDI is default. - FileFormat midi_format_; - //! SMF format identifier. - unsigned midi_smf_format_; - //! Loop points format - LoopFormat midi_loop_format_; - - //! Current position - Position midi_current_position_; - //! Track begin position - Position midi_track_begin_position_; - //! Loop start point - Position midi_loop_begin_position_; - - //! Is looping enabled or not - bool midi_loop_enabled_; - //! Don't process loop: trigger hooks only if they are set - bool midi_loop_hooks_only_; - - //! Full song length in seconds - double midi_full_song_time_length_; - //! Delay after song playd before rejecting the output stream requests - double midi_post_song_wait_delay_; - - //! Global loop start time - double midi_loop_start_time_; - //! Global loop end time - double midi_loop_end_time_; - - //! Pre-processed track data storage - std::vector> midi_track_data_; - - //! Title of music - std::string midi_music_title_; - //! Copyright notice of music - std::string midi_music_copyright_; - //! List of track titles - std::vector midi_music_track_titles_; - //! List of MIDI markers - std::vector midi_music_markers_; - - //! Time of one tick - MidiFraction midi_individual_tick_delta_; - //! Current tempo - MidiFraction midi_tempo_; - - //! Tempo multiplier factor - double midi_tempo_multiplier_; - //! Is song at end - bool midi_at_end_; - - //! Set the number of loops limit. Lesser than 0 - loop infinite - int midi_loop_count_; - - //! The number of track of multi-track file (for exmaple, XMI) to load - int midi_load_track_number_; - - //! The XMI-specific list of raw songs, converted into SMF format - std::vector> midi_raw_songs_data_; - - /** - * @brief Loop stack entry - */ - struct LoopStackEntry - { - //! is infinite loop - bool infinity = false; - //! Count of loops left to break. <0 - infinite loop - int loops = 0; - //! Start position snapshot to return back - Position start_position; - //! Loop start tick - uint64_t start = 0; - //! Loop end tick - uint64_t end = 0; - }; - - class LoopState - { - public: - //! Loop start has reached - bool caught_start_; - //! Loop end has reached, reset on handling - bool caught_end_; - - //! Loop start has reached - bool caught_stack_start_; - //! Loop next has reached, reset on handling - bool caught_stack_end_; - //! Loop break has reached, reset on handling - bool caught_stack_break_; - //! Skip next stack loop start event handling - bool skip_stack_start_; - - //! Are loop points invalid? - bool invalid_loop_; /*Loop points are invalid (loopStart after loopEnd - or loopStart and loopEnd are on same place)*/ - - //! Is look got temporarily broken because of post-end seek? - bool temporary_broken_; - - //! How much times the loop should start repeat? For example, if you - //! want to loop song twice, set value 1 - int loops_count_; - - //! how many loops left until finish the song - int loops_left_; - - //! Stack of nested loops - std::vector stack_; - //! Current level on the loop stack (<0 - out of loop, 0++ - the index - //! in the loop stack) - int stack_level_; - - /** - * @brief Reset loop state to initial - */ - void Reset() - { - caught_start_ = false; - caught_end_ = false; - caught_stack_start_ = false; - caught_stack_end_ = false; - caught_stack_break_ = false; - skip_stack_start_ = false; - loops_left_ = loops_count_; - } - - void FullReset() - { - loops_count_ = -1; - Reset(); - invalid_loop_ = false; - temporary_broken_ = false; - stack_.clear(); - stack_level_ = -1; - } - - bool IsStackEnd() - { - if (caught_stack_end_ && (stack_level_ >= 0) && (stack_level_ < (int)(stack_.size()))) - { - const LoopStackEntry &e = stack_[(size_t)(stack_level_)]; - if (e.infinity || (!e.infinity && e.loops > 0)) - return true; - } - return false; - } - - void StackUp(int count = 1) - { - stack_level_ += count; - } - - void StackDown(int count = 1) - { - stack_level_ -= count; - } - - LoopStackEntry &GetCurrentStack() - { - if ((stack_level_ >= 0) && (stack_level_ < (int)(stack_.size()))) - return stack_[(size_t)(stack_level_)]; - if (stack_.empty()) - { - LoopStackEntry d; - d.loops = 0; - d.infinity = 0; - d.start = 0; - d.end = 0; - stack_.push_back(d); - } - return stack_[0]; - } - }; - - LoopState midi_loop_; - - //! Whether the nth track has playback disabled - std::vector midi_track_disabled_; - //! Index of solo track, or max for disabled - size_t midi_track_solo_; - //! MIDI channel disable (exception for extra port-prefix-based channels) - bool m_channelDisable[16]; - - /** - * @brief Handler of callback trigger events - * @param userdata Pointer to user data (usually, context of something) - * @param trigger Value of the event which triggered this callback. - * @param track Identifier of the track which triggered this callback. - */ - typedef void (*TriggerHandler)(void *userdata, unsigned trigger, size_t track); - - //! Handler of callback trigger events - TriggerHandler midi_trigger_handler_; - //! User data of callback trigger events - void *midi_trigger_userdata_; - - //! File parsing errors string (adding into midi_error_string_ on aborting - //! of the process) - std::string midi_parsing_errors_string_; - //! Common error string - std::string midi_error_string_; - - class SequencerTime - { - public: - //! Time buffer - double time_rest_; - //! Sample rate - uint32_t sample_rate_; - //! Size of one frame in bytes - uint32_t frame_size_; - //! Minimum possible delay, granuality - double minimum_delay_; - //! Last delay - double delay_; - - void Init() - { - sample_rate_ = 44100; - frame_size_ = 2; - Reset(); - } - - void Reset() - { - time_rest_ = 0.0; - minimum_delay_ = 1.0 / (double)(sample_rate_); - delay_ = 0.0; - } - }; - - SequencerTime midi_time_; - - public: - MidiSequencer(); - virtual ~MidiSequencer(); - - /** - * @brief Sets the RT interface - * @param intrf Pre-Initialized interface structure (pointer will be taken) - */ - void SetInterface(const MidiRealTimeInterface *intrf); - - /** - * @brief Runs ticking in a sync with audio streaming. Use this together - * with onPcmRender hook to easily play MIDI. - * @param stream pointer to the output PCM stream - * @param length length of the buffer in bytes - * @return Count of recorded data in bytes - */ - int PlayStream(uint8_t *stream, size_t length); - - /** - * @brief Returns file format type of currently loaded file - * @return File format type enumeration - */ - FileFormat GetFormat(); - - /** - * @brief Returns the number of tracks - * @return Track count - */ - size_t GetTrackCount() const; - - /** - * @brief Sets whether a track is playing - * @param track Track identifier - * @param enable Whether to enable track playback - * @return true on success, false if there was no such track - */ - bool SetTrackEnabled(size_t track, bool enable); - - /** - * @brief Disable/enable a channel is sounding - * @param channel Channel number from 0 to 15 - * @param enable Enable the channel playback - * @return true on success, false if there was no such channel - */ - bool SetChannelEnabled(size_t channel, bool enable); - - /** - * @brief Enables or disables solo on a track - * @param track Identifier of solo track, or max to disable - */ - void SetSoloTrack(size_t track); - - /** - * @brief Set the song number of a multi-song file (such as XMI) - * @param trackNumber Identifier of the song to load (or -1 to mix all songs - * as one song) - */ - void SetSongNum(int track); - - /** - * @brief Retrive the number of songs in a currently opened file - * @return Number of songs in the file. If 1 or less, means, the file has - * only one song inside. - */ - int GetSongsCount(); - - /** - * @brief Defines a handler for callback trigger events - * @param handler Handler to invoke from the sequencer when triggered, or - * nullptr. - * @param userdata Instance of the library - */ - void SetTriggerHandler(TriggerHandler handler, void *userdata); - - /** - * @brief Get string that describes reason of error - * @return Error string - */ - const std::string &GetErrorString(); - - /** - * @brief Check is loop enabled - * @return true if loop enabled - */ - bool GetLoopEnabled(); - - /** - * @brief Switch loop on/off - * @param enabled Enable loop - */ - void SetLoopEnabled(bool enabled); - - /** - * @brief Get the number of loops set - * @return number of loops or -1 if loop infinite - */ - int GetLoopsCount(); - - /** - * @brief How many times song should loop - * @param loops count or -1 to loop infinite - */ - void SetLoopsCount(int loops); - - /** - * @brief Switch loop hooks-only mode on/off - * @param enabled Don't loop: trigger hooks only without loop - */ - void SetLoopHooksOnly(bool enabled); - - /** - * @brief Get music title - * @return music title string - */ - const std::string &GetMusicTitle(); - - /** - * @brief Get music copyright notice - * @return music copyright notice string - */ - const std::string &GetMusicCopyright(); - - /** - * @brief Get list of track titles - * @return array of track title strings - */ - const std::vector &GetTrackTitles(); - - /** - * @brief Get list of MIDI markers - * @return Array of MIDI marker structures - */ - const std::vector &GetMarkers(); - - /** - * @brief Is position of song at end - * @return true if end of song was reached - */ - bool PositionAtEnd(); - - /** - * @brief Load MIDI file from a memory block - * @param data Pointer to memory block with MIDI data - * @param size Size of source memory block - * @param rate For IMF formats, the proper playback rate in Hz - * @return true if file successfully opened, false on any error - */ - bool LoadMidi(const uint8_t *data, size_t size, uint16_t rate = 0); - - /** - * @brief Load MIDI file by using FileAndMemReader interface - * @param mfr mem_file_c with opened source file - * @param rate For IMF formats, the proper playback rate in Hz - * @return true if file successfully opened, false on any error - */ - bool LoadMidi(epi::MemFile *mfr, uint16_t rate); - - /** - * @brief Periodic tick handler. - * @param s seconds since last call - * @param granularity don't expect intervals smaller than this, in seconds - * @return desired number of seconds until next call - */ - double Tick(double s, double granularity); - - /** - * @brief Change current position to specified time position in seconds - * @param granularity don't expect intervals smaller than this, in seconds - * @param seconds Absolute time position in seconds - * @return desired number of seconds until next call of Tick() - */ - double Seek(double seconds, const double granularity); - - /** - * @brief Gives current time position in seconds - * @return Current time position in seconds - */ - double Tell(); - - /** - * @brief Gives time length of current song in seconds - * @return Time length of current song in seconds - */ - double TimeLength(); - - /** - * @brief Gives loop start time position in seconds - * @return Loop start time position in seconds or -1 if song has no loop - * points - */ - double GetLoopStart(); - - /** - * @brief Gives loop end time position in seconds - * @return Loop end time position in seconds or -1 if song has no loop - * points - */ - double GetLoopEnd(); - - /** - * @brief Return to begin of current song - */ - void Rewind(); - - /** - * @brief Get current tempor multiplier value - * @return - */ - double GetTempoMultiplier(); - - /** - * @brief Set tempo multiplier - * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - - * slower - */ - void SetTempo(double tempo); - - private: -#if EDGE_IMF_SUPPORT - /** - * @brief Load file as Id-software-Music-File (Wolfenstein) - * @param mfr mem_file_c with opened source file - * @param rate For IMF formats, the proper playback rate in Hz - * @return true on successful load - */ - bool ParseIMF(epi::MemFile *mfr, uint16_t rate); -#endif - /** - * @brief Load file as EA MUS - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseRSXX(epi::MemFile *mfr); - - /** - * @brief Load file as GMD/MUS files (ScummVM) - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseGMF(epi::MemFile *mfr); - - /** - * @brief Load file as Standard MIDI file - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseSMF(epi::MemFile *mfr); - - /** - * @brief Load file as RIFF MIDI - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseRMI(epi::MemFile *mfr); -#if EDGE_MUS_SUPPORT - /** - * @brief Load file as DMX MUS file (Doom) - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseMUS(epi::MemFile *mfr); -#endif -#if EDGE_XMI_SUPPORT - /** - * @brief Load file as AIL eXtended MIdi - * @param mfr mem_file_c with opened source file - * @return true on successful load - */ - bool ParseXMI(epi::MemFile *mfr); -#endif -}; - -/** - * @brief Utility function to read Big-Endian integer from raw binary data - * @param buffer Pointer to raw binary buffer - * @param nbytes Count of bytes to parse integer - * @return Extracted unsigned integer - */ -static inline uint64_t ReadIntBigEndian(const void *buffer, size_t nbytes) -{ - uint64_t result = 0; - const uint8_t *data = (const uint8_t *)(buffer); - - for (size_t n = 0; n < nbytes; ++n) - result = (result << 8) + data[n]; - - return result; -} - -/** - * @brief Utility function to read Little-Endian integer from raw binary data - * @param buffer Pointer to raw binary buffer - * @param nbytes Count of bytes to parse integer - * @return Extracted unsigned integer - */ -static inline uint64_t ReadIntLittleEndian(const void *buffer, size_t nbytes) -{ - uint64_t result = 0; - const uint8_t *data = (const uint8_t *)(buffer); - - for (size_t n = 0; n < nbytes; ++n) - result = result + (uint64_t)(data[n] << (n * 8)); - - return result; -} - -/** - * @brief Secure Standard MIDI Variable-Length numeric value parser with - * anti-out-of-range protection - * @param [_inout] ptr Pointer to memory block that contains begin of - * variable-length value, will be iterated forward - * @param [_in end Pointer to end of memory block where variable-length value is - * stored (after end of track) - * @param [_out] ok Reference to boolean which takes result of variable-length - * value parsing - * @return Unsigned integer that conains parsed variable-length value - */ -static inline uint64_t ReadVariableLengthValue(const uint8_t **ptr, const uint8_t *end, bool &ok) -{ - uint64_t result = 0; - ok = false; - - for (;;) - { - if (*ptr >= end) - return 2; - unsigned char byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if (!(byte & 0x80)) - break; - } - - ok = true; - return result; -} - -MidiSequencer::MidiTrackRow::MidiTrackRow() : time_(0.0), delay_(0), absolute_position_(0), time_delay_(0.0) -{ -} - -void MidiSequencer::MidiTrackRow::Clear() -{ - time_ = 0.0; - delay_ = 0; - absolute_position_ = 0; - time_delay_ = 0.0; - events_.clear(); -} - -void MidiSequencer::MidiTrackRow::SortEvents(bool *note_states) -{ - typedef std::vector EvtArr; - EvtArr sysEx; - EvtArr metas; - EvtArr noteOffs; - EvtArr controllers; - EvtArr anyOther; - - for (size_t i = 0; i < events_.size(); i++) - { - if (events_[i].type == MidiEvent::kNoteOff) - { - if (noteOffs.capacity() == 0) - noteOffs.reserve(events_.size()); - noteOffs.push_back(events_[i]); - } - else if (events_[i].type == MidiEvent::kSysex || events_[i].type == MidiEvent::kSysex2) - { - if (sysEx.capacity() == 0) - sysEx.reserve(events_.size()); - sysEx.push_back(events_[i]); - } - else if ((events_[i].type == MidiEvent::kControlChange) || (events_[i].type == MidiEvent::kPatchChange) || - (events_[i].type == MidiEvent::kPitchWheel) || (events_[i].type == MidiEvent::kChannelAftertouch)) - { - if (controllers.capacity() == 0) - controllers.reserve(events_.size()); - controllers.push_back(events_[i]); - } - else if ((events_[i].type == MidiEvent::kSpecial) && - ((events_[i].sub_type == MidiEvent::kMarker) || (events_[i].sub_type == MidiEvent::kDeviceSwitch) || - (events_[i].sub_type == MidiEvent::kSongBeginHook) || - (events_[i].sub_type == MidiEvent::kLoopStart) || (events_[i].sub_type == MidiEvent::kLoopEnd) || - (events_[i].sub_type == MidiEvent::kLoopStackBegin) || - (events_[i].sub_type == MidiEvent::kLoopStackEnd) || - (events_[i].sub_type == MidiEvent::kLoopStackBreak))) - { - if (metas.capacity() == 0) - metas.reserve(events_.size()); - metas.push_back(events_[i]); - } - else - { - if (anyOther.capacity() == 0) - anyOther.reserve(events_.size()); - anyOther.push_back(events_[i]); - } - } - - /* - * If Note-Off and it's Note-On is on the same row - move this damned note - * off down! - */ - if (note_states) - { - std::set markAsOn; - for (size_t i = 0; i < anyOther.size(); i++) - { - const MidiEvent e = anyOther[i]; - if (e.type == MidiEvent::kNoteOn) - { - const size_t note_i = (size_t)(e.channel * 255) + (e.data[0] & 0x7F); - // Check, was previously note is on or off - bool wasOn = note_states[note_i]; - markAsOn.insert(note_i); - // Detect zero-length notes are following previously pressed - // note - int noteOffsOnSameNote = 0; - for (EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end();) - { - // If note was off, and note-off on same row with note-on - - // move it down! - if (((*j).channel == e.channel) && ((*j).data[0] == e.data[0])) - { - // If note is already off OR more than one note-off on - // same row and same note - if (!wasOn || (noteOffsOnSameNote != 0)) - { - anyOther.push_back(*j); - j = noteOffs.erase(j); - markAsOn.erase(note_i); - continue; - } - else - { - // When same row has many note-offs on same row - // that means a zero-length note follows previous - // note it must be shuted down - noteOffsOnSameNote++; - } - } - j++; - } - } - } - - // Mark other notes as released - for (EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end(); j++) - { - size_t note_i = (size_t)(j->channel * 255) + (j->data[0] & 0x7F); - note_states[note_i] = false; - } - - for (std::set::iterator j = markAsOn.begin(); j != markAsOn.end(); j++) - note_states[*j] = true; - } - /***********************************************************************************/ - - events_.clear(); - if (!sysEx.empty()) - events_.insert(events_.end(), sysEx.begin(), sysEx.end()); - if (!noteOffs.empty()) - events_.insert(events_.end(), noteOffs.begin(), noteOffs.end()); - if (!metas.empty()) - events_.insert(events_.end(), metas.begin(), metas.end()); - if (!controllers.empty()) - events_.insert(events_.end(), controllers.begin(), controllers.end()); - if (!anyOther.empty()) - events_.insert(events_.end(), anyOther.begin(), anyOther.end()); -} - -MidiSequencer::MidiSequencer() - : midi_output_interface_(nullptr), midi_format_(kFormatMidi), midi_smf_format_(0), midi_loop_format_(kLoopDefault), - midi_loop_enabled_(false), midi_loop_hooks_only_(false), midi_full_song_time_length_(0.0), - midi_post_song_wait_delay_(1.0), midi_loop_start_time_(-1.0), midi_loop_end_time_(-1.0), - midi_tempo_multiplier_(1.0), midi_at_end_(false), midi_loop_count_(-1), midi_load_track_number_(0), - midi_track_solo_(~(size_t)(0)), midi_trigger_handler_(nullptr), midi_trigger_userdata_(nullptr) -{ - midi_loop_.Reset(); - midi_loop_.invalid_loop_ = false; - midi_time_.Init(); -} - -MidiSequencer::~MidiSequencer() -{ -} - -void MidiSequencer::SetInterface(const MidiRealTimeInterface *intrf) -{ - // Interface must NOT be nullptr - EPI_ASSERT(intrf); - - // Note ON hook is REQUIRED - EPI_ASSERT(intrf->rt_noteOn); - // Note OFF hook is REQUIRED - EPI_ASSERT(intrf->rt_noteOff || intrf->rt_noteOffVel); - // Note Aftertouch hook is REQUIRED - EPI_ASSERT(intrf->rt_noteAfterTouch); - // Channel Aftertouch hook is REQUIRED - EPI_ASSERT(intrf->rt_channelAfterTouch); - // Controller change hook is REQUIRED - EPI_ASSERT(intrf->rt_controllerChange); - // Patch change hook is REQUIRED - EPI_ASSERT(intrf->rt_patchChange); - // Pitch bend hook is REQUIRED - EPI_ASSERT(intrf->rt_pitchBend); - // System Exclusive hook is REQUIRED - EPI_ASSERT(intrf->rt_systemExclusive); - - if (intrf->pcmSampleRate != 0 && intrf->pcmFrameSize != 0) - { - midi_time_.sample_rate_ = intrf->pcmSampleRate; - midi_time_.frame_size_ = intrf->pcmFrameSize; - midi_time_.Reset(); - } - - midi_output_interface_ = intrf; -} - -int MidiSequencer::PlayStream(uint8_t *stream, size_t length) -{ - int count = 0; - size_t samples = (size_t)(length / (size_t)(midi_time_.frame_size_)); - size_t left = samples; - size_t periodSize = 0; - uint8_t *stream_pos = stream; - - EPI_ASSERT(midi_output_interface_->onPcmRender); - - while (left > 0) - { - const double leftDelay = left / double(midi_time_.sample_rate_); - const double maxDelay = midi_time_.time_rest_ < leftDelay ? midi_time_.time_rest_ : leftDelay; - if ((PositionAtEnd()) && (midi_time_.delay_ <= 0.0)) - break; // Stop to fetch samples at reaching the song end with - // disabled loop - - midi_time_.time_rest_ -= maxDelay; - periodSize = (size_t)((double)(midi_time_.sample_rate_) * maxDelay); - - if (stream) - { - size_t generateSize = periodSize > left ? (size_t)(left) : (size_t)(periodSize); - midi_output_interface_->onPcmRender(midi_output_interface_->onPcmRender_userdata, stream_pos, - generateSize * midi_time_.frame_size_); - stream_pos += generateSize * midi_time_.frame_size_; - count += generateSize; - left -= generateSize; - EPI_ASSERT(left <= samples); - } - - if (midi_time_.time_rest_ <= 0.0) - { - midi_time_.delay_ = Tick(midi_time_.delay_, midi_time_.minimum_delay_); - midi_time_.time_rest_ += midi_time_.delay_; - } - } - - return count * (int)(midi_time_.frame_size_); -} - -MidiSequencer::FileFormat MidiSequencer::GetFormat() -{ - return midi_format_; -} - -size_t MidiSequencer::GetTrackCount() const -{ - return midi_track_data_.size(); -} - -bool MidiSequencer::SetTrackEnabled(size_t track, bool enable) -{ - size_t track_count = midi_track_data_.size(); - if (track >= track_count) - return false; - midi_track_disabled_[track] = !enable; - return true; -} - -bool MidiSequencer::SetChannelEnabled(size_t channel, bool enable) -{ - if (channel >= 16) - return false; - - if (!enable && m_channelDisable[channel] != !enable) - { - uint8_t ch = (uint8_t)(channel); - - // Releae all pedals - midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, ch, 64, 0); - midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, ch, 66, 0); - - // Release all notes on the channel now - for (int i = 0; i < 127; ++i) - { - if (midi_output_interface_->rt_noteOff) - midi_output_interface_->rt_noteOff(midi_output_interface_->rtUserData, ch, i); - if (midi_output_interface_->rt_noteOffVel) - midi_output_interface_->rt_noteOffVel(midi_output_interface_->rtUserData, ch, i, 0); - } - } - - m_channelDisable[channel] = !enable; - return true; -} - -void MidiSequencer::SetSoloTrack(size_t track) -{ - midi_track_solo_ = track; -} - -void MidiSequencer::SetSongNum(int track) -{ - midi_load_track_number_ = track; -#if EDGE_XMI_SUPPORT - if (!midi_raw_songs_data_.empty() && midi_format_ == kFormatXMidi) // Reload the song - { - if (midi_load_track_number_ >= (int)midi_raw_songs_data_.size()) - midi_load_track_number_ = midi_raw_songs_data_.size() - 1; - - if (midi_output_interface_ && midi_output_interface_->rt_controllerChange) - { - for (int i = 0; i < 15; i++) - midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, i, 123, 0); - } - - midi_at_end_ = false; - midi_loop_.FullReset(); - midi_loop_.caught_start_ = true; - - midi_smf_format_ = 0; - - epi::MemFile *mfr = new epi::MemFile(midi_raw_songs_data_[midi_load_track_number_].data(), - midi_raw_songs_data_[midi_load_track_number_].size()); - ParseSMF(mfr); - - midi_format_ = kFormatXMidi; - } -#endif -} - -int MidiSequencer::GetSongsCount() -{ - return (int)midi_raw_songs_data_.size(); -} - -void MidiSequencer::SetTriggerHandler(TriggerHandler handler, void *userdata) -{ - midi_trigger_handler_ = handler; - midi_trigger_userdata_ = userdata; -} - -const std::string &MidiSequencer::GetErrorString() -{ - return midi_error_string_; -} - -bool MidiSequencer::GetLoopEnabled() -{ - return midi_loop_enabled_; -} - -void MidiSequencer::SetLoopEnabled(bool enabled) -{ - midi_loop_enabled_ = enabled; -} - -int MidiSequencer::GetLoopsCount() -{ - return midi_loop_count_ >= 0 ? (midi_loop_count_ + 1) : midi_loop_count_; -} - -void MidiSequencer::SetLoopsCount(int loops) -{ - if (loops >= 1) - loops -= 1; // Internally, loops count has the 0 base - midi_loop_count_ = loops; -} - -void MidiSequencer::SetLoopHooksOnly(bool enabled) -{ - midi_loop_hooks_only_ = enabled; -} - -const std::string &MidiSequencer::GetMusicTitle() -{ - return midi_music_title_; -} - -const std::string &MidiSequencer::GetMusicCopyright() -{ - return midi_music_copyright_; -} - -const std::vector &MidiSequencer::GetTrackTitles() -{ - return midi_music_track_titles_; -} - -const std::vector &MidiSequencer::GetMarkers() -{ - return midi_music_markers_; -} - -bool MidiSequencer::PositionAtEnd() -{ - return midi_at_end_; -} - -double MidiSequencer::GetTempoMultiplier() -{ - return midi_tempo_multiplier_; -} - -void MidiSequencer::BuildSMFSetupReset(size_t track_count) -{ - midi_full_song_time_length_ = 0.0; - midi_loop_start_time_ = -1.0; - midi_loop_end_time_ = -1.0; - midi_loop_format_ = kLoopDefault; - midi_track_disabled_.clear(); - EPI_CLEAR_MEMORY(m_channelDisable, bool, 16); - midi_track_solo_ = ~(size_t)0; - midi_music_title_.clear(); - midi_music_copyright_.clear(); - midi_music_track_titles_.clear(); - midi_music_markers_.clear(); - midi_track_data_.clear(); - midi_track_data_.resize(track_count, std::list()); - midi_track_disabled_.resize(track_count); - - midi_loop_.Reset(); - midi_loop_.invalid_loop_ = false; - midi_time_.Reset(); - - midi_current_position_.began = false; - midi_current_position_.absolute_time_position = 0.0; - midi_current_position_.wait = 0.0; - midi_current_position_.track.clear(); - midi_current_position_.track.resize(track_count); -} - -bool MidiSequencer::BuildSMFTrackData(const std::vector> &track_data) -{ - const size_t track_count = track_data.size(); - BuildSMFSetupReset(track_count); - - bool gotGlobalLoopStart = false, gotGlobalLoopEnd = false, gotStackLoopStart = false, gotLoopEventInThisRow = false; - - //! Tick position of loop start tag - uint64_t loop_start_ticks = 0; - //! Tick position of loop end tag - uint64_t loop_end_ticks = 0; - //! Full length of song in ticks - uint64_t ticksSongLength = 0; - //! Cache for error message strign - char error[150]; - - //! Caches note on/off states. - bool note_states[16 * 255]; - /* This is required to carefully detect zero-length notes * - * and avoid a move of "note-off" event over "note-on" while sort. * - * Otherwise, after sort those notes will play infinite sound */ - - //! Tempo change events list - std::vector temposList; - - /* - * TODO: Make this be safer for memory in case of broken input data - * which may cause going away of available track data (and then give a - * crash!) - * - * POST: Check this more carefully for possible vulnuabilities are can crash - * this - */ - for (size_t tk = 0; tk < track_count; ++tk) - { - uint64_t abs_position = 0; - int status = 0; - MidiEvent event; - bool ok = false; - const uint8_t *end = track_data[tk].data() + track_data[tk].size(); - const uint8_t *trackPtr = track_data[tk].data(); - EPI_CLEAR_MEMORY(note_states, bool, 4080); - - // Time delay that follows the first event in the track - { - MidiTrackRow evtPos; - if (midi_format_ == kFormatRSXX) - ok = true; - else - evtPos.delay_ = ReadVariableLengthValue(&trackPtr, end, ok); - if (!ok) - { - int len = stbsp_snprintf(error, 150, - "buildTrackData: Can't read variable-length " - "value at begin of track %d.\n", - (int)tk); - if ((len > 0) && (len < 150)) - midi_parsing_errors_string_ += std::string(error, (size_t)len); - return false; - } - - // HACK: Begin every track with "Reset all controllers" event to - // avoid controllers state break came from end of song - if (tk == 0) - { - MidiEvent resetEvent; - resetEvent.type = MidiEvent::kSpecial; - resetEvent.sub_type = MidiEvent::kSongBeginHook; - evtPos.events_.push_back(resetEvent); - } - - evtPos.absolute_position_ = abs_position; - abs_position += evtPos.delay_; - midi_track_data_[tk].push_back(evtPos); - } - - MidiTrackRow evtPos; - do - { - event = ParseEvent(&trackPtr, end, status); - if (!event.is_valid) - { - int len = stbsp_snprintf(error, 150, "buildTrackData: Fail to parse event in the track %d.\n", (int)tk); - if ((len > 0) && (len < 150)) - midi_parsing_errors_string_ += std::string(error, (size_t)len); - return false; - } - - evtPos.events_.push_back(event); - if (event.type == MidiEvent::kSpecial) - { - if (event.sub_type == MidiEvent::kTempoChange) - { - event.absolute_tick_position = abs_position; - temposList.push_back(event); - } - else if (!midi_loop_.invalid_loop_ && (event.sub_type == MidiEvent::kLoopStart)) - { - /* - * loopStart is invalid when: - * - starts together with loopEnd - * - appears more than one time in same MIDI file - */ - if (gotGlobalLoopStart || gotLoopEventInThisRow) - midi_loop_.invalid_loop_ = true; - else - { - gotGlobalLoopStart = true; - loop_start_ticks = abs_position; - } - // In this row we got loop event, register this! - gotLoopEventInThisRow = true; - } - else if (!midi_loop_.invalid_loop_ && (event.sub_type == MidiEvent::kLoopEnd)) - { - /* - * loopEnd is invalid when: - * - starts before loopStart - * - starts together with loopStart - * - appars more than one time in same MIDI file - */ - if (gotGlobalLoopEnd || gotLoopEventInThisRow) - { - midi_loop_.invalid_loop_ = true; - if (midi_output_interface_->onDebugMessage) - { - midi_output_interface_->onDebugMessage( - midi_output_interface_->onDebugMessage_userdata, "== Invalid loop detected! %s %s ==", - (gotGlobalLoopEnd ? "[Caught more than 1 loopEnd!]" : ""), - (gotLoopEventInThisRow ? "[loopEnd in same row as loopStart!]" : "")); - } - } - else - { - gotGlobalLoopEnd = true; - loop_end_ticks = abs_position; - } - // In this row we got loop event, register this! - gotLoopEventInThisRow = true; - } - else if (!midi_loop_.invalid_loop_ && (event.sub_type == MidiEvent::kLoopStackBegin)) - { - if (!gotStackLoopStart) - { - if (!gotGlobalLoopStart) - loop_start_ticks = abs_position; - gotStackLoopStart = true; - } - - midi_loop_.StackUp(); - if (midi_loop_.stack_level_ >= (int)(midi_loop_.stack_.size())) - { - LoopStackEntry e; - e.loops = event.data[0]; - e.infinity = (event.data[0] == 0); - e.start = abs_position; - e.end = abs_position; - midi_loop_.stack_.push_back(e); - } - } - else if (!midi_loop_.invalid_loop_ && ((event.sub_type == MidiEvent::kLoopStackEnd) || - (event.sub_type == MidiEvent::kLoopStackBreak))) - { - if (midi_loop_.stack_level_ <= -1) - { - midi_loop_.invalid_loop_ = true; // Caught loop end without of loop start! - if (midi_output_interface_->onDebugMessage) - { - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "== Invalid loop detected! [Caught loop end " - "without of loop start] =="); - } - } - else - { - if (loop_end_ticks < abs_position) - loop_end_ticks = abs_position; - midi_loop_.GetCurrentStack().end = abs_position; - midi_loop_.StackDown(); - } - } - } - - if (event.sub_type != MidiEvent::kEndTrack) // Don't try to read delta after - // EndOfTrack event! - { - evtPos.delay_ = ReadVariableLengthValue(&trackPtr, end, ok); - if (!ok) - { - /* End of track has been reached! However, there is no EOT - * event presented */ - event.type = MidiEvent::kSpecial; - event.sub_type = MidiEvent::kEndTrack; - } - } - - if ((evtPos.delay_ > 0) || (event.sub_type == MidiEvent::kEndTrack)) - { - evtPos.absolute_position_ = abs_position; - abs_position += evtPos.delay_; - evtPos.SortEvents(note_states); - midi_track_data_[tk].push_back(evtPos); - evtPos.Clear(); - gotLoopEventInThisRow = false; - } - } while ((trackPtr <= end) && (event.sub_type != MidiEvent::kEndTrack)); - - if (ticksSongLength < abs_position) - ticksSongLength = abs_position; - // Set the chain of events begin - if (midi_track_data_[tk].size() > 0) - midi_current_position_.track[tk].pos = midi_track_data_[tk].begin(); - } - - if (gotGlobalLoopStart && !gotGlobalLoopEnd) - { - gotGlobalLoopEnd = true; - loop_end_ticks = ticksSongLength; - } - - // loopStart must be located before loopEnd! - if (loop_start_ticks >= loop_end_ticks) - { - midi_loop_.invalid_loop_ = true; - if (midi_output_interface_->onDebugMessage && (gotGlobalLoopStart || gotGlobalLoopEnd)) - { - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "== Invalid loop detected! [loopEnd is " - "going before loopStart] =="); - } - } - - BuildTimeLine(temposList, loop_start_ticks, loop_end_ticks); - - return true; -} - -void MidiSequencer::BuildTimeLine(const std::vector &tempos, uint64_t loop_start_ticks, - uint64_t loop_end_ticks) -{ - const size_t track_count = midi_track_data_.size(); - /********************************************************************************/ - // Calculate time basing on collected tempo events - /********************************************************************************/ - for (size_t tk = 0; tk < track_count; ++tk) - { - MidiFraction currentTempo = midi_tempo_; - double time = 0.0; - size_t tempo_change_index = 0; - std::list &track = midi_track_data_[tk]; - if (track.empty()) - continue; // Empty track is useless! - - MidiTrackRow *posPrev = &(*(track.begin())); // First element - for (std::list::iterator it = track.begin(); it != track.end(); it++) - { - MidiTrackRow &pos = *it; - if ((posPrev != &pos) && // Skip first event - (!tempos.empty()) && // Only when in-track tempo events are - // available - (tempo_change_index < tempos.size())) - { - // If tempo event is going between of current and previous event - if (tempos[tempo_change_index].absolute_tick_position <= pos.absolute_position_) - { - // Stop points: begin point and tempo change points are - // before end point - std::vector points; - MidiFraction t; - TempoChangePoint firstPoint = {posPrev->absolute_position_, currentTempo}; - points.push_back(firstPoint); - - // Collect tempo change points between previous and current - // events - do - { - TempoChangePoint tempoMarker; - const MidiEvent &tempoPoint = tempos[tempo_change_index]; - tempoMarker.absolute_position = tempoPoint.absolute_tick_position; - tempoMarker.tempo = - midi_individual_tick_delta_ * - MidiFraction(ReadIntBigEndian(tempoPoint.data.data(), tempoPoint.data.size())); - points.push_back(tempoMarker); - tempo_change_index++; - } while ((tempo_change_index < tempos.size()) && - (tempos[tempo_change_index].absolute_tick_position <= pos.absolute_position_)); - - // Re-calculate time delay of previous event - time -= posPrev->time_delay_; - posPrev->time_delay_ = 0.0; - - for (size_t i = 0, j = 1; j < points.size(); i++, j++) - { - /* If one or more tempo events are appears between of - * two events, calculate delays between each tempo - * point, begin and end */ - uint64_t midDelay = 0; - // Delay between points - midDelay = points[j].absolute_position - points[i].absolute_position; - // Time delay between points - t = midDelay * currentTempo; - posPrev->time_delay_ += t.Value(); - - // Apply next tempo - currentTempo = points[j].tempo; - } - // Then calculate time between last tempo change point and - // end point - TempoChangePoint tailTempo = points.back(); - uint64_t postDelay = pos.absolute_position_ - tailTempo.absolute_position; - t = postDelay * currentTempo; - posPrev->time_delay_ += t.Value(); - - // Store Common time delay - posPrev->time_ = time; - time += posPrev->time_delay_; - } - } - - MidiFraction t = pos.delay_ * currentTempo; - pos.time_delay_ = t.Value(); - pos.time_ = time; - time += pos.time_delay_; - - // Capture markers after time value calculation - for (size_t i = 0; i < pos.events_.size(); i++) - { - MidiEvent &e = pos.events_[i]; - if ((e.type == MidiEvent::kSpecial) && (e.sub_type == MidiEvent::kMarker)) - { - MidiMarkerEntry marker; - marker.label = std::string((char *)e.data.data(), e.data.size()); - marker.position_ticks = pos.absolute_position_; - marker.position_time = pos.time_; - midi_music_markers_.push_back(marker); - } - } - - // Capture loop points time positions - if (!midi_loop_.invalid_loop_) - { - // Set loop points times - if (loop_start_ticks == pos.absolute_position_) - midi_loop_start_time_ = pos.time_; - else if (loop_end_ticks == pos.absolute_position_) - midi_loop_end_time_ = pos.time_; - } - posPrev = &pos; - } - - if (time > midi_full_song_time_length_) - midi_full_song_time_length_ = time; - } - - midi_full_song_time_length_ += midi_post_song_wait_delay_; - // Set begin of the music - midi_track_begin_position_ = midi_current_position_; - // Initial loop position will begin at begin of track until passing of the - // loop point - midi_loop_begin_position_ = midi_current_position_; - // Set lowest level of the loop stack - midi_loop_.stack_level_ = -1; - - // Set the count of loops - midi_loop_.loops_count_ = midi_loop_count_; - midi_loop_.loops_left_ = midi_loop_count_; - - /********************************************************************************/ - // Find and set proper loop points - /********************************************************************************/ - if (!midi_loop_.invalid_loop_ && !midi_current_position_.track.empty()) - { - unsigned caughLoopStart = 0; - bool scanDone = false; - const size_t ctrack_count = midi_current_position_.track.size(); - Position rowPosition(midi_current_position_); - - while (!scanDone) - { - const Position rowBeginPosition(rowPosition); - - for (size_t tk = 0; tk < ctrack_count; ++tk) - { - Position::TrackInfo &track = rowPosition.track[tk]; - if ((track.lastHandledEvent >= 0) && (track.delay <= 0)) - { - // Check is an end of track has been reached - if (track.pos == midi_track_data_[tk].end()) - { - track.lastHandledEvent = -1; - continue; - } - - for (size_t i = 0; i < track.pos->events_.size(); i++) - { - const MidiEvent &evt = track.pos->events_[i]; - if (evt.type == MidiEvent::kSpecial && evt.sub_type == MidiEvent::kLoopStart) - { - caughLoopStart++; - scanDone = true; - break; - } - } - - if (track.lastHandledEvent >= 0) - { - track.delay += track.pos->delay_; - track.pos++; - } - } - } - - // Find a shortest delay from all track - uint64_t shortestDelay = 0; - bool shortestDelayNotFound = true; - - for (size_t tk = 0; tk < ctrack_count; ++tk) - { - Position::TrackInfo &track = rowPosition.track[tk]; - if ((track.lastHandledEvent >= 0) && (shortestDelayNotFound || track.delay < shortestDelay)) - { - shortestDelay = track.delay; - shortestDelayNotFound = false; - } - } - - // Schedule the next playevent to be processed after that delay - for (size_t tk = 0; tk < ctrack_count; ++tk) - rowPosition.track[tk].delay -= shortestDelay; - - if (caughLoopStart > 0) - { - midi_loop_begin_position_ = rowBeginPosition; - midi_loop_begin_position_.absolute_time_position = midi_loop_start_time_; - scanDone = true; - } - - if (shortestDelayNotFound) - break; - } - } -} - -bool MidiSequencer::ProcessEvents(bool is_seek) -{ - if (midi_current_position_.track.size() == 0) - midi_at_end_ = true; // No MIDI track data to play - if (midi_at_end_) - return false; // No more events in the queue - - midi_loop_.caught_end_ = false; - const size_t track_count = midi_current_position_.track.size(); - const Position rowBeginPosition(midi_current_position_); - bool doLoopJump = false; - unsigned caughLoopStart = 0; - unsigned caughLoopStackStart = 0; - unsigned caughLoopStackEnds = 0; - double caughLoopStackEndsTime = 0.0; - unsigned caughLoopStackBreaks = 0; - - for (size_t tk = 0; tk < track_count; ++tk) - { - Position::TrackInfo &track = midi_current_position_.track[tk]; - if ((track.lastHandledEvent >= 0) && (track.delay <= 0)) - { - // Check is an end of track has been reached - if (track.pos == midi_track_data_[tk].end()) - { - track.lastHandledEvent = -1; - break; - } - - // Handle event - for (size_t i = 0; i < track.pos->events_.size(); i++) - { - const MidiEvent &evt = track.pos->events_[i]; - - if (is_seek && (evt.type == MidiEvent::kNoteOn)) - continue; - handleEvent(tk, evt, track.lastHandledEvent); - - if (midi_loop_.caught_start_) - { - if (midi_output_interface_->onloopStart) // Loop Start hook - midi_output_interface_->onloopStart(midi_output_interface_->onloopStart_userdata); - - caughLoopStart++; - midi_loop_.caught_start_ = false; - } - - if (midi_loop_.caught_stack_start_) - { - if (midi_output_interface_->onloopStart && - (midi_loop_start_time_ >= track.pos->time_)) // Loop Start hook - midi_output_interface_->onloopStart(midi_output_interface_->onloopStart_userdata); - - caughLoopStackStart++; - midi_loop_.caught_stack_start_ = false; - } - - if (midi_loop_.caught_stack_break_) - { - caughLoopStackBreaks++; - midi_loop_.caught_stack_break_ = false; - } - - if (midi_loop_.caught_end_ || midi_loop_.IsStackEnd()) - { - if (midi_loop_.caught_stack_end_) - { - midi_loop_.caught_stack_end_ = false; - caughLoopStackEnds++; - caughLoopStackEndsTime = track.pos->time_; - } - doLoopJump = true; - break; // Stop event handling on catching loopEnd event! - } - } - - // Read next event time (unless the track just ended) - if (track.lastHandledEvent >= 0) - { - track.delay += track.pos->delay_; - track.pos++; - } - - if (doLoopJump) - break; - } - } - - // Find a shortest delay from all track - uint64_t shortestDelay = 0; - bool shortestDelayNotFound = true; - - for (size_t tk = 0; tk < track_count; ++tk) - { - Position::TrackInfo &track = midi_current_position_.track[tk]; - if ((track.lastHandledEvent >= 0) && (shortestDelayNotFound || track.delay < shortestDelay)) - { - shortestDelay = track.delay; - shortestDelayNotFound = false; - } - } - - // Schedule the next playevent to be processed after that delay - for (size_t tk = 0; tk < track_count; ++tk) - midi_current_position_.track[tk].delay -= shortestDelay; - - MidiFraction t = shortestDelay * midi_tempo_; - - midi_current_position_.wait += t.Value(); - - if (caughLoopStart > 0 && midi_loop_begin_position_.absolute_time_position <= 0.0) - midi_loop_begin_position_ = rowBeginPosition; - - if (caughLoopStackStart > 0) - { - while (caughLoopStackStart > 0) - { - midi_loop_.StackUp(); - LoopStackEntry &s = midi_loop_.GetCurrentStack(); - s.start_position = rowBeginPosition; - caughLoopStackStart--; - } - return true; - } - - if (caughLoopStackBreaks > 0) - { - while (caughLoopStackBreaks > 0) - { - LoopStackEntry &s = midi_loop_.GetCurrentStack(); - s.loops = 0; - s.infinity = false; - // Quit the loop - midi_loop_.StackDown(); - caughLoopStackBreaks--; - } - } - - if (caughLoopStackEnds > 0) - { - while (caughLoopStackEnds > 0) - { - LoopStackEntry &s = midi_loop_.GetCurrentStack(); - if (s.infinity) - { - if (midi_output_interface_->onloopEnd && - (midi_loop_end_time_ >= caughLoopStackEndsTime)) // Loop End hook - { - midi_output_interface_->onloopEnd(midi_output_interface_->onloopEnd_userdata); - if (midi_loop_hooks_only_) // Stop song on reaching loop - // end - { - midi_at_end_ = true; // Don't handle events anymore - midi_current_position_.wait += midi_post_song_wait_delay_; // One second delay - // until stop playing - } - } - - midi_current_position_ = s.start_position; - midi_loop_.skip_stack_start_ = true; - - for (uint8_t i = 0; i < 16; i++) - midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, i, 123, 0); - - return true; - } - else if (s.loops >= 0) - { - s.loops--; - if (s.loops > 0) - { - midi_current_position_ = s.start_position; - midi_loop_.skip_stack_start_ = true; - - for (uint8_t i = 0; i < 16; i++) - midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, i, 123, 0); - - return true; - } - else - { - // Quit the loop - midi_loop_.StackDown(); - } - } - else - { - // Quit the loop - midi_loop_.StackDown(); - } - caughLoopStackEnds--; - } - - return true; - } - - if (shortestDelayNotFound || midi_loop_.caught_end_) - { - if (midi_output_interface_->onloopEnd) // Loop End hook - midi_output_interface_->onloopEnd(midi_output_interface_->onloopEnd_userdata); - - for (uint8_t i = 0; i < 16; i++) - midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, i, 123, 0); - - // Loop if song end or loop end point has reached - midi_loop_.caught_end_ = false; - shortestDelay = 0; - - if (!midi_loop_enabled_ || - (shortestDelayNotFound && midi_loop_.loops_count_ >= 0 && midi_loop_.loops_left_ < 1) || - midi_loop_hooks_only_) - { - midi_at_end_ = true; // Don't handle events anymore - midi_current_position_.wait += midi_post_song_wait_delay_; // One second delay until stop - // playing - return true; // We have caugh end here! - } - - if (midi_loop_.temporary_broken_) - { - midi_current_position_ = midi_track_begin_position_; - midi_loop_.temporary_broken_ = false; - } - else if (midi_loop_.loops_count_ < 0 || midi_loop_.loops_left_ >= 1) - { - midi_current_position_ = midi_loop_begin_position_; - if (midi_loop_.loops_count_ >= 1) - midi_loop_.loops_left_--; - } - } - - return true; // Has events in queue -} - -MidiSequencer::MidiEvent MidiSequencer::ParseEvent(const uint8_t **pptr, const uint8_t *end, int &status) -{ - const uint8_t *&ptr = *pptr; - MidiSequencer::MidiEvent evt; - - if (ptr + 1 > end) - { - // When track doesn't ends on the middle of event data, it's must be - // fine - evt.type = MidiEvent::kSpecial; - evt.sub_type = MidiEvent::kEndTrack; - return evt; - } - - unsigned char byte = *(ptr++); - bool ok = false; - - if (byte == MidiEvent::kSysex || byte == MidiEvent::kSysex2) // Ignore SysEx - { - uint64_t length = ReadVariableLengthValue(pptr, end, ok); - if (!ok || (ptr + length > end)) - { - midi_parsing_errors_string_ += "ParseEvent: Can't read SysEx event - Unexpected end of track " - "data.\n"; - evt.is_valid = 0; - return evt; - } - evt.type = MidiEvent::kSysex; - evt.data.clear(); - evt.data.push_back(byte); - std::copy(ptr, ptr + length, std::back_inserter(evt.data)); - ptr += (size_t)length; - return evt; - } - - if (byte == MidiEvent::kSpecial) - { - // Special event FF - uint8_t evtype = *(ptr++); - uint64_t length = ReadVariableLengthValue(pptr, end, ok); - if (!ok || (ptr + length > end)) - { - midi_parsing_errors_string_ += "ParseEvent: Can't read Special event - Unexpected end of " - "track data.\n"; - evt.is_valid = 0; - return evt; - } - std::string data(length ? (const char *)ptr : nullptr, (size_t)length); - ptr += (size_t)length; - - evt.type = byte; - evt.sub_type = evtype; - evt.data.insert(evt.data.begin(), data.begin(), data.end()); - - /* TODO: Store those meta-strings separately and give ability to read - * them by external functions (to display song title and copyright in - * the player) */ - if (evt.sub_type == MidiEvent::kCopyright) - { - if (midi_music_copyright_.empty()) - { - midi_music_copyright_ = std::string((const char *)evt.data.data(), evt.data.size()); - midi_music_copyright_.push_back('\0'); /* ending fix for UTF16 strings */ - if (midi_output_interface_->onDebugMessage) - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Music copyright: %s", midi_music_copyright_.c_str()); - } - else if (midi_output_interface_->onDebugMessage) - { - std::string str((const char *)evt.data.data(), evt.data.size()); - str.push_back('\0'); /* ending fix for UTF16 strings */ - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Extra copyright event: %s", str.c_str()); - } - } - else if (evt.sub_type == MidiEvent::kSequenceTrackTitle) - { - if (midi_music_title_.empty()) - { - midi_music_title_ = std::string((const char *)evt.data.data(), evt.data.size()); - midi_music_title_.push_back('\0'); /* ending fix for UTF16 strings */ - if (midi_output_interface_->onDebugMessage) - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Music title: %s", midi_music_title_.c_str()); - } - else - { - std::string str((const char *)evt.data.data(), evt.data.size()); - str.push_back('\0'); /* ending fix for UTF16 strings */ - midi_music_track_titles_.push_back(str); - if (midi_output_interface_->onDebugMessage) - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Track title: %s", str.c_str()); - } - } - else if (evt.sub_type == MidiEvent::kInstrumentTitle) - { - if (midi_output_interface_->onDebugMessage) - { - std::string str((const char *)evt.data.data(), evt.data.size()); - str.push_back('\0'); /* ending fix for UTF16 strings */ - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Instrument: %s", str.c_str()); - } - } - else if (evt.sub_type == MidiEvent::kMarker) - { - // To lower - for (size_t i = 0; i < data.size(); i++) - { - if (data[i] <= 'Z' && data[i] >= 'A') - data[i] = (char)(data[i] - ('Z' - 'z')); - } - - if (data == "loopstart") - { - // Return a custom Loop Start event instead of Marker - evt.sub_type = MidiEvent::kLoopStart; - evt.data.clear(); // Data is not needed - return evt; - } - - if (data == "loopend") - { - // Return a custom Loop End event instead of Marker - evt.sub_type = MidiEvent::kLoopEnd; - evt.data.clear(); // Data is not needed - return evt; - } - - if (data.substr(0, 10) == "loopstart=") - { - evt.type = MidiEvent::kSpecial; - evt.sub_type = MidiEvent::kLoopStackBegin; - uint8_t loops = (uint8_t)(atoi(data.substr(10).c_str())); - evt.data.clear(); - evt.data.push_back(loops); - - if (midi_output_interface_->onDebugMessage) - { - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Stack Marker Loop Start at %d to %d level with %d " - "loops", - midi_loop_.stack_level_, midi_loop_.stack_level_ + 1, loops); - } - return evt; - } - - if (data.substr(0, 8) == "loopend=") - { - evt.type = MidiEvent::kSpecial; - evt.sub_type = MidiEvent::kLoopStackEnd; - evt.data.clear(); - - if (midi_output_interface_->onDebugMessage) - { - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Stack Marker Loop %s at %d to %d level", - (evt.sub_type == MidiEvent::kLoopStackEnd ? "End" : "Break"), - midi_loop_.stack_level_, midi_loop_.stack_level_ - 1); - } - return evt; - } - } - - if (evtype == MidiEvent::kEndTrack) - status = -1; // Finalize track - - return evt; - } - - // Any normal event (80..EF) - if (byte < 0x80) - { - byte = (uint8_t)(status | 0x80); - ptr--; - } - - // Sys Com Song Select(Song #) [0-127] - if (byte == MidiEvent::kSysComSongSelect) - { - if (ptr + 1 > end) - { - midi_parsing_errors_string_ += "ParseEvent: Can't read System Command Song Select event - " - "Unexpected end of track data.\n"; - evt.is_valid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - return evt; - } - - // Sys Com Song Position Pntr [LSB, MSB] - if (byte == MidiEvent::kSysComSongPositionPointer) - { - if (ptr + 2 > end) - { - midi_parsing_errors_string_ += "ParseEvent: Can't read System Command Position Pointer event " - "- Unexpected end of track data.\n"; - evt.is_valid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - return evt; - } - - uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F; - status = byte; - evt.channel = midCh; - evt.type = evType; - - switch (evType) - { - case MidiEvent::kNoteOff: // 2 byte length - case MidiEvent::kNoteOn: - case MidiEvent::kNoteTouch: - case MidiEvent::kControlChange: - case MidiEvent::kPitchWheel: - if (ptr + 2 > end) - { - midi_parsing_errors_string_ += "ParseEvent: Can't read regular 2-byte event - Unexpected " - "end of track data.\n"; - evt.is_valid = 0; - return evt; - } - - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - - if ((evType == MidiEvent::kNoteOn) && (evt.data[1] == 0)) - { - evt.type = MidiEvent::kNoteOff; // Note ON with zero velocity - // is Note OFF! - } - else if (evType == MidiEvent::kControlChange) - { - // 111'th loopStart controller (RPG Maker and others) - if (midi_format_ == kFormatMidi) - { - switch (evt.data[0]) - { - case 110: - if (midi_loop_format_ == kLoopDefault) - { - // Change event type to custom Loop Start event - // and clear data - evt.type = MidiEvent::kSpecial; - evt.sub_type = MidiEvent::kLoopStart; - evt.data.clear(); - midi_loop_format_ = kLoopHmi; - } - else if (midi_loop_format_ == kLoopHmi) - { - // Repeating of 110'th point is BAD practice, - // treat as EMIDI - midi_loop_format_ = kLoopEMidi; - } - break; - - case 111: - if (midi_loop_format_ == kLoopHmi) - { - // Change event type to custom Loop End event - // and clear data - evt.type = MidiEvent::kSpecial; - evt.sub_type = MidiEvent::kLoopEnd; - evt.data.clear(); - } - else if (midi_loop_format_ != kLoopEMidi) - { - // Change event type to custom Loop Start event - // and clear data - evt.type = MidiEvent::kSpecial; - evt.sub_type = MidiEvent::kLoopStart; - evt.data.clear(); - } - break; - - case 113: - if (midi_loop_format_ == kLoopEMidi) - { - // EMIDI does using of CC113 with same purpose - // as CC7 - evt.data[0] = 7; - } - break; - } - } -#if EDGE_XMI_SUPPORT - if (midi_format_ == kFormatXMidi) - { - switch (evt.data[0]) - { - case 116: // For Loop Controller - evt.type = MidiEvent::kSpecial; - evt.sub_type = MidiEvent::kLoopStackBegin; - evt.data[0] = evt.data[1]; - evt.data.pop_back(); - - if (midi_output_interface_->onDebugMessage) - { - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Stack XMI Loop Start at %d to %d level " - "with %d loops", - midi_loop_.stack_level_, midi_loop_.stack_level_ + 1, - evt.data[0]); - } - break; - - case 117: // Next/Break Loop Controller - evt.type = MidiEvent::kSpecial; - evt.sub_type = evt.data[1] < 64 ? MidiEvent::kLoopStackBreak : MidiEvent::kLoopStackEnd; - evt.data.clear(); - - if (midi_output_interface_->onDebugMessage) - { - midi_output_interface_->onDebugMessage( - midi_output_interface_->onDebugMessage_userdata, "Stack XMI Loop %s at %d to %d level", - (evt.sub_type == MidiEvent::kLoopStackEnd ? "End" : "Break"), midi_loop_.stack_level_, - midi_loop_.stack_level_ - 1); - } - break; - - case 119: // Callback Trigger - evt.type = MidiEvent::kSpecial; - evt.sub_type = MidiEvent::kCallbackTrigger; - evt.data.assign(1, evt.data[1]); - break; - } - } -#endif - } - - return evt; - case MidiEvent::kPatchChange: // 1 byte length - case MidiEvent::kChannelAftertouch: - if (ptr + 1 > end) - { - midi_parsing_errors_string_ += "ParseEvent: Can't read regular 1-byte event - Unexpected " - "end of track data.\n"; - evt.is_valid = 0; - return evt; - } - evt.data.push_back(*(ptr++)); - return evt; - default: - break; - } - - return evt; -} - -void MidiSequencer::handleEvent(size_t track, const MidiSequencer::MidiEvent &evt, int32_t &status) -{ - if (track == 0 && midi_smf_format_ < 2 && evt.type == MidiEvent::kSpecial && - (evt.sub_type == MidiEvent::kTempoChange || evt.sub_type == MidiEvent::kTimeSignature)) - { - /* never reject track 0 timing events on SMF format != 2 - note: multi-track XMI convert to format 2 SMF */ - } - else - { - if (midi_track_solo_ != ~(size_t)(0) && track != midi_track_solo_) - return; - if (midi_track_disabled_[track]) - return; - } - - if (midi_output_interface_->onEvent) - { - midi_output_interface_->onEvent(midi_output_interface_->onEvent_userdata, evt.type, evt.sub_type, evt.channel, - evt.data.data(), evt.data.size()); - } - - if (evt.type == MidiEvent::kSysex || evt.type == MidiEvent::kSysex2) // Ignore SysEx - { - midi_output_interface_->rt_systemExclusive(midi_output_interface_->rtUserData, evt.data.data(), - evt.data.size()); - return; - } - - if (evt.type == MidiEvent::kSpecial) - { - // Special event FF - uint_fast16_t evtype = evt.sub_type; - uint64_t length = (uint64_t)(evt.data.size()); - const char *data(length ? (const char *)(evt.data.data()) : "\0\0\0\0\0\0\0\0"); - - if (midi_output_interface_->rt_metaEvent) // Meta event hook - midi_output_interface_->rt_metaEvent(midi_output_interface_->rtUserData, evtype, (const uint8_t *)(data), - size_t(length)); - - if (evtype == MidiEvent::kEndTrack) // End Of Track - { - status = -1; - return; - } - - if (evtype == MidiEvent::kTempoChange) // Tempo change - { - midi_tempo_ = - midi_individual_tick_delta_ * MidiFraction(ReadIntBigEndian(evt.data.data(), evt.data.size())); - return; - } - - if (evtype == MidiEvent::kMarker) // Meta event - { - // Do nothing! :-P - return; - } - - if (evtype == MidiEvent::kDeviceSwitch) - { - if (midi_output_interface_->onDebugMessage) - midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, - "Switching another device: %s", data); - if (midi_output_interface_->rt_deviceSwitch) - midi_output_interface_->rt_deviceSwitch(midi_output_interface_->rtUserData, track, data, - size_t(length)); - return; - } - - // Turn on Loop handling when loop is enabled - if (midi_loop_enabled_ && !midi_loop_.invalid_loop_) - { - if (evtype == MidiEvent::kLoopStart) // Special non-spec MIDI - // loop Start point - { - midi_loop_.caught_start_ = true; - return; - } - - if (evtype == MidiEvent::kLoopEnd) // Special non-spec MIDI loop End point - { - midi_loop_.caught_end_ = true; - return; - } - - if (evtype == MidiEvent::kLoopStackBegin) - { - if (midi_loop_.skip_stack_start_) - { - midi_loop_.skip_stack_start_ = false; - return; - } - - char x = data[0]; - size_t slevel = (size_t)(midi_loop_.stack_level_ + 1); - while (slevel >= midi_loop_.stack_.size()) - { - LoopStackEntry e; - e.loops = x; - e.infinity = (x == 0); - e.start = 0; - e.end = 0; - midi_loop_.stack_.push_back(e); - } - - LoopStackEntry &s = midi_loop_.stack_[slevel]; - s.loops = (int)(x); - s.infinity = (x == 0); - midi_loop_.caught_stack_start_ = true; - return; - } - - if (evtype == MidiEvent::kLoopStackEnd) - { - midi_loop_.caught_stack_end_ = true; - return; - } - - if (evtype == MidiEvent::kLoopStackBreak) - { - midi_loop_.caught_stack_break_ = true; - return; - } - } - - if (evtype == MidiEvent::kCallbackTrigger) - { - if (midi_trigger_handler_) - midi_trigger_handler_(midi_trigger_userdata_, (unsigned)(data[0]), track); - return; - } - - if (evtype == MidiEvent::kRawOPL) // Special non-spec ADLMIDI special for IMF - // playback: Direct poke to AdLib - { - if (midi_output_interface_->rt_rawOPL) - midi_output_interface_->rt_rawOPL(midi_output_interface_->rtUserData, (uint8_t)(data[0]), - (uint8_t)(data[1])); - return; - } - - if (evtype == MidiEvent::kSongBeginHook) - { - if (midi_output_interface_->onSongStart) - midi_output_interface_->onSongStart(midi_output_interface_->onSongStart_userdata); - return; - } - - return; - } - - if (evt.type == MidiEvent::kSysComSongSelect || evt.type == MidiEvent::kSysComSongPositionPointer) - return; - - size_t midCh = evt.channel; - if (midi_output_interface_->rt_currentDevice) - midCh += midi_output_interface_->rt_currentDevice(midi_output_interface_->rtUserData, track); - status = evt.type; - - switch (evt.type) - { - case MidiEvent::kNoteOff: // Note off - { - if (midCh < 16 && m_channelDisable[midCh]) - break; // Disabled channel - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - if (midi_output_interface_->rt_noteOff) - midi_output_interface_->rt_noteOff(midi_output_interface_->rtUserData, (uint8_t)(midCh), note); - if (midi_output_interface_->rt_noteOffVel) - midi_output_interface_->rt_noteOffVel(midi_output_interface_->rtUserData, (uint8_t)(midCh), note, vol); - break; - } - - case MidiEvent::kNoteOn: // Note on - { - if (midCh < 16 && m_channelDisable[midCh]) - break; // Disabled channel - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - midi_output_interface_->rt_noteOn(midi_output_interface_->rtUserData, (uint8_t)(midCh), note, vol); - break; - } - - case MidiEvent::kNoteTouch: // Note touch - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - midi_output_interface_->rt_noteAfterTouch(midi_output_interface_->rtUserData, (uint8_t)(midCh), note, vol); - break; - } - - case MidiEvent::kControlChange: // Controller change - { - uint8_t ctrlno = evt.data[0]; - uint8_t value = evt.data[1]; - midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, (uint8_t)(midCh), ctrlno, - value); - break; - } - - case MidiEvent::kPatchChange: // Patch change - { - midi_output_interface_->rt_patchChange(midi_output_interface_->rtUserData, (uint8_t)(midCh), evt.data[0]); - break; - } - - case MidiEvent::kChannelAftertouch: // Channel after-touch - { - uint8_t chanat = evt.data[0]; - midi_output_interface_->rt_channelAfterTouch(midi_output_interface_->rtUserData, (uint8_t)(midCh), chanat); - break; - } - - case MidiEvent::kPitchWheel: // Wheel/pitch bend - { - uint8_t a = evt.data[0]; - uint8_t b = evt.data[1]; - midi_output_interface_->rt_pitchBend(midi_output_interface_->rtUserData, (uint8_t)(midCh), b, a); - break; - } - - default: - break; - } // switch -} - -double MidiSequencer::Tick(double s, double granularity) -{ - EPI_ASSERT(midi_output_interface_); // MIDI output interface must be defined! - - s *= midi_tempo_multiplier_; - midi_current_position_.wait -= s; - midi_current_position_.absolute_time_position += s; - - int antiFreezeCounter = 10000; // Limit 10000 loops to avoid freezing - while ((midi_current_position_.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) - { - if (!ProcessEvents()) - break; - if (midi_current_position_.wait <= 0.0) - antiFreezeCounter--; - } - - if (antiFreezeCounter <= 0) - midi_current_position_.wait += 1.0; /* Add extra 1 second when over 10000 events - with zero delay are been detected */ - - if (midi_current_position_.wait < 0.0) // Avoid negative delay value! - return 0.0; - - return midi_current_position_.wait; -} - -double MidiSequencer::Seek(double seconds, const double granularity) -{ - if (seconds < 0.0) - return 0.0; // Seeking negative position is forbidden! :-P - const double granualityHalf = granularity * 0.5, - s = seconds; // m_setup.delay_ < m_setup.maxdelay ? - // m_setup.delay_ : m_setup.maxdelay; - - /* Attempt to go away out of song end must rewind position to begin */ - if (seconds > midi_full_song_time_length_) - { - this->Rewind(); - return 0.0; - } - - bool loopFlagState = midi_loop_enabled_; - // Turn loop pooints off because it causes wrong position rememberin on a - // quick seek - midi_loop_enabled_ = false; - - /* - * Seeking search is similar to regular ticking, except of next things: - * - We don't processsing arpeggio and vibrato - * - To keep correctness of the state after seek, begin every search from - * begin - * - All sustaining notes must be killed - * - Ignore Note-On events - */ - this->Rewind(); - - /* - * Set "loop Start" to false to prevent overwrite of loopStart position with - * seek destinition position - * - * TODO: Detect & set loopStart position on load time to don't break loop - * while seeking - */ - midi_loop_.caught_start_ = false; - - midi_loop_.temporary_broken_ = (seconds >= midi_loop_end_time_); - - while ((midi_current_position_.absolute_time_position < seconds) && - (midi_current_position_.absolute_time_position < midi_full_song_time_length_)) - { - midi_current_position_.wait -= s; - midi_current_position_.absolute_time_position += s; - int antiFreezeCounter = 10000; // Limit 10000 loops to avoid freezing - double dstWait = midi_current_position_.wait + granualityHalf; - while ((midi_current_position_.wait <= granualityHalf) /*&& (antiFreezeCounter > 0)*/) - { - // std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if (!ProcessEvents(true)) - break; - // Avoid freeze because of no waiting increasing in more than 10000 - // cycles - if (midi_current_position_.wait <= dstWait) - antiFreezeCounter--; - else - { - dstWait = midi_current_position_.wait + granualityHalf; - antiFreezeCounter = 10000; - } - } - if (antiFreezeCounter <= 0) - midi_current_position_.wait += 1.0; /* Add extra 1 second when over 10000 events - with zero delay are been detected */ - } - - if (midi_current_position_.wait < 0.0) - midi_current_position_.wait = 0.0; - - if (midi_at_end_) - { - this->Rewind(); - midi_loop_enabled_ = loopFlagState; - return 0.0; - } - - midi_time_.Reset(); - midi_time_.delay_ = midi_current_position_.wait; - - midi_loop_enabled_ = loopFlagState; - return midi_current_position_.wait; -} - -double MidiSequencer::Tell() -{ - return midi_current_position_.absolute_time_position; -} - -double MidiSequencer::TimeLength() -{ - return midi_full_song_time_length_; -} - -double MidiSequencer::GetLoopStart() -{ - return midi_loop_start_time_; -} - -double MidiSequencer::GetLoopEnd() -{ - return midi_loop_end_time_; -} - -void MidiSequencer::Rewind() -{ - midi_current_position_ = midi_track_begin_position_; - midi_at_end_ = false; - - midi_loop_.loops_count_ = midi_loop_count_; - midi_loop_.Reset(); - midi_loop_.caught_start_ = true; - midi_loop_.temporary_broken_ = false; - midi_time_.Reset(); -} - -void MidiSequencer::SetTempo(double tempo) -{ - midi_tempo_multiplier_ = tempo; -} - -bool MidiSequencer::LoadMidi(const uint8_t *data, size_t size, uint16_t rate) -{ - epi::MemFile *mfr = new epi::MemFile(data, size); - return LoadMidi(mfr, rate); -} - -template class BufferGuard -{ - T *m_ptr; - - public: - BufferGuard() : m_ptr(nullptr) - { - } - - ~BufferGuard() - { - set(); - } - - void set(T *p = nullptr) - { - if (m_ptr) - free(m_ptr); - m_ptr = p; - } -}; - -/** - * @brief Detect the EA-MUS file format - * @param head Header part - * @param fr Context with opened file data - * @return true if given file was identified as EA-MUS - */ -static bool detectRSXX(const char *head, epi::MemFile *mfr) -{ - char headerBuf[7] = ""; - bool ret = false; - - // Try to identify RSXX format - if (head[0] >= 0x5D) - { - mfr->Seek(head[0] - 0x10, epi::File::kSeekpointStart); - mfr->Read(headerBuf, 6); - if (memcmp(headerBuf, "rsxx}u", 6) == 0) - ret = true; - } - - mfr->Seek(0, epi::File::kSeekpointStart); - return ret; -} -#if EDGE_IMF_SUPPORT -/** - * @brief Detect the Id-software Music File format - * @param head Header part - * @param fr Context with opened file data - * @return true if given file was identified as IMF - */ -static bool detectIMF(const char *head, epi::MemFile *mfr) -{ - uint8_t raw[4]; - size_t end = (size_t)(head[0]) + 256 * (size_t)(head[1]); - - if (end & 3) - return false; - - size_t backup_pos = mfr->GetPosition(); - int64_t sum1 = 0, sum2 = 0; - mfr->Seek((end > 0 ? 2 : 0), epi::File::kSeekpointStart); - - for (size_t n = 0; n < 16383; ++n) - { - if (mfr->Read(raw, 4) != 4) - break; - int64_t value1 = raw[0]; - value1 += raw[1] << 8; - sum1 += value1; - int64_t value2 = raw[2]; - value2 += raw[3] << 8; - sum2 += value2; - } - - mfr->Seek((long)(backup_pos), epi::File::kSeekpointStart); - - return (sum1 > sum2); -} -#endif -bool MidiSequencer::LoadMidi(epi::MemFile *mfr, uint16_t rate) -{ - midi_parsing_errors_string_.clear(); - - EPI_ASSERT(midi_output_interface_); // MIDI output interface must be defined! - - midi_at_end_ = false; - midi_loop_.FullReset(); - midi_loop_.caught_start_ = true; - - midi_format_ = kFormatMidi; - midi_smf_format_ = 0; - - midi_raw_songs_data_.clear(); - - const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 - char headerBuf[headerSize] = ""; - - size_t fsize = mfr->Read(headerBuf, headerSize); - if (fsize < headerSize) - { - midi_error_string_ = "Unexpected end of file at header!\n"; - delete mfr; - return false; - } - - if (memcmp(headerBuf, "MThd\0\0\0\6", 8) == 0) - { - mfr->Seek(0, epi::File::kSeekpointStart); - return ParseSMF(mfr); - } - - if (memcmp(headerBuf, "RIFF", 4) == 0) - { - mfr->Seek(0, epi::File::kSeekpointStart); - return ParseRMI(mfr); - } - - if (memcmp(headerBuf, "GMF\x1", 4) == 0) - { - mfr->Seek(0, epi::File::kSeekpointStart); - return ParseGMF(mfr); - } -#if EDGE_MUS_SUPPORT - if (memcmp(headerBuf, "MUS\x1A", 4) == 0) - { - mfr->Seek(0, epi::File::kSeekpointStart); - return ParseMUS(mfr); - } -#endif -#if EDGE_XMI_SUPPORT - if ((memcmp(headerBuf, "FORM", 4) == 0) && (memcmp(headerBuf + 8, "XDIR", 4) == 0)) - { - mfr->Seek(0, epi::File::kSeekpointStart); - return ParseXMI(mfr); - } -#endif -#if EDGE_IMF_SUPPORT - if (detectIMF(headerBuf, mfr)) - { - mfr->Seek(0, epi::File::kSeekpointStart); - return ParseIMF(mfr, rate); - } -#endif - if (detectRSXX(headerBuf, mfr)) - { - mfr->Seek(0, epi::File::kSeekpointStart); - return ParseRSXX(mfr); - } - - midi_error_string_ = "Unknown or unsupported file format"; - delete mfr; - return false; -} -#if EDGE_IMF_SUPPORT -bool MidiSequencer::ParseIMF(epi::MemFile *mfr, uint16_t rate) -{ - const size_t deltaTicks = 1; - const size_t track_count = 1; - uint32_t imfTempo = 0; - size_t imfEnd = 0; - uint64_t abs_position = 0; - uint8_t imfRaw[4]; - - MidiTrackRow evtPos; - MidiEvent event; - - switch (rate) - { - case 280: - imfTempo = 3570; - break; - case 560: - imfTempo = 1785; - break; - case 700: - imfTempo = 1428; - break; - default: - imfTempo = 1428; - break; - } - - std::vector temposList; - - midi_format_ = kFormatIMF; - - BuildSMFSetupReset(track_count); - - midi_individual_tick_delta_ = MidiFraction(1, 1000000l * (uint64_t)(deltaTicks)); - midi_tempo_ = MidiFraction(1, (uint64_t)(deltaTicks) * 2); - - mfr->Seek(0, epi::File::kSeekpointStart); - if (mfr->Read(imfRaw, 2) != 2) - { - midi_error_string_ = "Unexpected end of file at header!\n"; - delete mfr; - return false; - } - - imfEnd = (size_t)(imfRaw[0]) + 256 * (size_t)(imfRaw[1]); - - // Define the playing tempo - event.type = MidiEvent::kSpecial; - event.sub_type = MidiEvent::kTempoChange; - event.absolute_tick_position = 0; - event.data.resize(4); - event.data[0] = (uint8_t)((imfTempo >> 24) & 0xFF); - event.data[1] = (uint8_t)((imfTempo >> 16) & 0xFF); - event.data[2] = (uint8_t)((imfTempo >> 8) & 0xFF); - event.data[3] = (uint8_t)((imfTempo & 0xFF)); - evtPos.events_.push_back(event); - temposList.push_back(event); - - // Define the draft for IMF events - event.type = MidiEvent::kSpecial; - event.sub_type = MidiEvent::kRawOPL; - event.absolute_tick_position = 0; - event.data.resize(2); - - mfr->Seek((imfEnd > 0) ? 2 : 0, epi::File::kSeekpointStart); - - if (imfEnd == 0) // IMF Type 0 with unlimited file length - imfEnd = mfr->GetLength(); - - while (mfr->GetPosition() < (int)imfEnd) - { - if (mfr->Read(imfRaw, 4) != 4) - break; - - event.data[0] = imfRaw[0]; // port index - event.data[1] = imfRaw[1]; // port value - event.absolute_tick_position = abs_position; - event.is_valid = 1; - - evtPos.events_.push_back(event); - evtPos.delay_ = (uint64_t)(imfRaw[2]) + 256 * (uint64_t)(imfRaw[3]); - - if (evtPos.delay_ > 0) - { - evtPos.absolute_position_ = abs_position; - abs_position += evtPos.delay_; - midi_track_data_[0].push_back(evtPos); - evtPos.Clear(); - } - } - - // Add final row - evtPos.absolute_position_ = abs_position; - abs_position += evtPos.delay_; - midi_track_data_[0].push_back(evtPos); - - if (!midi_track_data_[0].empty()) - midi_current_position_.track[0].pos = midi_track_data_[0].begin(); - - BuildTimeLine(temposList); - - delete mfr; - - return true; -} -#endif -bool MidiSequencer::ParseRSXX(epi::MemFile *mfr) -{ - const size_t headerSize = 14; - char headerBuf[headerSize] = ""; - size_t fsize = 0; - size_t deltaTicks = 192, track_count = 1; - std::vector> rawTrackData; - - fsize = mfr->Read(headerBuf, headerSize); - if (fsize < headerSize) - { - midi_error_string_ = "Unexpected end of file at header!\n"; - delete mfr; - return false; - } - - // Try to identify RSXX format - char start = headerBuf[0]; - if (start < 0x5D) - { - midi_error_string_ = "RSXX song too short!\n"; - delete mfr; - return false; - } - else - { - mfr->Seek(headerBuf[0] - 0x10, epi::File::kSeekpointStart); - mfr->Read(headerBuf, 6); - if (memcmp(headerBuf, "rsxx}u", 6) == 0) - { - midi_format_ = kFormatRSXX; - mfr->Seek(start, epi::File::kSeekpointStart); - track_count = 1; - deltaTicks = 60; - } - else - { - midi_error_string_ = "Invalid RSXX header!\n"; - delete mfr; - return false; - } - } - - rawTrackData.clear(); - rawTrackData.resize(track_count, std::vector()); - midi_individual_tick_delta_ = MidiFraction(1, 1000000l * (uint64_t)(deltaTicks)); - midi_tempo_ = MidiFraction(1, (uint64_t)(deltaTicks)); - - size_t totalGotten = 0; - - for (size_t tk = 0; tk < track_count; ++tk) - { - // Read track header - size_t trackLength; - - size_t pos = mfr->GetPosition(); - mfr->Seek(0, epi::File::kSeekpointEnd); - trackLength = mfr->GetPosition() - pos; - mfr->Seek((long)(pos), epi::File::kSeekpointStart); - - // Read track data - rawTrackData[tk].resize(trackLength); - fsize = mfr->Read(&rawTrackData[tk][0], trackLength); - if (fsize < trackLength) - { - midi_error_string_ = "MIDI Loader: Unexpected file ending while getting raw track " - "data!\n"; - delete mfr; - return false; - } - totalGotten += fsize; - - // Finalize raw track data with a zero - rawTrackData[tk].push_back(0); - } - - for (size_t tk = 0; tk < track_count; ++tk) - totalGotten += rawTrackData[tk].size(); - - if (totalGotten == 0) - { - midi_error_string_ = "MIDI Loader: Empty track data"; - delete mfr; - return false; - } - - // Build new MIDI events table - if (!BuildSMFTrackData(rawTrackData)) - { - midi_error_string_ = "MIDI Loader: MIDI data parsing error has occouped!\n" + midi_parsing_errors_string_; - delete mfr; - return false; - } - - midi_smf_format_ = 0; - midi_loop_.stack_level_ = -1; - - delete mfr; - - return true; -} - -bool MidiSequencer::ParseGMF(epi::MemFile *mfr) -{ - const size_t headerSize = 14; - char headerBuf[headerSize] = ""; - size_t fsize = 0; - size_t deltaTicks = 192, track_count = 1; - std::vector> rawTrackData; - - fsize = mfr->Read(headerBuf, headerSize); - if (fsize < headerSize) - { - midi_error_string_ = "Unexpected end of file at header!\n"; - delete mfr; - return false; - } - - if (memcmp(headerBuf, "GMF\x1", 4) != 0) - { - midi_error_string_ = "MIDI Loader: Invalid format, GMF\\x1 signature is not found!\n"; - delete mfr; - return false; - } - - mfr->Seek(7 - (long)(headerSize), epi::File::kSeekpointCurrent); - - rawTrackData.clear(); - rawTrackData.resize(track_count, std::vector()); - midi_individual_tick_delta_ = MidiFraction(1, 1000000l * (uint64_t)(deltaTicks)); - midi_tempo_ = MidiFraction(1, (uint64_t)(deltaTicks) * 2); - static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; - size_t totalGotten = 0; - - for (size_t tk = 0; tk < track_count; ++tk) - { - // Read track header - size_t trackLength; - size_t pos = mfr->GetPosition(); - mfr->Seek(0, epi::File::kSeekpointEnd); - trackLength = mfr->GetPosition() - pos; - mfr->Seek((long)(pos), epi::File::kSeekpointStart); - - // Read track data - rawTrackData[tk].resize(trackLength); - fsize = mfr->Read(&rawTrackData[tk][0], trackLength); - if (fsize < trackLength) - { - midi_error_string_ = "MIDI Loader: Unexpected file ending while getting raw track " - "data!\n"; - delete mfr; - return false; - } - totalGotten += fsize; - // Note: GMF does include the track end tag. - rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); - } - - for (size_t tk = 0; tk < track_count; ++tk) - totalGotten += rawTrackData[tk].size(); - - if (totalGotten == 0) - { - midi_error_string_ = "MIDI Loader: Empty track data"; - delete mfr; - return false; - } - - // Build new MIDI events table - if (!BuildSMFTrackData(rawTrackData)) - { - midi_error_string_ = "MIDI Loader: : MIDI data parsing error has occouped!\n" + midi_parsing_errors_string_; - delete mfr; - return false; - } - - delete mfr; - - return true; -} - -bool MidiSequencer::ParseSMF(epi::MemFile *mfr) -{ - const size_t headerSize = 14; // 4 + 4 + 2 + 2 + 2 - char headerBuf[headerSize] = ""; - size_t fsize = 0; - size_t deltaTicks = 192, TrackCount = 1; - unsigned smfFormat = 0; - std::vector> rawTrackData; - - fsize = mfr->Read(headerBuf, headerSize); - if (fsize < headerSize) - { - midi_error_string_ = "Unexpected end of file at header!\n"; - delete mfr; - return false; - } - - if (memcmp(headerBuf, "MThd\0\0\0\6", 8) != 0) - { - midi_error_string_ = "MIDI Loader: Invalid format, MThd signature is not found!\n"; - delete mfr; - return false; - } - - smfFormat = (unsigned)(ReadIntBigEndian(headerBuf + 8, 2)); - TrackCount = (size_t)(ReadIntBigEndian(headerBuf + 10, 2)); - deltaTicks = (size_t)(ReadIntBigEndian(headerBuf + 12, 2)); - - if (smfFormat > 2) - smfFormat = 1; - - rawTrackData.clear(); - rawTrackData.resize(TrackCount, std::vector()); - midi_individual_tick_delta_ = MidiFraction(1, 1000000l * (uint64_t)(deltaTicks)); - midi_tempo_ = MidiFraction(1, (uint64_t)(deltaTicks) * 2); - - size_t totalGotten = 0; - - for (size_t tk = 0; tk < TrackCount; ++tk) - { - // Read track header - size_t trackLength; - - fsize = mfr->Read(headerBuf, 8); - if ((fsize < 8) || (memcmp(headerBuf, "MTrk", 4) != 0)) - { - midi_error_string_ = "MIDI Loader: Invalid format, MTrk signature is not found!\n"; - delete mfr; - return false; - } - trackLength = (size_t)ReadIntBigEndian(headerBuf + 4, 4); - - // Read track data - rawTrackData[tk].resize(trackLength); - fsize = mfr->Read(&rawTrackData[tk][0], trackLength); - if (fsize < trackLength) - { - midi_error_string_ = "MIDI Loader: Unexpected file ending while getting raw track " - "data!\n"; - delete mfr; - return false; - } - - totalGotten += fsize; - } - - for (size_t tk = 0; tk < TrackCount; ++tk) - totalGotten += rawTrackData[tk].size(); - - if (totalGotten == 0) - { - midi_error_string_ = "MIDI Loader: Empty track data"; - delete mfr; - return false; - } - - // Build new MIDI events table - if (!BuildSMFTrackData(rawTrackData)) - { - midi_error_string_ = "MIDI Loader: MIDI data parsing error has occouped!\n" + midi_parsing_errors_string_; - delete mfr; - return false; - } - - midi_smf_format_ = smfFormat; - midi_loop_.stack_level_ = -1; - - delete mfr; - - return true; -} - -bool MidiSequencer::ParseRMI(epi::MemFile *mfr) -{ - const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 - char headerBuf[headerSize] = ""; - - size_t fsize = mfr->Read(headerBuf, headerSize); - if (fsize < headerSize) - { - midi_error_string_ = "Unexpected end of file at header!\n"; - delete mfr; - return false; - } - - if (memcmp(headerBuf, "RIFF", 4) != 0) - { - midi_error_string_ = "MIDI Loader: Invalid format, RIFF signature is not found!\n"; - delete mfr; - return false; - } - - midi_format_ = kFormatMidi; - - mfr->Seek(6l, epi::File::kSeekpointCurrent); - return ParseSMF(mfr); -} -#if EDGE_MUS_SUPPORT -bool MidiSequencer::ParseMUS(epi::MemFile *mfr) -{ - const size_t headerSize = 14; - char headerBuf[headerSize] = ""; - size_t fsize = 0; - BufferGuard cvt_buf; - - fsize = mfr->Read(headerBuf, headerSize); - if (fsize < headerSize) - { - midi_error_string_ = "Unexpected end of file at header!\n"; - delete mfr; - return false; - } - - if (memcmp(headerBuf, "MUS\x1A", 4) != 0) - { - midi_error_string_ = "MIDI Loader: Invalid format, MUS\\x1A signature is not found!\n"; - delete mfr; - return false; - } - - size_t mus_len = mfr->GetLength(); - - mfr->Seek(0, epi::File::kSeekpointStart); - uint8_t *mus = (uint8_t *)malloc(mus_len); - if (!mus) - { - midi_error_string_ = "Out of memory!"; - delete mfr; - return false; - } - fsize = mfr->Read(mus, mus_len); - if (fsize < mus_len) - { - midi_error_string_ = "Failed to read MUS file data!\n"; - delete mfr; - return false; - } - - // Close source stream - delete mfr; - mfr = nullptr; - - uint8_t *mid = nullptr; - uint32_t mid_len = 0; - int m2mret = ConvertMusToMidi(mus, (uint32_t)(mus_len), &mid, &mid_len, 0); - - if (mus) - free(mus); - - if (m2mret < 0) - { - midi_error_string_ = "Invalid MUS/DMX data format!"; - if (mid) - free(mid); - return false; - } - cvt_buf.set(mid); - - // Open converted MIDI file - mfr = new epi::MemFile(mid, (size_t)(mid_len)); - - return ParseSMF(mfr); -} -#endif -#if EDGE_XMI_SUPPORT -bool MidiSequencer::ParseXMI(epi::MemFile *mfr) -{ - const size_t headerSize = 14; - char headerBuf[headerSize] = ""; - size_t fsize = 0; - std::vector> song_buf; - bool ret; - - fsize = mfr->Read(headerBuf, headerSize); - if (fsize < headerSize) - { - midi_error_string_ = "Unexpected end of file at header!\n"; - delete mfr; - return false; - } - - if (memcmp(headerBuf, "FORM", 4) != 0) - { - midi_error_string_ = "MIDI Loader: Invalid format, FORM signature is not found!\n"; - delete mfr; - return false; - } - - if (memcmp(headerBuf + 8, "XDIR", 4) != 0) - { - midi_error_string_ = "MIDI Loader: Invalid format\n"; - delete mfr; - return false; - } - - size_t mus_len = mfr->GetLength(); - mfr->Seek(0, epi::File::kSeekpointStart); - - uint8_t *mus = (uint8_t *)malloc(mus_len + 20); - if (!mus) - { - midi_error_string_ = "Out of memory!"; - delete mfr; - return false; - } - - EPI_CLEAR_MEMORY(mus, uint8_t, mus_len + 20); - - fsize = mfr->Read(mus, mus_len); - if (fsize < mus_len) - { - midi_error_string_ = "Failed to read XMI file data!\n"; - delete mfr; - return false; - } - - // Close source stream - delete mfr; - mfr = nullptr; - - int m2mret = ConvertXMIToMidi(mus, (uint32_t)(mus_len + 20), song_buf, kXMINoConversion); - if (mus) - free(mus); - if (m2mret < 0) - { - song_buf.clear(); - midi_error_string_ = "Invalid XMI data format!"; - return false; - } - - if (midi_load_track_number_ >= (int)song_buf.size()) - midi_load_track_number_ = song_buf.size() - 1; - - for (size_t i = 0; i < song_buf.size(); ++i) - { - midi_raw_songs_data_.push_back(song_buf[i]); - } - - song_buf.clear(); - - // Open converted MIDI file - mfr = new epi::MemFile(midi_raw_songs_data_[midi_load_track_number_].data(), - midi_raw_songs_data_[midi_load_track_number_].size()); - // Set format as XMIDI - midi_format_ = kFormatXMidi; - - ret = ParseSMF(mfr); - - return ret; -} -#endif \ No newline at end of file +//--- editor settings --- +// vi:ts=4:sw=4:noexpandtab diff --git a/source_files/edge/s_midi_seq.h b/source_files/edge/s_midi_seq.h new file mode 100644 index 000000000..fc4498378 --- /dev/null +++ b/source_files/edge/s_midi_seq.h @@ -0,0 +1,5521 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2022 Vitaly Novichkov + * Copyright (c) 2024 The EDGE Team. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + * OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include + +#include // std::copy +#include // std::back_inserter +#include +#include +#include + +#include "epi.h" +#include "stb_sprintf.h" + +#if EDGE_MUS_SUPPORT +static constexpr uint8_t kMusFrequency = 140; +static constexpr int kMusTempo = 0x00068A1B; /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ + /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ +static constexpr uint16_t kMusDivision = 0x0101; /* 257 for 140Hz files with a 140MPQN */ + /* 0x0088 -> 136 for 70Hz files with a 140MPQN */ + /* 0x010B -> 267 for 70hz files with a 70MPQN */ + /* 0x01F9 -> 505 for 140hz files with a 70MPQN */ + +/* New + * QLS: MPQN/1000000 = 0.428571 + * TDPS: QLS/PPQN = 0.428571/136 = 0.003151257 + * PPQN: 136 + * + * QLS: MPQN/1000000 = 0.428571 + * TDPS: QLS/PPQN = 0.428571/257 = 0.001667591 + * PPQN: 257 + * + * QLS: MPQN/1000000 = 0.857142 + * TDPS: QLS/PPQN = 0.857142/267 = 0.00321027 + * PPQN: 267 + * + * QLS: MPQN/1000000 = 0.857142 + * TDPS: QLS/PPQN = 0.857142/505 = 0.001697311 + * PPQN: 505 + * + * Old + * QLS: MPQN/1000000 = 1.745673 + * TDPS: QLS/PPQN = 1.745673 / 89 = 0.019614303 (seconds per tick) + * PPQN: (TDPS = QLS/PPQN) (0.019614303 = 1.745673/PPQN) (0.019614303*PPQN + * = 1.745673) (PPQN = 89.000001682) + * + */ + +enum MusEvent +{ + kMusEventKeyOff = 0, + kMusEventKeyOn = 1, + kMusEventPitchWheel = 2, + kMusEventChannelMode = 3, + kMusEventControllerChange = 4, + kMusEventEnd = 6, +}; + +static constexpr uint8_t kMusMidiMaxChannels = 16; + +static constexpr char kMusHeader[] = {'M', 'U', 'S', 0x1A}; + +static constexpr uint8_t kMusToMidiMap[] = { + /* MIDI Number Description */ + 0, /* 0 program change */ + 0, /* 1 bank selection */ + 0x01, /* 2 Modulation pot (frequency vibrato depth) */ + 0x07, /* 3 Volume: 0-silent, ~100-normal, 127-loud */ + 0x0A, /* 4 Pan (balance) pot: 0-left, 64-center (default), 127-right */ + 0x0B, /* 5 Expression pot */ + 0x5B, /* 6 Reverb depth */ + 0x5D, /* 7 Chorus depth */ + 0x40, /* 8 Sustain pedal */ + 0x43, /* 9 Soft pedal */ + 0x78, /* 10 All sounds off */ + 0x7B, /* 11 All notes off */ + 0x7E, /* 12 Mono (use numchannels + 1) */ + 0x7F, /* 13 Poly */ + 0x79, /* 14 reset all controllers */ +}; + +struct MusHeader +{ + char ID[4]; /* identifier: "MUS" 0x1A */ + uint16_t scoreLen; + uint16_t scoreStart; + uint16_t channels; /* count of primary channels */ + uint16_t sec_channels; /* count of secondary channels */ + uint16_t instrCnt; +}; + +struct MidiHeaderChunk +{ + char name[4]; + int32_t length; + int16_t format; /* make 0 */ + int16_t ntracks; /* make 1 */ + int16_t division; /* 0xe250 ?? */ +}; + +struct MidiTrackChunk +{ + char name[4]; + int32_t length; +}; + +struct MusConversionContext +{ + uint8_t *src, *src_ptr; + uint32_t srcsize; + uint32_t datastart; + uint8_t *dst, *dst_ptr; + uint32_t dstsize, dstrem; +}; + +static constexpr uint16_t kMusDestinationChunkSize = 8192; +static void MusToMidiResizeDestination(struct MusConversionContext *ctx) +{ + uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); + ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + kMusDestinationChunkSize); + ctx->dstsize += kMusDestinationChunkSize; + ctx->dstrem += kMusDestinationChunkSize; + ctx->dst_ptr = ctx->dst + pos; +} + +static void MusToMidiWrite1(struct MusConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 1) + MusToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem--; +} + +static void MusToMidiWrite2(struct MusConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 2) + MusToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = (val >> 8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 2; +} + +static void MusToMidiWrite4(struct MusConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 4) + MusToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = (uint8_t)((val >> 24) & 0xff); + *ctx->dst_ptr++ = (uint8_t)((val >> 16) & 0xff); + *ctx->dst_ptr++ = (uint8_t)((val >> 8) & 0xff); + *ctx->dst_ptr++ = (uint8_t)((val & 0xff)); + ctx->dstrem -= 4; +} + +static void MusToMidiSeekDestination(struct MusConversionContext *ctx, uint32_t pos) +{ + ctx->dst_ptr = ctx->dst + pos; + while (ctx->dstsize < pos) + MusToMidiResizeDestination(ctx); + ctx->dstrem = ctx->dstsize - pos; +} + +static void MusToMidiSkipDestination(struct MusConversionContext *ctx, int32_t pos) +{ + size_t newpos; + ctx->dst_ptr += pos; + newpos = ctx->dst_ptr - ctx->dst; + while (ctx->dstsize < newpos) + MusToMidiResizeDestination(ctx); + ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); +} + +static uint32_t MusToMidiGetDestinationPosition(struct MusConversionContext *ctx) +{ + return (uint32_t)(ctx->dst_ptr - ctx->dst); +} + +/* writes a variable length integer to a buffer, and returns bytes written */ +static int32_t MusToMidiWriteVariableLength(int32_t value, uint8_t *out) +{ + int32_t buffer, count = 0; + + buffer = value & 0x7f; + while ((value >>= 7) > 0) + { + buffer <<= 8; + buffer += 0x80; + buffer += (value & 0x7f); + } + + while (1) + { + ++count; + *out = (uint8_t)buffer; + ++out; + if (buffer & 0x80) + buffer >>= 8; + else + break; + } + return (count); +} + +#define EDGE_MUS_READ_SHORT(b) ((b)[0] | ((b)[1] << 8)) +#define EDGE_MUS_READ_INT(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24)) + +static int ConvertMusToMidi(uint8_t *in, uint32_t insize, uint8_t **out, uint32_t *outsize, uint16_t frequency) +{ + struct MusConversionContext ctx; + MusHeader header; + uint8_t *cur, *end; + uint32_t track_size_pos, begin_track_pos, current_pos; + int32_t delta_time; /* Delta time for midi event */ + int temp, ret = -1; + int channel_volume[kMusMidiMaxChannels]; + int channelMap[kMusMidiMaxChannels], currentChannel; + + if (insize < sizeof(MusHeader)) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", + * 0);*/ + return (-1); + } + + if (!frequency) + frequency = kMusFrequency; + + /* read the MUS header and set our location */ + memcpy(header.ID, in, 4); + header.scoreLen = EDGE_MUS_READ_SHORT(&in[4]); + header.scoreStart = EDGE_MUS_READ_SHORT(&in[6]); + header.channels = EDGE_MUS_READ_SHORT(&in[8]); + header.sec_channels = EDGE_MUS_READ_SHORT(&in[10]); + header.instrCnt = EDGE_MUS_READ_SHORT(&in[12]); + + if (memcmp(header.ID, kMusHeader, 4)) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, nullptr, + * 0);*/ + return (-1); + } + if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", + * 0);*/ + return (-1); + } + /* channel #15 should be excluded in the numchannels field: */ + if (header.channels > kMusMidiMaxChannels - 1) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, nullptr, + * 0);*/ + return (-1); + } + + EPI_CLEAR_MEMORY(&ctx, MusConversionContext, 1); + ctx.src = ctx.src_ptr = in; + ctx.srcsize = insize; + + ctx.dst = (uint8_t *)calloc(kMusDestinationChunkSize, sizeof(uint8_t)); + ctx.dst_ptr = ctx.dst; + ctx.dstsize = kMusDestinationChunkSize; + ctx.dstrem = kMusDestinationChunkSize; + + /* Map channel 15 to 9 (percussions) */ + for (temp = 0; temp < kMusMidiMaxChannels; ++temp) + { + channelMap[temp] = -1; + channel_volume[temp] = 0x40; + } + channelMap[15] = 9; + + /* Header is 14 bytes long and add the rest as well */ + MusToMidiWrite1(&ctx, 'M'); + MusToMidiWrite1(&ctx, 'T'); + MusToMidiWrite1(&ctx, 'h'); + MusToMidiWrite1(&ctx, 'd'); + MusToMidiWrite4(&ctx, 6); /* length of header */ + MusToMidiWrite2(&ctx, 0); /* MIDI type (always 0) */ + MusToMidiWrite2(&ctx, 1); /* MUS files only have 1 track */ + MusToMidiWrite2(&ctx, kMusDivision); /* division */ + + /* Write out track header and track length position for later */ + begin_track_pos = MusToMidiGetDestinationPosition(&ctx); + MusToMidiWrite1(&ctx, 'M'); + MusToMidiWrite1(&ctx, 'T'); + MusToMidiWrite1(&ctx, 'r'); + MusToMidiWrite1(&ctx, 'k'); + track_size_pos = MusToMidiGetDestinationPosition(&ctx); + MusToMidiSkipDestination(&ctx, 4); + + /* write tempo: microseconds per quarter note */ + MusToMidiWrite1(&ctx, 0x00); /* delta time */ + MusToMidiWrite1(&ctx, 0xff); /* sys command */ + MusToMidiWrite2(&ctx, 0x5103); /* command - set tempo */ + MusToMidiWrite1(&ctx, kMusTempo & 0x000000ff); + MusToMidiWrite1(&ctx, (kMusTempo & 0x0000ff00) >> 8); + MusToMidiWrite1(&ctx, (kMusTempo & 0x00ff0000) >> 16); + + /* Percussions channel starts out at volume 100 */ + MusToMidiWrite1(&ctx, 0x00); + MusToMidiWrite1(&ctx, 0xB9); + MusToMidiWrite1(&ctx, 0x07); + MusToMidiWrite1(&ctx, 100); + + /* get current position in source, and end of position */ + cur = in + header.scoreStart; + end = cur + header.scoreLen; + + currentChannel = 0; + delta_time = 0; + + /* main loop */ + while (cur < end) + { + /*printf("LOOP DEBUG: %d\r\n",iterator++);*/ + uint8_t channel; + uint8_t event; + uint8_t temp_buffer[32]; /* temp buffer for current iterator */ + uint8_t *out_local = temp_buffer; + uint8_t status, bit1, bit2, bitc = 2; + + /* read in current bit */ + event = *cur++; + channel = (event & 15); /* current channel */ + + /* write variable length delta time */ + out_local += MusToMidiWriteVariableLength(delta_time, out_local); + + /* set all channels to 127 (max) volume */ + if (channelMap[channel] < 0) + { + *out_local++ = 0xB0 + currentChannel; + *out_local++ = 0x07; + *out_local++ = 100; + *out_local++ = 0x00; + channelMap[channel] = currentChannel++; + if (currentChannel == 9) + ++currentChannel; + } + status = channelMap[channel]; + + /* handle events */ + switch ((event & 122) >> 4) + { + case kMusEventKeyOff: + status |= 0x80; + bit1 = *cur++; + bit2 = 0x40; + break; + case kMusEventKeyOn: + status |= 0x90; + bit1 = *cur & 127; + if (*cur++ & 128) /* volume bit? */ + channel_volume[channelMap[channel]] = *cur++; + bit2 = channel_volume[channelMap[channel]]; + break; + case kMusEventPitchWheel: + status |= 0xE0; + bit1 = (*cur & 1) >> 6; + bit2 = (*cur++ >> 1) & 127; + break; + case kMusEventChannelMode: + status |= 0xB0; + if (*cur >= sizeof(kMusToMidiMap) / sizeof(kMusToMidiMap[0])) + { + /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", + __FUNCTION__, __LINE__, *cur);*/ + goto _end; + } + bit1 = kMusToMidiMap[*cur++]; + bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; + break; + case kMusEventControllerChange: + if (*cur == 0) + { + cur++; + status |= 0xC0; + bit1 = *cur++; + bit2 = 0; /* silence bogus warnings */ + bitc = 1; + } + else + { + status |= 0xB0; + if (*cur >= sizeof(kMusToMidiMap) / sizeof(kMusToMidiMap[0])) + { + /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", + __FUNCTION__, __LINE__, *cur);*/ + goto _end; + } + bit1 = kMusToMidiMap[*cur++]; + bit2 = *cur++; + } + break; + case kMusEventEnd: /* End */ + status = 0xff; + bit1 = 0x2f; + bit2 = 0x00; + if (cur != end) + { /* should we error here or report-only? */ + /*_WM_DEBUG_MSG("%s:%i: MUS buffer off by %ld bytes", + __FUNCTION__, __LINE__, (long)(cur - end));*/ + } + break; + case 5: /* Unknown */ + case 7: /* Unknown */ + default: /* shouldn't happen */ + /*_WM_ERROR_NEW("%s:%i: unrecognized event (%u)", + __FUNCTION__, __LINE__, event);*/ + goto _end; + } + + /* write it out */ + *out_local++ = status; + *out_local++ = bit1; + if (bitc == 2) + *out_local++ = bit2; + + /* write out our temp buffer */ + if (out_local != temp_buffer) + { + if (ctx.dstrem < sizeof(temp_buffer)) + MusToMidiResizeDestination(&ctx); + + memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer); + ctx.dst_ptr += out_local - temp_buffer; + ctx.dstrem -= (uint32_t)(out_local - temp_buffer); + } + + if (event & 128) + { + delta_time = 0; + do + { + delta_time = (int32_t)((delta_time * 128 + (*cur & 127)) * (140.0 / (double)frequency)); + } while ((*cur++ & 128)); + } + else + { + delta_time = 0; + } + } + + /* write out track length */ + current_pos = MusToMidiGetDestinationPosition(&ctx); + MusToMidiSeekDestination(&ctx, track_size_pos); + MusToMidiWrite4(&ctx, current_pos - begin_track_pos - sizeof(MidiTrackChunk)); + MusToMidiSeekDestination(&ctx, current_pos); /* reseek to end position */ + + *out = ctx.dst; + *outsize = ctx.dstsize - ctx.dstrem; + ret = 0; + +_end: /* cleanup */ + if (ret < 0) + { + free(ctx.dst); + *out = nullptr; + *outsize = 0; + } + + return (ret); +} +#endif + +#if EDGE_XMI_SUPPORT +enum XMIConversionType +{ + kXMINoConversion = 0x00, + kXMIConvertMt32ToGm = 0x01, + kXMIConvertMt32ToGs = 0x02, + kXMIConvertMt32ToGs127 = 0x03, + kXMIConvertMt32ToGs127Drum = 0x04, + kXMIConvertGs127ToGs = 0x05 +}; + +enum XMIStatusByte +{ + kXMIStatusNoteOff = 0x8, + kXMIStatusNoteOn = 0x9, + kXMIStatusAftertouch = 0xA, + kXMIStatusController = 0xB, + kXMIStatusProgramChange = 0xC, + kXMIStatusPressure = 0xD, + kXMIStatusPitchWheel = 0xE, + kXMIStatusSysex = 0xF +}; + +struct XMIToMidiEvent +{ + int32_t time; + uint8_t status; + uint8_t data[2]; + uint32_t len; + uint8_t *buffer; + XMIToMidiEvent *next; +}; + +struct MidiDescriptor +{ + uint16_t type; + uint16_t tracks; +}; + +struct XMIToMidiConversionContext +{ + uint8_t *src, *src_ptr, *src_end; + uint32_t srcsize; + uint32_t datastart; + uint8_t *dst, *dst_ptr; + uint32_t dstsize, dstrem; + uint32_t convert_type; + MidiDescriptor info; + int bank127[16]; + XMIToMidiEvent **events; + int16_t *timing; + XMIToMidiEvent *list; + XMIToMidiEvent *current; + std::vector> *dyn_out; + std::vector *dyn_out_cur; +}; + +struct XMIToMidiBranch +{ + unsigned count; + uint8_t id[128]; + uint32_t offset[128]; +}; + +/* forward declarations of private functions */ +static void XMIToMidiDeleteEventList(XMIToMidiEvent *mlist); +static void XMIToMidiCreateNewEvent(struct XMIToMidiConversionContext *ctx, int32_t time); /* List manipulation */ +static int XMIToMidiGetVlq(struct XMIToMidiConversionContext *ctx, uint32_t *quant); /* Variable length quantity */ +static int XMIToMidiGetVlq2(struct XMIToMidiConversionContext *ctx, uint32_t *quant); /* Variable length quantity */ +static int XMIToMidiPutVlq(struct XMIToMidiConversionContext *ctx, uint32_t value); /* Variable length quantity */ +static int XMIToMidiConvertEvent(struct XMIToMidiConversionContext *ctx, const int32_t time, const uint8_t status, + const int size); +static int32_t XMIToMidiConvertSystemMessage(struct XMIToMidiConversionContext *ctx, const int32_t time, + const uint8_t status); +static int32_t XMIToMidiConvertFiletoList(struct XMIToMidiConversionContext *ctx, const XMIToMidiBranch *rbrn); +static uint32_t XMIToMidiConvertListToMidiTrack(struct XMIToMidiConversionContext *ctx, XMIToMidiEvent *mlist); +static int XMIToMidiParseXMI(struct XMIToMidiConversionContext *ctx); +static int XMIToMidiExtractTracks(struct XMIToMidiConversionContext *ctx, int32_t dstTrackNumber); +static uint32_t XMIToMidiExtractTracksFromXMI(struct XMIToMidiConversionContext *ctx); + +static uint32_t XMIToMidiRead1(struct XMIToMidiConversionContext *ctx) +{ + uint8_t b0; + EPI_ASSERT(ctx->src_ptr + 1 < ctx->src_end); + b0 = *ctx->src_ptr++; + return (b0); +} + +static uint32_t XMIToMidiRead2(struct XMIToMidiConversionContext *ctx) +{ + uint8_t b0, b1; + EPI_ASSERT(ctx->src_ptr + 2 < ctx->src_end); + b0 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + return (b0 + ((uint32_t)b1 << 8)); +} + +static uint32_t XMIToMidiRead4(struct XMIToMidiConversionContext *ctx) +{ + uint8_t b0, b1, b2, b3; + EPI_ASSERT(ctx->src_ptr + 4 < ctx->src_end); + b3 = *ctx->src_ptr++; + b2 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + b0 = *ctx->src_ptr++; + return (b0 + ((uint32_t)b1 << 8) + ((uint32_t)b2 << 16) + ((uint32_t)b3 << 24)); +} + +static uint32_t XMIToMidiRead4LittleEndian(struct XMIToMidiConversionContext *ctx) +{ + uint8_t b0, b1, b2, b3; + EPI_ASSERT(ctx->src_ptr + 4 < ctx->src_end); + b3 = *ctx->src_ptr++; + b2 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + b0 = *ctx->src_ptr++; + return (b3 + ((uint32_t)b2 << 8) + ((uint32_t)b1 << 16) + ((uint32_t)b0 << 24)); +} + +static void XMIToMidiCopy(struct XMIToMidiConversionContext *ctx, char *b, uint32_t len) +{ + EPI_ASSERT(ctx->src_ptr + len < ctx->src_end); + memcpy(b, ctx->src_ptr, len); + ctx->src_ptr += len; +} + +static constexpr uint16_t kDestinationChunkSize = 8192; +static void XMIToMidiResizeDestination(struct XMIToMidiConversionContext *ctx) +{ + uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); + if (ctx->dyn_out && ctx->dyn_out_cur) + { + ctx->dyn_out_cur->resize(ctx->dstsize + kDestinationChunkSize); + ctx->dst = ctx->dyn_out_cur->data(); + } + else + ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + kDestinationChunkSize); + ctx->dstsize += kDestinationChunkSize; + ctx->dstrem += kDestinationChunkSize; + ctx->dst_ptr = ctx->dst + pos; +} + +static void XMIToMidiWrite1(struct XMIToMidiConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 1) + XMIToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem--; +} + +static void XMIToMidiWrite2(struct XMIToMidiConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 2) + XMIToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = (val >> 8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 2; +} + +static void XMIToMidiWrite4(struct XMIToMidiConversionContext *ctx, uint32_t val) +{ + if (ctx->dstrem < 4) + XMIToMidiResizeDestination(ctx); + *ctx->dst_ptr++ = (val >> 24) & 0xff; + *ctx->dst_ptr++ = (val >> 16) & 0xff; + *ctx->dst_ptr++ = (val >> 8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 4; +} + +static void XMIToMidiSeekSource(struct XMIToMidiConversionContext *ctx, uint32_t pos) +{ + ctx->src_ptr = ctx->src + pos; +} + +static void XMIToMidiSeekDestination(struct XMIToMidiConversionContext *ctx, uint32_t pos) +{ + ctx->dst_ptr = ctx->dst + pos; + while (ctx->dstsize < pos) + XMIToMidiResizeDestination(ctx); + ctx->dstrem = ctx->dstsize - pos; +} + +static void XMIToMidiSkipSource(struct XMIToMidiConversionContext *ctx, int32_t pos) +{ + ctx->src_ptr += pos; +} + +static void XMIToMidiSkipDestination(struct XMIToMidiConversionContext *ctx, int32_t pos) +{ + size_t newpos; + ctx->dst_ptr += pos; + newpos = ctx->dst_ptr - ctx->dst; + while (ctx->dstsize < newpos) + XMIToMidiResizeDestination(ctx); + ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); +} + +static uint32_t XMIToMidiGetSourceSize(struct XMIToMidiConversionContext *ctx) +{ + return (ctx->srcsize); +} + +static uint32_t XMIToMidiGetSourcePosition(struct XMIToMidiConversionContext *ctx) +{ + return (uint32_t)(ctx->src_ptr - ctx->src); +} + +static uint32_t XMIToMidiGetDestinationPosition(struct XMIToMidiConversionContext *ctx) +{ + return (uint32_t)(ctx->dst_ptr - ctx->dst); +} + +/* This is a default set of patches to convert from MT32 to GM + * The index is the MT32 Patch number and the value is the GM Patch + * This is only suitable for music that doesn't do timbre changes + * XMIDIs that contain Timbre changes will not convert properly. + */ +static constexpr char Mt32ToGmMap[128] = { + 0, /* 0 Piano 1 */ + 1, /* 1 Piano 2 */ + 2, /* 2 Piano 3 (synth) */ + 4, /* 3 EPiano 1 */ + 4, /* 4 EPiano 2 */ + 5, /* 5 EPiano 3 */ + 5, /* 6 EPiano 4 */ + 3, /* 7 Honkytonk */ + 16, /* 8 Organ 1 */ + 17, /* 9 Organ 2 */ + 18, /* 10 Organ 3 */ + 16, /* 11 Organ 4 */ + 19, /* 12 Pipe Organ 1 */ + 19, /* 13 Pipe Organ 2 */ + 19, /* 14 Pipe Organ 3 */ + 21, /* 15 Accordion */ + 6, /* 16 Harpsichord 1 */ + 6, /* 17 Harpsichord 2 */ + 6, /* 18 Harpsichord 3 */ + 7, /* 19 Clavinet 1 */ + 7, /* 20 Clavinet 2 */ + 7, /* 21 Clavinet 3 */ + 8, /* 22 Celesta 1 */ + 8, /* 23 Celesta 2 */ + 62, /* 24 Synthbrass 1 (62) */ + 63, /* 25 Synthbrass 2 (63) */ + 62, /* 26 Synthbrass 3 Bank 8 */ + 63, /* 27 Synthbrass 4 Bank 8 */ + 38, /* 28 Synthbass 1 */ + 39, /* 29 Synthbass 2 */ + 38, /* 30 Synthbass 3 Bank 8 */ + 39, /* 31 Synthbass 4 Bank 8 */ + 88, /* 32 Fantasy */ + 90, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ + 52, /* 34 Choral ?? Currently set to SynthVox(54). Should it be + ChoirAhhs(52)??? */ + 92, /* 35 Glass */ + 97, /* 36 Soundtrack */ + 99, /* 37 Atmosphere */ + 14, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular + Bells(14) would be better. It is! */ + 54, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ + 98, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ + 96, /* 41 IceRain */ + 68, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ + 95, /* 43 EchoPans, no equiv, setting to SweepPad */ + 81, /* 44 DoctorSolo Bank 8 */ + 87, /* 45 SchoolDaze, no real equiv */ + 112, /* 46 Bell Singer */ + 80, /* 47 SquareWave */ + 48, /* 48 Strings 1 */ + 48, /* 49 Strings 2 - should be 49 */ + 44, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should + be 50 */ + 45, /* 51 Pizzicato Strings */ + 40, /* 52 Violin 1 */ + 40, /* 53 Violin 2 ? Viola */ + 42, /* 54 Cello 1 */ + 42, /* 55 Cello 2 */ + 43, /* 56 Contrabass */ + 46, /* 57 Harp 1 */ + 46, /* 58 Harp 2 */ + 24, /* 59 Guitar 1 (Nylon) */ + 25, /* 60 Guitar 2 (Steel) */ + 26, /* 61 Elec Guitar 1 */ + 27, /* 62 Elec Guitar 2 */ + 104, /* 63 Sitar */ + 32, /* 64 Acou Bass 1 */ + 32, /* 65 Acou Bass 2 */ + 33, /* 66 Elec Bass 1 */ + 34, /* 67 Elec Bass 2 */ + 36, /* 68 Slap Bass 1 */ + 37, /* 69 Slap Bass 2 */ + 35, /* 70 Fretless Bass 1 */ + 35, /* 71 Fretless Bass 2 */ + 73, /* 72 Flute 1 */ + 73, /* 73 Flute 2 */ + 72, /* 74 Piccolo 1 */ + 72, /* 75 Piccolo 2 */ + 74, /* 76 Recorder */ + 75, /* 77 Pan Pipes */ + 64, /* 78 Sax 1 */ + 65, /* 79 Sax 2 */ + 66, /* 80 Sax 3 */ + 67, /* 81 Sax 4 */ + 71, /* 82 Clarinet 1 */ + 71, /* 83 Clarinet 2 */ + 68, /* 84 Oboe */ + 69, /* 85 English Horn (Cor Anglais) */ + 70, /* 86 Bassoon */ + 22, /* 87 Harmonica */ + 56, /* 88 Trumpet 1 */ + 56, /* 89 Trumpet 2 */ + 57, /* 90 Trombone 1 */ + 57, /* 91 Trombone 2 */ + 60, /* 92 French Horn 1 */ + 60, /* 93 French Horn 2 */ + 58, /* 94 Tuba */ + 61, /* 95 Brass Section 1 */ + 61, /* 96 Brass Section 2 */ + 11, /* 97 Vibes 1 */ + 11, /* 98 Vibes 2 */ + 99, /* 99 Syn Mallet Bank 1 */ + 112, /* 100 WindBell no real equiv Set to TinkleBell(112) */ + 9, /* 101 Glockenspiel */ + 14, /* 102 Tubular Bells */ + 13, /* 103 Xylophone */ + 12, /* 104 Marimba */ + 107, /* 105 Koto */ + 111, /* 106 Sho?? set to Shanai(111) */ + 77, /* 107 Shakauhachi */ + 78, /* 108 Whistle 1 */ + 78, /* 109 Whistle 2 */ + 76, /* 110 Bottle Blow */ + 76, /* 111 Breathpipe no real equiv set to bottle blow(76) */ + 47, /* 112 Timpani */ + 117, /* 113 Melodic Tom */ + 116, /* 114 Deap Snare no equiv, set to Taiko(116) */ + 118, /* 115 Electric Perc 1 */ + 118, /* 116 Electric Perc 2 */ + 116, /* 117 Taiko */ + 115, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ + 119, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ + 115, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ + 112, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ + 55, /* 122 Orchestral Hit */ + 124, /* 123 Telephone */ + 123, /* 124 BirdTweet */ + 94, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ + 98, /* 126 Water Bell set to Crystal Pad(98) */ + 121 /* 127 Jungle Tune set to Breath Noise */ +}; + +/* Same as above, except include patch changes + * so GS instruments can be used */ +static constexpr char Mt32ToGsMap[256] = { + 0, + 0, /* 0 Piano 1 */ + 1, + 0, /* 1 Piano 2 */ + 2, + 0, /* 2 Piano 3 (synth) */ + 4, + 0, /* 3 EPiano 1 */ + 4, + 0, /* 4 EPiano 2 */ + 5, + 0, /* 5 EPiano 3 */ + 5, + 0, /* 6 EPiano 4 */ + 3, + 0, /* 7 Honkytonk */ + 16, + 0, /* 8 Organ 1 */ + 17, + 0, /* 9 Organ 2 */ + 18, + 0, /* 10 Organ 3 */ + 16, + 0, /* 11 Organ 4 */ + 19, + 0, /* 12 Pipe Organ 1 */ + 19, + 0, /* 13 Pipe Organ 2 */ + 19, + 0, /* 14 Pipe Organ 3 */ + 21, + 0, /* 15 Accordion */ + 6, + 0, /* 16 Harpsichord 1 */ + 6, + 0, /* 17 Harpsichord 2 */ + 6, + 0, /* 18 Harpsichord 3 */ + 7, + 0, /* 19 Clavinet 1 */ + 7, + 0, /* 20 Clavinet 2 */ + 7, + 0, /* 21 Clavinet 3 */ + 8, + 0, /* 22 Celesta 1 */ + 8, + 0, /* 23 Celesta 2 */ + 62, + 0, /* 24 Synthbrass 1 (62) */ + 63, + 0, /* 25 Synthbrass 2 (63) */ + 62, + 0, /* 26 Synthbrass 3 Bank 8 */ + 63, + 0, /* 27 Synthbrass 4 Bank 8 */ + 38, + 0, /* 28 Synthbass 1 */ + 39, + 0, /* 29 Synthbass 2 */ + 38, + 0, /* 30 Synthbass 3 Bank 8 */ + 39, + 0, /* 31 Synthbass 4 Bank 8 */ + 88, + 0, /* 32 Fantasy */ + 90, + 0, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ + 52, + 0, /* 34 Choral ?? Currently set to SynthVox(54). Should it be + ChoirAhhs(52)??? */ + 92, + 0, /* 35 Glass */ + 97, + 0, /* 36 Soundtrack */ + 99, + 0, /* 37 Atmosphere */ + 14, + 0, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular + Bells(14) would be better. It is! */ + 54, + 0, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ + 98, + 0, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ + 96, + 0, /* 41 IceRain */ + 68, + 0, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ + 95, + 0, /* 43 EchoPans, no equiv, setting to SweepPad */ + 81, + 0, /* 44 DoctorSolo Bank 8 */ + 87, + 0, /* 45 SchoolDaze, no real equiv */ + 112, + 0, /* 46 Bell Singer */ + 80, + 0, /* 47 SquareWave */ + 48, + 0, /* 48 Strings 1 */ + 48, + 0, /* 49 Strings 2 - should be 49 */ + 44, + 0, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should + be 50 */ + 45, + 0, /* 51 Pizzicato Strings */ + 40, + 0, /* 52 Violin 1 */ + 40, + 0, /* 53 Violin 2 ? Viola */ + 42, + 0, /* 54 Cello 1 */ + 42, + 0, /* 55 Cello 2 */ + 43, + 0, /* 56 Contrabass */ + 46, + 0, /* 57 Harp 1 */ + 46, + 0, /* 58 Harp 2 */ + 24, + 0, /* 59 Guitar 1 (Nylon) */ + 25, + 0, /* 60 Guitar 2 (Steel) */ + 26, + 0, /* 61 Elec Guitar 1 */ + 27, + 0, /* 62 Elec Guitar 2 */ + 104, + 0, /* 63 Sitar */ + 32, + 0, /* 64 Acou Bass 1 */ + 32, + 0, /* 65 Acou Bass 2 */ + 33, + 0, /* 66 Elec Bass 1 */ + 34, + 0, /* 67 Elec Bass 2 */ + 36, + 0, /* 68 Slap Bass 1 */ + 37, + 0, /* 69 Slap Bass 2 */ + 35, + 0, /* 70 Fretless Bass 1 */ + 35, + 0, /* 71 Fretless Bass 2 */ + 73, + 0, /* 72 Flute 1 */ + 73, + 0, /* 73 Flute 2 */ + 72, + 0, /* 74 Piccolo 1 */ + 72, + 0, /* 75 Piccolo 2 */ + 74, + 0, /* 76 Recorder */ + 75, + 0, /* 77 Pan Pipes */ + 64, + 0, /* 78 Sax 1 */ + 65, + 0, /* 79 Sax 2 */ + 66, + 0, /* 80 Sax 3 */ + 67, + 0, /* 81 Sax 4 */ + 71, + 0, /* 82 Clarinet 1 */ + 71, + 0, /* 83 Clarinet 2 */ + 68, + 0, /* 84 Oboe */ + 69, + 0, /* 85 English Horn (Cor Anglais) */ + 70, + 0, /* 86 Bassoon */ + 22, + 0, /* 87 Harmonica */ + 56, + 0, /* 88 Trumpet 1 */ + 56, + 0, /* 89 Trumpet 2 */ + 57, + 0, /* 90 Trombone 1 */ + 57, + 0, /* 91 Trombone 2 */ + 60, + 0, /* 92 French Horn 1 */ + 60, + 0, /* 93 French Horn 2 */ + 58, + 0, /* 94 Tuba */ + 61, + 0, /* 95 Brass Section 1 */ + 61, + 0, /* 96 Brass Section 2 */ + 11, + 0, /* 97 Vibes 1 */ + 11, + 0, /* 98 Vibes 2 */ + 99, + 0, /* 99 Syn Mallet Bank 1 */ + 112, + 0, /* 100 WindBell no real equiv Set to TinkleBell(112) */ + 9, + 0, /* 101 Glockenspiel */ + 14, + 0, /* 102 Tubular Bells */ + 13, + 0, /* 103 Xylophone */ + 12, + 0, /* 104 Marimba */ + 107, + 0, /* 105 Koto */ + 111, + 0, /* 106 Sho?? set to Shanai(111) */ + 77, + 0, /* 107 Shakauhachi */ + 78, + 0, /* 108 Whistle 1 */ + 78, + 0, /* 109 Whistle 2 */ + 76, + 0, /* 110 Bottle Blow */ + 76, + 0, /* 111 Breathpipe no real equiv set to bottle blow(76) */ + 47, + 0, /* 112 Timpani */ + 117, + 0, /* 113 Melodic Tom */ + 116, + 0, /* 114 Deap Snare no equiv, set to Taiko(116) */ + 118, + 0, /* 115 Electric Perc 1 */ + 118, + 0, /* 116 Electric Perc 2 */ + 116, + 0, /* 117 Taiko */ + 115, + 0, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ + 119, + 0, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ + 115, + 0, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ + 112, + 0, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ + 55, + 0, /* 122 Orchestral Hit */ + 124, + 0, /* 123 Telephone */ + 123, + 0, /* 124 BirdTweet */ + 94, + 0, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ + 98, + 0, /* 126 Water Bell set to Crystal Pad(98) */ + 121, + 0 /* 127 Jungle Tune set to Breath Noise */ +}; + +static int ConvertXMIToMidi(uint8_t *in, uint32_t insize, std::vector> &out, uint32_t convert_type) +{ + struct XMIToMidiConversionContext ctx; + unsigned int i; + int ret = -1; + + if (convert_type > kXMIConvertMt32ToGs) + { + /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", + * __FUNCTION__, __LINE__, convert_type);*/ + return (ret); + } + + EPI_CLEAR_MEMORY(&ctx, XMIToMidiConversionContext, 1); + ctx.src = ctx.src_ptr = in; + ctx.srcsize = insize; + ctx.src_end = ctx.src + insize; + ctx.convert_type = convert_type; + ctx.dyn_out = &out; + + if (XMIToMidiParseXMI(&ctx) < 0) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, nullptr, + * 0);*/ + goto _end; + } + + if (XMIToMidiExtractTracks(&ctx, 0) < 0) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, nullptr, + * 0);*/ + goto _end; + } + + for (i = 0; i < ctx.info.tracks; i++) + { + out.push_back(std::vector()); + ctx.dyn_out_cur = &out.back(); + ctx.dyn_out_cur->resize(kDestinationChunkSize); + ctx.dst = ctx.dyn_out_cur->data(); + ctx.dst_ptr = ctx.dst; + ctx.dstsize = kDestinationChunkSize; + ctx.dstrem = kDestinationChunkSize; + + /* Header is 14 bytes long and add the rest as well */ + XMIToMidiWrite1(&ctx, 'M'); + XMIToMidiWrite1(&ctx, 'T'); + XMIToMidiWrite1(&ctx, 'h'); + XMIToMidiWrite1(&ctx, 'd'); + + XMIToMidiWrite4(&ctx, 6); + + XMIToMidiWrite2(&ctx, ctx.info.type); + XMIToMidiWrite2(&ctx, 1); + XMIToMidiWrite2(&ctx, ctx.timing[i]); /* write divisions from track0 */ + + XMIToMidiConvertListToMidiTrack(&ctx, ctx.events[i]); + ctx.dyn_out_cur->resize(ctx.dstsize - ctx.dstrem); + } + + ret = 0; + +_end: /* cleanup */ + if (ret < 0) + { + out.clear(); + } + if (ctx.events) + { + for (i = 0; i < ctx.info.tracks; i++) + XMIToMidiDeleteEventList(ctx.events[i]); + free(ctx.events); + } + free(ctx.timing); + + return (ret); +} + +static void XMIToMidiDeleteEventList(XMIToMidiEvent *mlist) +{ + XMIToMidiEvent *event; + XMIToMidiEvent *next; + + next = mlist; + + while ((event = next) != nullptr) + { + next = event->next; + free(event->buffer); + free(event); + } +} + +/* Sets current to the new event and updates list */ +static void XMIToMidiCreateNewEvent(struct XMIToMidiConversionContext *ctx, int32_t time) +{ + if (!ctx->list) + { + ctx->list = ctx->current = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); + ctx->current->time = (time < 0) ? 0 : time; + return; + } + + if (time < 0) + { + XMIToMidiEvent *event = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); + event->next = ctx->list; + ctx->list = ctx->current = event; + return; + } + + if (ctx->current->time > time) + ctx->current = ctx->list; + + while (ctx->current->next) + { + if (ctx->current->next->time > time) + { + XMIToMidiEvent *event = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); + event->next = ctx->current->next; + ctx->current->next = event; + ctx->current = event; + ctx->current->time = time; + return; + } + + ctx->current = ctx->current->next; + } + + ctx->current->next = (XMIToMidiEvent *)calloc(1, sizeof(XMIToMidiEvent)); + ctx->current = ctx->current->next; + ctx->current->time = time; +} + +/* Conventional Variable Length Quantity */ +static int XMIToMidiGetVlq(struct XMIToMidiConversionContext *ctx, uint32_t *quant) +{ + int i; + uint32_t data; + + *quant = 0; + for (i = 0; i < 4; i++) + { + if (ctx->src_ptr + 1 >= ctx->src + ctx->srcsize) + break; + data = XMIToMidiRead1(ctx); + *quant <<= 7; + *quant |= data & 0x7F; + + if (!(data & 0x80)) + { + i++; + break; + } + } + return (i); +} + +/* XMIDI Delta Variable Length Quantity */ +static int XMIToMidiGetVlq2(struct XMIToMidiConversionContext *ctx, uint32_t *quant) +{ + int i; + int32_t data; + + *quant = 0; + for (i = 0; XMIToMidiGetSourcePosition(ctx) != XMIToMidiGetSourceSize(ctx); ++i) + { + data = XMIToMidiRead1(ctx); + if (data & 0x80) + { + XMIToMidiSkipSource(ctx, -1); + break; + } + *quant += data; + } + return (i); +} + +static int XMIToMidiPutVlq(struct XMIToMidiConversionContext *ctx, uint32_t value) +{ + int32_t buffer; + int i = 1, j; + buffer = value & 0x7F; + while (value >>= 7) + { + buffer <<= 8; + buffer |= ((value & 0x7F) | 0x80); + i++; + } + for (j = 0; j < i; j++) + { + XMIToMidiWrite1(ctx, buffer & 0xFF); + buffer >>= 8; + } + + return (i); +} + +/* Converts Events + * + * Source is at the first data byte + * size 1 is single data byte + * size 2 is dual data byte + * size 3 is XMI Note on + * Returns bytes converted */ +static int XMIToMidiConvertEvent(struct XMIToMidiConversionContext *ctx, const int32_t time, const uint8_t status, + const int size) +{ + uint32_t delta = 0; + int32_t data; + XMIToMidiEvent *prev; + int i; + + data = XMIToMidiRead1(ctx); + + /*HACK!*/ + if (((status >> 4) == 0xB) && (status & 0xF) != 9 && (data == 114)) + { + data = 32; /*Change XMI 114 controller into XG bank*/ + } + + /* Bank changes are handled here */ + if ((status >> 4) == 0xB && data == 0) + { + data = XMIToMidiRead1(ctx); + + ctx->bank127[status & 0xF] = 0; + + if (ctx->convert_type == kXMIConvertMt32ToGm || ctx->convert_type == kXMIConvertMt32ToGs || + ctx->convert_type == kXMIConvertMt32ToGs127 || + (ctx->convert_type == kXMIConvertMt32ToGs127Drum && (status & 0xF) == 9)) + return (2); + + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = status; + ctx->current->data[0] = 0; + ctx->current->data[1] = data == 127 ? 0 : data; /*HACK:*/ + + if (ctx->convert_type == kXMIConvertGs127ToGs && data == 127) + ctx->bank127[status & 0xF] = 1; + + return (2); + } + + /* Handling for patch change mt32 conversion, probably should go elsewhere + */ + if ((status >> 4) == 0xC && (status & 0xF) != 9 && ctx->convert_type != kXMINoConversion) + { + if (ctx->convert_type == kXMIConvertMt32ToGm) + { + data = Mt32ToGmMap[data]; + } + else if ((ctx->convert_type == kXMIConvertGs127ToGs && ctx->bank127[status & 0xF]) || + ctx->convert_type == kXMIConvertMt32ToGs || ctx->convert_type == kXMIConvertMt32ToGs127Drum) + { + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = 0xB0 | (status & 0xF); + ctx->current->data[0] = 0; + ctx->current->data[1] = Mt32ToGsMap[data * 2 + 1]; + + data = Mt32ToGsMap[data * 2]; + } + else if (ctx->convert_type == kXMIConvertMt32ToGs127) + { + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = 0xB0 | (status & 0xF); + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + } + /* Drum track handling */ + else if ((status >> 4) == 0xC && (status & 0xF) == 9 && + (ctx->convert_type == kXMIConvertMt32ToGs127Drum || ctx->convert_type == kXMIConvertMt32ToGs127)) + { + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = 0xB9; + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = status; + + ctx->current->data[0] = data; + + if (size == 1) + return (1); + + ctx->current->data[1] = XMIToMidiRead1(ctx); + + if (size == 2) + return (2); + + /* XMI Note On handling */ + prev = ctx->current; + i = XMIToMidiGetVlq(ctx, &delta); + XMIToMidiCreateNewEvent(ctx, time + delta * 3); + + ctx->current->status = status; + ctx->current->data[0] = data; + ctx->current->data[1] = 0; + ctx->current = prev; + + return (i + 2); +} + +/* Simple routine to convert system messages */ +static int32_t XMIToMidiConvertSystemMessage(struct XMIToMidiConversionContext *ctx, const int32_t time, + const uint8_t status) +{ + int32_t i = 0; + + XMIToMidiCreateNewEvent(ctx, time); + ctx->current->status = status; + + /* Handling of Meta events */ + if (status == 0xFF) + { + ctx->current->data[0] = XMIToMidiRead1(ctx); + i++; + } + + i += XMIToMidiGetVlq(ctx, &ctx->current->len); + + if (!ctx->current->len) + return (i); + + ctx->current->buffer = (uint8_t *)malloc(sizeof(uint8_t) * ctx->current->len); + XMIToMidiCopy(ctx, (char *)ctx->current->buffer, ctx->current->len); + + return (i + ctx->current->len); +} + +/* XMIDI and Midi to List + * Returns XMIDI PPQN */ +static int32_t XMIToMidiConvertFiletoList(struct XMIToMidiConversionContext *ctx, const XMIToMidiBranch *rbrn) +{ + int32_t time = 0; + uint32_t data; + int32_t end = 0; + int32_t tempo = 500000; + int32_t tempo_set = 0; + uint32_t status = 0; + uint32_t file_size = XMIToMidiGetSourceSize(ctx); + uint32_t begin = XMIToMidiGetSourcePosition(ctx); + + /* Set Drum track to correct setting if required */ + if (ctx->convert_type == kXMIConvertMt32ToGs127) + { + XMIToMidiCreateNewEvent(ctx, 0); + ctx->current->status = 0xB9; + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + + while (!end && XMIToMidiGetSourcePosition(ctx) < file_size) + { + uint32_t offset = XMIToMidiGetSourcePosition(ctx) - begin; + + /* search for branch to this offset */ + for (unsigned i = 0, n = rbrn->count; i < n; ++i) + { + if (offset == rbrn->offset[i]) + { + unsigned id = rbrn->id[i]; + + XMIToMidiCreateNewEvent(ctx, time); + + uint8_t *marker = (uint8_t *)malloc(sizeof(uint8_t) * 8); + memcpy(marker, ":XBRN:", 6); + const char hex[] = "0123456789ABCDEF"; + marker[6] = hex[id >> 4]; + marker[7] = hex[id & 15]; + + ctx->current->status = 0xFF; + ctx->current->data[0] = 0x06; + ctx->current->len = 8; + + ctx->current->buffer = marker; + } + } + + XMIToMidiGetVlq2(ctx, &data); + time += data * 3; + + status = XMIToMidiRead1(ctx); + + switch (status >> 4) + { + case kXMIStatusNoteOn: + XMIToMidiConvertEvent(ctx, time, status, 3); + break; + + /* 2 byte data */ + case kXMIStatusNoteOff: + case kXMIStatusAftertouch: + case kXMIStatusController: + case kXMIStatusPitchWheel: + XMIToMidiConvertEvent(ctx, time, status, 2); + break; + + /* 1 byte data */ + case kXMIStatusProgramChange: + case kXMIStatusPressure: + XMIToMidiConvertEvent(ctx, time, status, 1); + break; + + case kXMIStatusSysex: + if (status == 0xFF) + { + int32_t pos = XMIToMidiGetSourcePosition(ctx); + uint32_t dat = XMIToMidiRead1(ctx); + + if (dat == 0x2F) /* End */ + end = 1; + else if (dat == 0x51 && !tempo_set) /* Tempo. Need it for PPQN */ + { + XMIToMidiSkipSource(ctx, 1); + tempo = XMIToMidiRead1(ctx) << 16; + tempo += XMIToMidiRead1(ctx) << 8; + tempo += XMIToMidiRead1(ctx); + tempo *= 3; + tempo_set = 1; + } + else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */ + { + XMIToMidiGetVlq(ctx, &dat); + XMIToMidiSkipSource(ctx, dat); + break; + } + + XMIToMidiSeekSource(ctx, pos); + } + XMIToMidiConvertSystemMessage(ctx, time, status); + break; + + default: + break; + } + } + return ((tempo * 3) / 25000); +} + +/* Converts and event list to a MidiTrack + * Returns bytes of the array + * buf can be nullptr */ +static uint32_t XMIToMidiConvertListToMidiTrack(struct XMIToMidiConversionContext *ctx, XMIToMidiEvent *mlist) +{ + int32_t time = 0; + XMIToMidiEvent *event; + uint32_t delta; + uint8_t last_status = 0; + uint32_t i = 8; + uint32_t j; + uint32_t size_pos, cur_pos; + int end = 0; + + XMIToMidiWrite1(ctx, 'M'); + XMIToMidiWrite1(ctx, 'T'); + XMIToMidiWrite1(ctx, 'r'); + XMIToMidiWrite1(ctx, 'k'); + + size_pos = XMIToMidiGetDestinationPosition(ctx); + XMIToMidiSkipDestination(ctx, 4); + + for (event = mlist; event && !end; event = event->next) + { + delta = (event->time - time); + time = event->time; + + i += XMIToMidiPutVlq(ctx, delta); + + if ((event->status != last_status) || (event->status >= 0xF0)) + { + XMIToMidiWrite1(ctx, event->status); + i++; + } + + last_status = event->status; + + switch (event->status >> 4) + { + /* 2 bytes data + * Note off, Note on, Aftertouch, Controller and Pitch Wheel */ + case 0x8: + case 0x9: + case 0xA: + case 0xB: + case 0xE: + XMIToMidiWrite1(ctx, event->data[0]); + XMIToMidiWrite1(ctx, event->data[1]); + i += 2; + break; + + /* 1 bytes data + * Program Change and Channel Pressure */ + case 0xC: + case 0xD: + XMIToMidiWrite1(ctx, event->data[0]); + i++; + break; + + /* Variable length + * SysEx */ + case 0xF: + if (event->status == 0xFF) + { + if (event->data[0] == 0x2f) + end = 1; + XMIToMidiWrite1(ctx, event->data[0]); + i++; + } + i += XMIToMidiPutVlq(ctx, event->len); + if (event->len) + { + for (j = 0; j < event->len; j++) + { + XMIToMidiWrite1(ctx, event->buffer[j]); + i++; + } + } + break; + + /* Never occur */ + default: + /*_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);*/ + break; + } + } + + cur_pos = XMIToMidiGetDestinationPosition(ctx); + XMIToMidiSeekDestination(ctx, size_pos); + XMIToMidiWrite4(ctx, i - 8); + XMIToMidiSeekDestination(ctx, cur_pos); + + return (i); +} + +/* Assumes correct xmidi */ +static uint32_t XMIToMidiExtractTracksFromXMI(struct XMIToMidiConversionContext *ctx) +{ + uint32_t num = 0; + signed short ppqn; + uint32_t len = 0; + int32_t begin; + char buf[32]; + uint32_t branch[128]; + + /* clear branch points */ + for (unsigned i = 0; i < 128; ++i) + branch[i] = ~0u; + + while (XMIToMidiGetSourcePosition(ctx) < XMIToMidiGetSourceSize(ctx) && num != ctx->info.tracks) + { + /* Read first 4 bytes of name */ + XMIToMidiCopy(ctx, buf, 4); + len = XMIToMidiRead4(ctx); + + /* Skip the FORM entries */ + if (!memcmp(buf, "FORM", 4)) + { + XMIToMidiSkipSource(ctx, 4); + XMIToMidiCopy(ctx, buf, 4); + len = XMIToMidiRead4(ctx); + } + + if (!memcmp(buf, "RBRN", 4)) + { + begin = XMIToMidiGetSourcePosition(ctx); + uint32_t count; + + if (len < 2) + { + /* insufficient data */ + goto rbrn_nodata; + } + + count = XMIToMidiRead2(ctx); + if (len - 2 < 6 * count) + { + /* insufficient data */ + goto rbrn_nodata; + } + + for (uint32_t i = 0; i < count; ++i) + { + /* read branch point as byte offset */ + uint32_t ctlvalue = XMIToMidiRead2(ctx); + uint32_t evtoffset = XMIToMidiRead4LittleEndian(ctx); + if (ctlvalue < 128) + branch[ctlvalue] = evtoffset; + } + + rbrn_nodata: + XMIToMidiSeekSource(ctx, begin + ((len + 1) & ~1)); + continue; + } + + if (memcmp(buf, "EVNT", 4)) + { + XMIToMidiSkipSource(ctx, (len + 1) & ~1); + continue; + } + + ctx->list = nullptr; + begin = XMIToMidiGetSourcePosition(ctx); + + /* Rearrange branches as structure */ + XMIToMidiBranch rbrn; + rbrn.count = 0; + for (unsigned i = 0; i < 128; ++i) + { + if (branch[i] != ~0u) + { + unsigned index = rbrn.count; + rbrn.id[index] = i; + rbrn.offset[index] = branch[i]; + rbrn.count = index + 1; + } + } + + /* Convert it */ + if ((ppqn = XMIToMidiConvertFiletoList(ctx, &rbrn)) == 0) + { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, nullptr, + * 0);*/ + break; + } + ctx->timing[num] = ppqn; + ctx->events[num] = ctx->list; + + /* Increment Counter */ + num++; + + /* go to start of next track */ + XMIToMidiSeekSource(ctx, begin + ((len + 1) & ~1)); + + /* clear branch points */ + for (unsigned i = 0; i < 128; ++i) + branch[i] = ~0u; + } + + /* Return how many were converted */ + return (num); +} + +static int XMIToMidiParseXMI(struct XMIToMidiConversionContext *ctx) +{ + uint32_t i; + uint32_t start; + uint32_t len; + uint32_t chunk_len; + uint32_t file_size; + char buf[32]; + + file_size = XMIToMidiGetSourceSize(ctx); + if (XMIToMidiGetSourcePosition(ctx) + 8 > file_size) + { + badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too + short)", 0);*/ + return (-1); + } + + /* Read first 4 bytes of header */ + XMIToMidiCopy(ctx, buf, 4); + + /* Could be XMIDI */ + if (!memcmp(buf, "FORM", 4)) + { + /* Read length of */ + len = XMIToMidiRead4(ctx); + + start = XMIToMidiGetSourcePosition(ctx); + if (start + 4 > file_size) + goto badfile; + + /* Read 4 bytes of type */ + XMIToMidiCopy(ctx, buf, 4); + + /* XDIRless XMIDI, we can handle them here. */ + if (!memcmp(buf, "XMID", 4)) + { + /*_WM_DEBUG_MSG("Warning: XMIDI without XDIR");*/ + ctx->info.tracks = 1; + } + /* Not an XMIDI that we recognise */ + else if (memcmp(buf, "XDIR", 4)) + { + goto badfile; + } + else + { /* Seems Valid */ + ctx->info.tracks = 0; + + for (i = 4; i < len; i++) + { + /* check too short files */ + if (XMIToMidiGetSourcePosition(ctx) + 10 > file_size) + break; + + /* Read 4 bytes of type */ + XMIToMidiCopy(ctx, buf, 4); + + /* Read length of chunk */ + chunk_len = XMIToMidiRead4(ctx); + + /* Add eight bytes */ + i += 8; + + if (memcmp(buf, "INFO", 4)) + { + /* Must align */ + XMIToMidiSkipSource(ctx, (chunk_len + 1) & ~1); + i += (chunk_len + 1) & ~1; + continue; + } + + /* Must be at least 2 bytes long */ + if (chunk_len < 2) + break; + + ctx->info.tracks = XMIToMidiRead2(ctx); + break; + } + + /* Didn't get to fill the header */ + if (ctx->info.tracks == 0) + { + goto badfile; + } + + /* Ok now to start part 2 + * Goto the right place */ + XMIToMidiSeekSource(ctx, start + ((len + 1) & ~1)); + if (XMIToMidiGetSourcePosition(ctx) + 12 > file_size) + goto badfile; + + /* Read 4 bytes of type */ + XMIToMidiCopy(ctx, buf, 4); + + if (memcmp(buf, "CAT ", 4)) + { + /*_WM_ERROR_NEW("XMI error: expected \"CAT \", found + \"%c%c%c%c\".", buf[0], buf[1], buf[2], buf[3]);*/ + return (-1); + } + + /* Now read length of this track */ + XMIToMidiRead4(ctx); + + /* Read 4 bytes of type */ + XMIToMidiCopy(ctx, buf, 4); + + if (memcmp(buf, "XMID", 4)) + { + /*_WM_ERROR_NEW("XMI error: expected \"XMID\", found + \"%c%c%c%c\".", buf[0], buf[1], buf[2], buf[3]);*/ + return (-1); + } + + /* Valid XMID */ + ctx->datastart = XMIToMidiGetSourcePosition(ctx); + return (0); + } + } + + return (-1); +} + +static int XMIToMidiExtractTracks(struct XMIToMidiConversionContext *ctx, int32_t dstTrackNumber) +{ + uint32_t i; + + ctx->events = (XMIToMidiEvent **)calloc(ctx->info.tracks, sizeof(XMIToMidiEvent *)); + ctx->timing = (int16_t *)calloc(ctx->info.tracks, sizeof(int16_t)); + /* type-2 for multi-tracks, type-0 otherwise */ + ctx->info.type = (ctx->info.tracks > 1 && (dstTrackNumber < 0 || ctx->info.tracks >= dstTrackNumber)) ? 2 : 0; + + XMIToMidiSeekSource(ctx, ctx->datastart); + i = XMIToMidiExtractTracksFromXMI(ctx); + + if (i != ctx->info.tracks) + { + /*_WM_ERROR_NEW("XMI error: extracted only %u out of %u tracks from + XMIDI", ctx->info.tracks, i);*/ + return (-1); + } + + return (0); +} +#endif + +/*! Raw MIDI event hook */ +typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, + size_t len); +/*! PCM render */ +typedef void (*PcmRender)(void *userdata, uint8_t *stream, size_t length); +/*! Library internal debug messages */ +typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); +/*! Loop Start event hook */ +typedef void (*LoopStartHook)(void *userdata); +/*! Loop Start event hook */ +typedef void (*LoopEndHook)(void *userdata); +typedef void (*SongStartHook)(void *userdata); + +/*! Note-On MIDI event */ +typedef void (*RtNoteOn)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); +/*! Note-Off MIDI event */ +typedef void (*RtNoteOff)(void *userdata, uint8_t channel, uint8_t note); +/*! Note-Off MIDI event with a velocity */ +typedef void (*RtNoteOffVel)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); +/*! Note aftertouch MIDI event */ +typedef void (*RtNoteAfterTouch)(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal); +/*! Channel aftertouch MIDI event */ +typedef void (*RtChannelAfterTouch)(void *userdata, uint8_t channel, uint8_t atVal); +/*! Controller change MIDI event */ +typedef void (*RtControllerChange)(void *userdata, uint8_t channel, uint8_t type, uint8_t value); +/*! Patch change MIDI event */ +typedef void (*RtPatchChange)(void *userdata, uint8_t channel, uint8_t patch); +/*! Pitch bend MIDI event */ +typedef void (*RtPitchBend)(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb); +/*! System Exclusive MIDI event */ +typedef void (*RtSysEx)(void *userdata, const uint8_t *msg, size_t size); +/*! Meta event hook */ +typedef void (*MetaEventHook)(void *userdata, uint8_t type, const uint8_t *data, size_t len); +/*! Device Switch MIDI event */ +typedef void (*RtDeviceSwitch)(void *userdata, size_t track, const char *data, size_t length); +/*! Get the channels offset for current MIDI device */ +typedef size_t (*RtCurrentDevice)(void *userdata, size_t track); +/*! [Non-Standard] Pass raw OPL3 data to the chip + */ +typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value); + +/** + \brief Real-Time MIDI interface between Sequencer and the Synthesizer + */ +struct MidiRealTimeInterface +{ + /*! MIDI event hook which catches all MIDI events */ + RawEventHook onEvent; + /*! User data which will be passed through On-Event hook */ + void *onEvent_userdata; + + /*! PCM render hook which catches passing of loop start point */ + PcmRender onPcmRender; + /*! User data which will be passed through On-PCM-render hook */ + void *onPcmRender_userdata; + + /*! Sample rate */ + uint32_t pcmSampleRate; + + /*! Size of one sample in bytes */ + uint32_t pcmFrameSize; + + /*! Debug message hook */ + DebugMessageHook onDebugMessage; + /*! User data which will be passed through Debug Message hook */ + void *onDebugMessage_userdata; + + /*! Loop start hook which catches passing of loop start point */ + LoopStartHook onloopStart; + /*! User data which will be passed through On-LoopStart hook */ + void *onloopStart_userdata; + + /*! Loop start hook which catches passing of loop start point */ + LoopEndHook onloopEnd; + /*! User data which will be passed through On-LoopStart hook */ + void *onloopEnd_userdata; + + /*! Song start hook which is calling when starting playing song at begin + */ + SongStartHook onSongStart; + /*! User data which will be passed through On-SongStart hook */ + void *onSongStart_userdata; + + /*! MIDI Run Time event calls user data */ + void *rtUserData; + + /*************************************************** + * Standard MIDI events. All of them are required! * + ***************************************************/ + + /*! Note-On MIDI event hook */ + RtNoteOn rt_noteOn; + /*! Note-Off MIDI event hook */ + RtNoteOff rt_noteOff; + + /*! Note-Off MIDI event hook with a velocity */ + RtNoteOffVel rt_noteOffVel; + + /*! Note aftertouch MIDI event hook */ + RtNoteAfterTouch rt_noteAfterTouch; + + /*! Channel aftertouch MIDI event hook */ + RtChannelAfterTouch rt_channelAfterTouch; + + /*! Controller change MIDI event hook */ + RtControllerChange rt_controllerChange; + + /*! Patch change MIDI event hook */ + RtPatchChange rt_patchChange; + + /*! Pitch bend MIDI event hook */ + RtPitchBend rt_pitchBend; + + /*! System Exclusive MIDI event hook */ + RtSysEx rt_systemExclusive; + + /******************* + * Optional events * + *******************/ + + /*! Meta event hook which catches all meta events */ + MetaEventHook rt_metaEvent; + + /*! Device Switch MIDI event hook */ + RtDeviceSwitch rt_deviceSwitch; + + /*! Get the channels offset for current MIDI device hook. Returms + * multiple to 16 value. */ + RtCurrentDevice rt_currentDevice; + + /****************************************** + * NonStandard events. There are optional * + ******************************************/ + /*! [Non-Standard] Pass raw OPL3 data to the chip hook */ + RtRawOPL rt_rawOPL; +}; + +// MidiFraction is a stripped down version of +// Bisqwit's Fraction class with the following +// copyright: +/* + * Fraction number handling. + * Copyright (C) 1992,2001 Bisqwit (http://iki.fi/bisqwit/) + * + * The license of this file is in Public Domain: + * https://bisqwit.iki.fi/src/index.html + * + * "... and orphan source code files are copyrighted public domain." + */ + +class MidiFraction +{ + uint64_t num1_, num2_; + void Optim(); + + public: + MidiFraction() : num1_(0), num2_(1) + { + } + MidiFraction(uint64_t value) : num1_(value), num2_(1) + { + } + MidiFraction(uint64_t n, uint64_t d) : num1_(n), num2_(d) + { + } + inline double Value() const + { + return Nom() / (double)Denom(); + } + MidiFraction &operator*=(const MidiFraction &b) + { + num1_ *= b.Nom(); + num2_ *= b.Denom(); + Optim(); + return *this; + } + MidiFraction operator*(const MidiFraction &b) const + { + MidiFraction tmp(*this); + tmp *= b; + return tmp; + } + const uint64_t &Nom() const + { + return num1_; + } + const uint64_t &Denom() const + { + return num2_; + } +}; + +void MidiFraction::Optim() +{ + /* Euclidean algorithm */ + uint64_t n1, n2, nn1, nn2; + + nn1 = num1_; + nn2 = num2_; + + if (nn1 < nn2) + n1 = num1_, n2 = num2_; + else + n1 = num2_, n2 = num1_; + + if (!num1_) + { + num2_ = 1; + return; + } + for (;;) + { + uint64_t tmp = n2 % n1; + if (!tmp) + break; + n2 = n1; + n1 = tmp; + } + num1_ /= n1; + num2_ /= n1; +} + +MidiFraction operator*(const uint64_t bla, const MidiFraction &b) +{ + return MidiFraction(bla) * b; +} + +class MidiSequencer +{ + /** + * @brief MIDI Event utility container + */ + struct MidiEvent + { + /** + * @brief Main MIDI event types + */ + enum Types + { + //! Unknown event + kUnknown = 0x00, + //! Note-Off event + kNoteOff = 0x08, // size == 2 + //! Note-On event + kNoteOn = 0x09, // size == 2 + //! Note After-Touch event + kNoteTouch = 0x0A, // size == 2 + //! Controller change event + kControlChange = 0x0B, // size == 2 + //! Patch change event + kPatchChange = 0x0C, // size == 1 + //! Channel After-Touch event + kChannelAftertouch = 0x0D, // size == 1 + //! Pitch-bend change event + kPitchWheel = 0x0E, // size == 2 + + //! System Exclusive message, type 1 + kSysex = 0xF0, // size == len + //! Sys Com Song Position Pntr [LSB, MSB] + kSysComSongPositionPointer = 0xF2, // size == 2 + //! Sys Com Song Select(Song #) [0-127] + kSysComSongSelect = 0xF3, // size == 1 + //! System Exclusive message, type 2 + kSysex2 = 0xF7, // size == len + //! Special event + kSpecial = 0xFF + }; + /** + * @brief Special MIDI event sub-types + */ + enum SubTypes + { + //! Sequension number + kSequensionNumber = 0x00, // size == 2 + //! Text label + kText = 0x01, // size == len + //! Copyright notice + kCopyright = 0x02, // size == len + //! Sequence track title + kSequenceTrackTitle = 0x03, // size == len + //! Instrument title + kInstrumentTitle = 0x04, // size == len + //! Lyrics text fragment + kLyrics = 0x05, // size == len + //! MIDI Marker + kMarker = 0x06, // size == len + //! Cue Point + kCuePoint = 0x07, // size == len + //! [Non-Standard] Device Switch + kDeviceSwitch = 0x09, // size == len + //! MIDI Channel prefix + kMidiChannelPrefix = 0x20, // size == 1 + + //! End of Track event + kEndTrack = 0x2F, // size == 0 + //! Tempo change event + kTempoChange = 0x51, // size == 3 + //! SMPTE offset + kSmpteOffset = 0x54, // size == 5 + //! Time signature + kTimeSignature = 0x55, // size == 4 + //! Key signature + kKeySignature = 0x59, // size == 2 + //! Sequencer specs + kSequencerSpec = 0x7F, // size == len + + /* Non-standard, internal ADLMIDI usage only */ + //! [Non-Standard] Loop Start point + kLoopStart = 0xE1, // size == 0 + //! [Non-Standard] Loop End point + kLoopEnd = 0xE2, // size == 0 + //! [Non-Standard] Raw OPL data + kRawOPL = 0xE3, // size == 0 + + //! [Non-Standard] Loop Start point with support of multi-loops + kLoopStackBegin = 0xE4, // size == 1 + //! [Non-Standard] Loop End point with + //! support of multi-loops + kLoopStackEnd = 0xE5, // size == 0 + //! [Non-Standard] Loop End point with + //! support of multi-loops + kLoopStackBreak = 0xE6, // size == 0 + //! [Non-Standard] Callback Trigger + kCallbackTrigger = 0xE7, // size == 1 + + // Built-in hooks + kSongBeginHook = 0x101 + }; + //! Main type of event + uint_fast16_t type = kUnknown; + //! Sub-type of the event + uint_fast16_t sub_type = kUnknown; + //! Targeted MIDI channel + uint_fast16_t channel = 0; + //! Is valid event + uint_fast16_t is_valid = 1; + //! Reserved 5 bytes padding + uint_fast16_t padding[4]; + //! Absolute tick position (Used for the tempo calculation only) + uint64_t absolute_tick_position = 0; + //! Raw data of this event + std::vector data; + }; + + /** + * @brief A track position event contains a chain of MIDI events until next + * delay value + * + * Created with purpose to sort events by type in the same position + * (for example, to keep controllers always first than note on events or + * lower than note-off events) + */ + class MidiTrackRow + { + public: + MidiTrackRow(); + //! Clear MIDI row data + void Clear(); + //! Absolute time position in seconds + double time_; + //! Delay to next event in ticks + uint64_t delay_; + //! Absolute position in ticks + uint64_t absolute_position_; + //! Delay to next event in seconds + double time_delay_; + //! List of MIDI events in the current row + std::vector events_; + /** + * @brief Sort events in this position + * @param note_states Buffer of currently pressed/released note keys in + * the track + */ + void SortEvents(bool *note_states = nullptr); + }; + + /** + * @brief Tempo change point entry. Used in the MIDI data building function + * only. + */ + struct TempoChangePoint + { + uint64_t absolute_position; + MidiFraction tempo; + }; + + /** + * @brief Song position context + */ + struct Position + { + //! Was track began playing + bool began = false; + //! Reserved + char padding[7] = {0, 0, 0, 0, 0, 0, 0}; + //! Waiting time before next event in seconds + double wait = 0.0; + //! Absolute time position on the track in seconds + double absolute_time_position = 0.0; + //! Track information + struct TrackInfo + { + //! Delay to next event in a track + uint64_t delay = 0; + //! Last handled event type + int32_t lastHandledEvent = 0; + //! Reserved + char padding2[4]; + //! MIDI Events queue position iterator + std::list::iterator pos; + }; + std::vector track; + }; + + //! MIDI Output interface context + const MidiRealTimeInterface *midi_output_interface_; + + /** + * @brief Prepare internal events storage for track data building + * @param track_count Count of tracks + */ + void BuildSMFSetupReset(size_t track_count); + + /** + * @brief Build MIDI track data from the raw track data storage + * @return true if everything successfully processed, or false on any error + */ + bool BuildSMFTrackData(const std::vector> &track_data); + + /** + * @brief Build the time line from off loaded events + * @param tempos Pre-collected list of tempo events + * @param loop_start_ticks Global loop start tick (give zero if no global + * loop presented) + * @param loop_end_ticks Global loop end tick (give zero if no global loop + * presented) + */ + void BuildTimeLine(const std::vector &tempos, uint64_t loop_start_ticks = 0, + uint64_t loop_end_ticks = 0); + + /** + * @brief Parse one event from raw MIDI track stream + * @param [_inout] ptr pointer to pointer to current position on the raw + * data track + * @param [_in] end address to end of raw track data, needed to validate + * position and size + * @param [_inout] status status of the track processing + * @return Parsed MIDI event entry + */ + MidiEvent ParseEvent(const uint8_t **ptr, const uint8_t *end, int &status); + + /** + * @brief Process MIDI events on the current tick moment + * @param is_seek is a seeking process + * @return returns false on reaching end of the song + */ + bool ProcessEvents(bool is_seek = false); + + /** + * @brief Handle one event from the chain + * @param tk MIDI track + * @param evt MIDI event entry + * @param status Recent event type, -1 returned when end of track event was + * handled. + */ + void handleEvent(size_t tk, const MidiEvent &evt, int32_t &status); + + public: + /** + * @brief MIDI marker entry + */ + struct MidiMarkerEntry + { + //! Label + std::string label; + //! Position time in seconds + double position_time; + //! Position time in MIDI ticks + uint64_t position_ticks; + }; + + /** + * @brief The FileFormat enum + */ + enum FileFormat + { + //! MIDI format + kFormatMidi, +#if EDGE_IMF_SUPPORT + //! Id-Software Music File + kFormatIMF, +#endif + //! EA-MUS format + kFormatRSXX, +#if EDGE_XMI_SUPPORT + //! AIL's XMIDI format (act same as MIDI, but with exceptions) + kFormatXMidi +#endif + }; + + /** + * @brief Format of loop points implemented by CC events + */ + enum LoopFormat + { + kLoopDefault, + kLoopRpgMaker = 1, + kLoopEMidi, + kLoopHmi + }; + + private: + //! Music file format type. MIDI is default. + FileFormat midi_format_; + //! SMF format identifier. + unsigned midi_smf_format_; + //! Loop points format + LoopFormat midi_loop_format_; + + //! Current position + Position midi_current_position_; + //! Track begin position + Position midi_track_begin_position_; + //! Loop start point + Position midi_loop_begin_position_; + + //! Is looping enabled or not + bool midi_loop_enabled_; + //! Don't process loop: trigger hooks only if they are set + bool midi_loop_hooks_only_; + + //! Full song length in seconds + double midi_full_song_time_length_; + //! Delay after song playd before rejecting the output stream requests + double midi_post_song_wait_delay_; + + //! Global loop start time + double midi_loop_start_time_; + //! Global loop end time + double midi_loop_end_time_; + + //! Pre-processed track data storage + std::vector> midi_track_data_; + + //! Title of music + std::string midi_music_title_; + //! Copyright notice of music + std::string midi_music_copyright_; + //! List of track titles + std::vector midi_music_track_titles_; + //! List of MIDI markers + std::vector midi_music_markers_; + + //! Time of one tick + MidiFraction midi_individual_tick_delta_; + //! Current tempo + MidiFraction midi_tempo_; + + //! Tempo multiplier factor + double midi_tempo_multiplier_; + //! Is song at end + bool midi_at_end_; + + //! Set the number of loops limit. Lesser than 0 - loop infinite + int midi_loop_count_; + + //! The number of track of multi-track file (for exmaple, XMI) to load + int midi_load_track_number_; + + //! The XMI-specific list of raw songs, converted into SMF format + std::vector> midi_raw_songs_data_; + + /** + * @brief Loop stack entry + */ + struct LoopStackEntry + { + //! is infinite loop + bool infinity = false; + //! Count of loops left to break. <0 - infinite loop + int loops = 0; + //! Start position snapshot to return back + Position start_position; + //! Loop start tick + uint64_t start = 0; + //! Loop end tick + uint64_t end = 0; + }; + + class LoopState + { + public: + //! Loop start has reached + bool caught_start_; + //! Loop end has reached, reset on handling + bool caught_end_; + + //! Loop start has reached + bool caught_stack_start_; + //! Loop next has reached, reset on handling + bool caught_stack_end_; + //! Loop break has reached, reset on handling + bool caught_stack_break_; + //! Skip next stack loop start event handling + bool skip_stack_start_; + + //! Are loop points invalid? + bool invalid_loop_; /*Loop points are invalid (loopStart after loopEnd + or loopStart and loopEnd are on same place)*/ + + //! Is look got temporarily broken because of post-end seek? + bool temporary_broken_; + + //! How much times the loop should start repeat? For example, if you + //! want to loop song twice, set value 1 + int loops_count_; + + //! how many loops left until finish the song + int loops_left_; + + //! Stack of nested loops + std::vector stack_; + //! Current level on the loop stack (<0 - out of loop, 0++ - the index + //! in the loop stack) + int stack_level_; + + /** + * @brief Reset loop state to initial + */ + void Reset() + { + caught_start_ = false; + caught_end_ = false; + caught_stack_start_ = false; + caught_stack_end_ = false; + caught_stack_break_ = false; + skip_stack_start_ = false; + loops_left_ = loops_count_; + } + + void FullReset() + { + loops_count_ = -1; + Reset(); + invalid_loop_ = false; + temporary_broken_ = false; + stack_.clear(); + stack_level_ = -1; + } + + bool IsStackEnd() + { + if (caught_stack_end_ && (stack_level_ >= 0) && (stack_level_ < (int)(stack_.size()))) + { + const LoopStackEntry &e = stack_[(size_t)(stack_level_)]; + if (e.infinity || (!e.infinity && e.loops > 0)) + return true; + } + return false; + } + + void StackUp(int count = 1) + { + stack_level_ += count; + } + + void StackDown(int count = 1) + { + stack_level_ -= count; + } + + LoopStackEntry &GetCurrentStack() + { + if ((stack_level_ >= 0) && (stack_level_ < (int)(stack_.size()))) + return stack_[(size_t)(stack_level_)]; + if (stack_.empty()) + { + LoopStackEntry d; + d.loops = 0; + d.infinity = 0; + d.start = 0; + d.end = 0; + stack_.push_back(d); + } + return stack_[0]; + } + }; + + LoopState midi_loop_; + + //! Whether the nth track has playback disabled + std::vector midi_track_disabled_; + //! Index of solo track, or max for disabled + size_t midi_track_solo_; + //! MIDI channel disable (exception for extra port-prefix-based channels) + bool m_channelDisable[16]; + + /** + * @brief Handler of callback trigger events + * @param userdata Pointer to user data (usually, context of something) + * @param trigger Value of the event which triggered this callback. + * @param track Identifier of the track which triggered this callback. + */ + typedef void (*TriggerHandler)(void *userdata, unsigned trigger, size_t track); + + //! Handler of callback trigger events + TriggerHandler midi_trigger_handler_; + //! User data of callback trigger events + void *midi_trigger_userdata_; + + //! File parsing errors string (adding into midi_error_string_ on aborting + //! of the process) + std::string midi_parsing_errors_string_; + //! Common error string + std::string midi_error_string_; + + class SequencerTime + { + public: + //! Time buffer + double time_rest_; + //! Sample rate + uint32_t sample_rate_; + //! Size of one frame in bytes + uint32_t frame_size_; + //! Minimum possible delay, granuality + double minimum_delay_; + //! Last delay + double delay_; + + void Init() + { + sample_rate_ = 44100; + frame_size_ = 2; + Reset(); + } + + void Reset() + { + time_rest_ = 0.0; + minimum_delay_ = 1.0 / (double)(sample_rate_); + delay_ = 0.0; + } + }; + + SequencerTime midi_time_; + + public: + MidiSequencer(); + virtual ~MidiSequencer(); + + /** + * @brief Sets the RT interface + * @param intrf Pre-Initialized interface structure (pointer will be taken) + */ + void SetInterface(const MidiRealTimeInterface *intrf); + + /** + * @brief Runs ticking in a sync with audio streaming. Use this together + * with onPcmRender hook to easily play MIDI. + * @param stream pointer to the output PCM stream + * @param length length of the buffer in bytes + * @return Count of recorded data in bytes + */ + int PlayStream(uint8_t *stream, size_t length); + + /** + * @brief Returns file format type of currently loaded file + * @return File format type enumeration + */ + FileFormat GetFormat(); + + /** + * @brief Returns the number of tracks + * @return Track count + */ + size_t GetTrackCount() const; + + /** + * @brief Sets whether a track is playing + * @param track Track identifier + * @param enable Whether to enable track playback + * @return true on success, false if there was no such track + */ + bool SetTrackEnabled(size_t track, bool enable); + + /** + * @brief Disable/enable a channel is sounding + * @param channel Channel number from 0 to 15 + * @param enable Enable the channel playback + * @return true on success, false if there was no such channel + */ + bool SetChannelEnabled(size_t channel, bool enable); + + /** + * @brief Enables or disables solo on a track + * @param track Identifier of solo track, or max to disable + */ + void SetSoloTrack(size_t track); + + /** + * @brief Set the song number of a multi-song file (such as XMI) + * @param trackNumber Identifier of the song to load (or -1 to mix all songs + * as one song) + */ + void SetSongNum(int track); + + /** + * @brief Retrive the number of songs in a currently opened file + * @return Number of songs in the file. If 1 or less, means, the file has + * only one song inside. + */ + int GetSongsCount(); + + /** + * @brief Defines a handler for callback trigger events + * @param handler Handler to invoke from the sequencer when triggered, or + * nullptr. + * @param userdata Instance of the library + */ + void SetTriggerHandler(TriggerHandler handler, void *userdata); + + /** + * @brief Get string that describes reason of error + * @return Error string + */ + const std::string &GetErrorString(); + + /** + * @brief Check is loop enabled + * @return true if loop enabled + */ + bool GetLoopEnabled(); + + /** + * @brief Switch loop on/off + * @param enabled Enable loop + */ + void SetLoopEnabled(bool enabled); + + /** + * @brief Get the number of loops set + * @return number of loops or -1 if loop infinite + */ + int GetLoopsCount(); + + /** + * @brief How many times song should loop + * @param loops count or -1 to loop infinite + */ + void SetLoopsCount(int loops); + + /** + * @brief Switch loop hooks-only mode on/off + * @param enabled Don't loop: trigger hooks only without loop + */ + void SetLoopHooksOnly(bool enabled); + + /** + * @brief Get music title + * @return music title string + */ + const std::string &GetMusicTitle(); + + /** + * @brief Get music copyright notice + * @return music copyright notice string + */ + const std::string &GetMusicCopyright(); + + /** + * @brief Get list of track titles + * @return array of track title strings + */ + const std::vector &GetTrackTitles(); + + /** + * @brief Get list of MIDI markers + * @return Array of MIDI marker structures + */ + const std::vector &GetMarkers(); + + /** + * @brief Is position of song at end + * @return true if end of song was reached + */ + bool PositionAtEnd(); + + /** + * @brief Load MIDI file from a memory block + * @param data Pointer to memory block with MIDI data + * @param size Size of source memory block + * @param rate For IMF formats, the proper playback rate in Hz + * @return true if file successfully opened, false on any error + */ + bool LoadMidi(const uint8_t *data, size_t size, uint16_t rate = 0); + + /** + * @brief Load MIDI file by using FileAndMemReader interface + * @param mfr mem_file_c with opened source file + * @param rate For IMF formats, the proper playback rate in Hz + * @return true if file successfully opened, false on any error + */ + bool LoadMidi(epi::MemFile *mfr, uint16_t rate); + + /** + * @brief Periodic tick handler. + * @param s seconds since last call + * @param granularity don't expect intervals smaller than this, in seconds + * @return desired number of seconds until next call + */ + double Tick(double s, double granularity); + + /** + * @brief Change current position to specified time position in seconds + * @param granularity don't expect intervals smaller than this, in seconds + * @param seconds Absolute time position in seconds + * @return desired number of seconds until next call of Tick() + */ + double Seek(double seconds, const double granularity); + + /** + * @brief Gives current time position in seconds + * @return Current time position in seconds + */ + double Tell(); + + /** + * @brief Gives time length of current song in seconds + * @return Time length of current song in seconds + */ + double TimeLength(); + + /** + * @brief Gives loop start time position in seconds + * @return Loop start time position in seconds or -1 if song has no loop + * points + */ + double GetLoopStart(); + + /** + * @brief Gives loop end time position in seconds + * @return Loop end time position in seconds or -1 if song has no loop + * points + */ + double GetLoopEnd(); + + /** + * @brief Return to begin of current song + */ + void Rewind(); + + /** + * @brief Get current tempor multiplier value + * @return + */ + double GetTempoMultiplier(); + + /** + * @brief Set tempo multiplier + * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - + * slower + */ + void SetTempo(double tempo); + + private: +#if EDGE_IMF_SUPPORT + /** + * @brief Load file as Id-software-Music-File (Wolfenstein) + * @param mfr mem_file_c with opened source file + * @param rate For IMF formats, the proper playback rate in Hz + * @return true on successful load + */ + bool ParseIMF(epi::MemFile *mfr, uint16_t rate); +#endif + /** + * @brief Load file as EA MUS + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseRSXX(epi::MemFile *mfr); + + /** + * @brief Load file as GMD/MUS files (ScummVM) + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseGMF(epi::MemFile *mfr); + + /** + * @brief Load file as Standard MIDI file + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseSMF(epi::MemFile *mfr); + + /** + * @brief Load file as RIFF MIDI + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseRMI(epi::MemFile *mfr); +#if EDGE_MUS_SUPPORT + /** + * @brief Load file as DMX MUS file (Doom) + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseMUS(epi::MemFile *mfr); +#endif +#if EDGE_XMI_SUPPORT + /** + * @brief Load file as AIL eXtended MIdi + * @param mfr mem_file_c with opened source file + * @return true on successful load + */ + bool ParseXMI(epi::MemFile *mfr); +#endif +}; + +/** + * @brief Utility function to read Big-Endian integer from raw binary data + * @param buffer Pointer to raw binary buffer + * @param nbytes Count of bytes to parse integer + * @return Extracted unsigned integer + */ +static inline uint64_t ReadIntBigEndian(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const uint8_t *data = (const uint8_t *)(buffer); + + for (size_t n = 0; n < nbytes; ++n) + result = (result << 8) + data[n]; + + return result; +} + +/** + * @brief Utility function to read Little-Endian integer from raw binary data + * @param buffer Pointer to raw binary buffer + * @param nbytes Count of bytes to parse integer + * @return Extracted unsigned integer + */ +static inline uint64_t ReadIntLittleEndian(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const uint8_t *data = (const uint8_t *)(buffer); + + for (size_t n = 0; n < nbytes; ++n) + result = result + (uint64_t)(data[n] << (n * 8)); + + return result; +} + +/** + * @brief Secure Standard MIDI Variable-Length numeric value parser with + * anti-out-of-range protection + * @param [_inout] ptr Pointer to memory block that contains begin of + * variable-length value, will be iterated forward + * @param [_in end Pointer to end of memory block where variable-length value is + * stored (after end of track) + * @param [_out] ok Reference to boolean which takes result of variable-length + * value parsing + * @return Unsigned integer that conains parsed variable-length value + */ +static inline uint64_t ReadVariableLengthValue(const uint8_t **ptr, const uint8_t *end, bool &ok) +{ + uint64_t result = 0; + ok = false; + + for (;;) + { + if (*ptr >= end) + return 2; + unsigned char byte = *((*ptr)++); + result = (result << 7) + (byte & 0x7F); + if (!(byte & 0x80)) + break; + } + + ok = true; + return result; +} + +MidiSequencer::MidiTrackRow::MidiTrackRow() : time_(0.0), delay_(0), absolute_position_(0), time_delay_(0.0) +{ +} + +void MidiSequencer::MidiTrackRow::Clear() +{ + time_ = 0.0; + delay_ = 0; + absolute_position_ = 0; + time_delay_ = 0.0; + events_.clear(); +} + +void MidiSequencer::MidiTrackRow::SortEvents(bool *note_states) +{ + typedef std::vector EvtArr; + EvtArr sysEx; + EvtArr metas; + EvtArr noteOffs; + EvtArr controllers; + EvtArr anyOther; + + for (size_t i = 0; i < events_.size(); i++) + { + if (events_[i].type == MidiEvent::kNoteOff) + { + if (noteOffs.capacity() == 0) + noteOffs.reserve(events_.size()); + noteOffs.push_back(events_[i]); + } + else if (events_[i].type == MidiEvent::kSysex || events_[i].type == MidiEvent::kSysex2) + { + if (sysEx.capacity() == 0) + sysEx.reserve(events_.size()); + sysEx.push_back(events_[i]); + } + else if ((events_[i].type == MidiEvent::kControlChange) || (events_[i].type == MidiEvent::kPatchChange) || + (events_[i].type == MidiEvent::kPitchWheel) || (events_[i].type == MidiEvent::kChannelAftertouch)) + { + if (controllers.capacity() == 0) + controllers.reserve(events_.size()); + controllers.push_back(events_[i]); + } + else if ((events_[i].type == MidiEvent::kSpecial) && + ((events_[i].sub_type == MidiEvent::kMarker) || (events_[i].sub_type == MidiEvent::kDeviceSwitch) || + (events_[i].sub_type == MidiEvent::kSongBeginHook) || + (events_[i].sub_type == MidiEvent::kLoopStart) || (events_[i].sub_type == MidiEvent::kLoopEnd) || + (events_[i].sub_type == MidiEvent::kLoopStackBegin) || + (events_[i].sub_type == MidiEvent::kLoopStackEnd) || + (events_[i].sub_type == MidiEvent::kLoopStackBreak))) + { + if (metas.capacity() == 0) + metas.reserve(events_.size()); + metas.push_back(events_[i]); + } + else + { + if (anyOther.capacity() == 0) + anyOther.reserve(events_.size()); + anyOther.push_back(events_[i]); + } + } + + /* + * If Note-Off and it's Note-On is on the same row - move this damned note + * off down! + */ + if (note_states) + { + std::set markAsOn; + for (size_t i = 0; i < anyOther.size(); i++) + { + const MidiEvent e = anyOther[i]; + if (e.type == MidiEvent::kNoteOn) + { + const size_t note_i = (size_t)(e.channel * 255) + (e.data[0] & 0x7F); + // Check, was previously note is on or off + bool wasOn = note_states[note_i]; + markAsOn.insert(note_i); + // Detect zero-length notes are following previously pressed + // note + int noteOffsOnSameNote = 0; + for (EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end();) + { + // If note was off, and note-off on same row with note-on - + // move it down! + if (((*j).channel == e.channel) && ((*j).data[0] == e.data[0])) + { + // If note is already off OR more than one note-off on + // same row and same note + if (!wasOn || (noteOffsOnSameNote != 0)) + { + anyOther.push_back(*j); + j = noteOffs.erase(j); + markAsOn.erase(note_i); + continue; + } + else + { + // When same row has many note-offs on same row + // that means a zero-length note follows previous + // note it must be shuted down + noteOffsOnSameNote++; + } + } + j++; + } + } + } + + // Mark other notes as released + for (EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end(); j++) + { + size_t note_i = (size_t)(j->channel * 255) + (j->data[0] & 0x7F); + note_states[note_i] = false; + } + + for (std::set::iterator j = markAsOn.begin(); j != markAsOn.end(); j++) + note_states[*j] = true; + } + /***********************************************************************************/ + + events_.clear(); + if (!sysEx.empty()) + events_.insert(events_.end(), sysEx.begin(), sysEx.end()); + if (!noteOffs.empty()) + events_.insert(events_.end(), noteOffs.begin(), noteOffs.end()); + if (!metas.empty()) + events_.insert(events_.end(), metas.begin(), metas.end()); + if (!controllers.empty()) + events_.insert(events_.end(), controllers.begin(), controllers.end()); + if (!anyOther.empty()) + events_.insert(events_.end(), anyOther.begin(), anyOther.end()); +} + +MidiSequencer::MidiSequencer() + : midi_output_interface_(nullptr), midi_format_(kFormatMidi), midi_smf_format_(0), midi_loop_format_(kLoopDefault), + midi_loop_enabled_(false), midi_loop_hooks_only_(false), midi_full_song_time_length_(0.0), + midi_post_song_wait_delay_(1.0), midi_loop_start_time_(-1.0), midi_loop_end_time_(-1.0), + midi_tempo_multiplier_(1.0), midi_at_end_(false), midi_loop_count_(-1), midi_load_track_number_(0), + midi_track_solo_(~(size_t)(0)), midi_trigger_handler_(nullptr), midi_trigger_userdata_(nullptr) +{ + midi_loop_.Reset(); + midi_loop_.invalid_loop_ = false; + midi_time_.Init(); +} + +MidiSequencer::~MidiSequencer() +{ +} + +void MidiSequencer::SetInterface(const MidiRealTimeInterface *intrf) +{ + // Interface must NOT be nullptr + EPI_ASSERT(intrf); + + // Note ON hook is REQUIRED + EPI_ASSERT(intrf->rt_noteOn); + // Note OFF hook is REQUIRED + EPI_ASSERT(intrf->rt_noteOff || intrf->rt_noteOffVel); + // Note Aftertouch hook is REQUIRED + EPI_ASSERT(intrf->rt_noteAfterTouch); + // Channel Aftertouch hook is REQUIRED + EPI_ASSERT(intrf->rt_channelAfterTouch); + // Controller change hook is REQUIRED + EPI_ASSERT(intrf->rt_controllerChange); + // Patch change hook is REQUIRED + EPI_ASSERT(intrf->rt_patchChange); + // Pitch bend hook is REQUIRED + EPI_ASSERT(intrf->rt_pitchBend); + // System Exclusive hook is REQUIRED + EPI_ASSERT(intrf->rt_systemExclusive); + + if (intrf->pcmSampleRate != 0 && intrf->pcmFrameSize != 0) + { + midi_time_.sample_rate_ = intrf->pcmSampleRate; + midi_time_.frame_size_ = intrf->pcmFrameSize; + midi_time_.Reset(); + } + + midi_output_interface_ = intrf; +} + +int MidiSequencer::PlayStream(uint8_t *stream, size_t length) +{ + int count = 0; + size_t samples = (size_t)(length / (size_t)(midi_time_.frame_size_)); + size_t left = samples; + size_t periodSize = 0; + uint8_t *stream_pos = stream; + + EPI_ASSERT(midi_output_interface_->onPcmRender); + + while (left > 0) + { + const double leftDelay = left / double(midi_time_.sample_rate_); + const double maxDelay = midi_time_.time_rest_ < leftDelay ? midi_time_.time_rest_ : leftDelay; + if ((PositionAtEnd()) && (midi_time_.delay_ <= 0.0)) + break; // Stop to fetch samples at reaching the song end with + // disabled loop + + midi_time_.time_rest_ -= maxDelay; + periodSize = (size_t)((double)(midi_time_.sample_rate_) * maxDelay); + + if (stream) + { + size_t generateSize = periodSize > left ? (size_t)(left) : (size_t)(periodSize); + midi_output_interface_->onPcmRender(midi_output_interface_->onPcmRender_userdata, stream_pos, + generateSize * midi_time_.frame_size_); + stream_pos += generateSize * midi_time_.frame_size_; + count += generateSize; + left -= generateSize; + EPI_ASSERT(left <= samples); + } + + if (midi_time_.time_rest_ <= 0.0) + { + midi_time_.delay_ = Tick(midi_time_.delay_, midi_time_.minimum_delay_); + midi_time_.time_rest_ += midi_time_.delay_; + } + } + + return count * (int)(midi_time_.frame_size_); +} + +MidiSequencer::FileFormat MidiSequencer::GetFormat() +{ + return midi_format_; +} + +size_t MidiSequencer::GetTrackCount() const +{ + return midi_track_data_.size(); +} + +bool MidiSequencer::SetTrackEnabled(size_t track, bool enable) +{ + size_t track_count = midi_track_data_.size(); + if (track >= track_count) + return false; + midi_track_disabled_[track] = !enable; + return true; +} + +bool MidiSequencer::SetChannelEnabled(size_t channel, bool enable) +{ + if (channel >= 16) + return false; + + if (!enable && m_channelDisable[channel] != !enable) + { + uint8_t ch = (uint8_t)(channel); + + // Releae all pedals + midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, ch, 64, 0); + midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, ch, 66, 0); + + // Release all notes on the channel now + for (int i = 0; i < 127; ++i) + { + if (midi_output_interface_->rt_noteOff) + midi_output_interface_->rt_noteOff(midi_output_interface_->rtUserData, ch, i); + if (midi_output_interface_->rt_noteOffVel) + midi_output_interface_->rt_noteOffVel(midi_output_interface_->rtUserData, ch, i, 0); + } + } + + m_channelDisable[channel] = !enable; + return true; +} + +void MidiSequencer::SetSoloTrack(size_t track) +{ + midi_track_solo_ = track; +} + +void MidiSequencer::SetSongNum(int track) +{ + midi_load_track_number_ = track; +#if EDGE_XMI_SUPPORT + if (!midi_raw_songs_data_.empty() && midi_format_ == kFormatXMidi) // Reload the song + { + if (midi_load_track_number_ >= (int)midi_raw_songs_data_.size()) + midi_load_track_number_ = midi_raw_songs_data_.size() - 1; + + if (midi_output_interface_ && midi_output_interface_->rt_controllerChange) + { + for (int i = 0; i < 15; i++) + midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, i, 123, 0); + } + + midi_at_end_ = false; + midi_loop_.FullReset(); + midi_loop_.caught_start_ = true; + + midi_smf_format_ = 0; + + epi::MemFile *mfr = new epi::MemFile(midi_raw_songs_data_[midi_load_track_number_].data(), + midi_raw_songs_data_[midi_load_track_number_].size()); + ParseSMF(mfr); + + midi_format_ = kFormatXMidi; + } +#endif +} + +int MidiSequencer::GetSongsCount() +{ + return (int)midi_raw_songs_data_.size(); +} + +void MidiSequencer::SetTriggerHandler(TriggerHandler handler, void *userdata) +{ + midi_trigger_handler_ = handler; + midi_trigger_userdata_ = userdata; +} + +const std::string &MidiSequencer::GetErrorString() +{ + return midi_error_string_; +} + +bool MidiSequencer::GetLoopEnabled() +{ + return midi_loop_enabled_; +} + +void MidiSequencer::SetLoopEnabled(bool enabled) +{ + midi_loop_enabled_ = enabled; +} + +int MidiSequencer::GetLoopsCount() +{ + return midi_loop_count_ >= 0 ? (midi_loop_count_ + 1) : midi_loop_count_; +} + +void MidiSequencer::SetLoopsCount(int loops) +{ + if (loops >= 1) + loops -= 1; // Internally, loops count has the 0 base + midi_loop_count_ = loops; +} + +void MidiSequencer::SetLoopHooksOnly(bool enabled) +{ + midi_loop_hooks_only_ = enabled; +} + +const std::string &MidiSequencer::GetMusicTitle() +{ + return midi_music_title_; +} + +const std::string &MidiSequencer::GetMusicCopyright() +{ + return midi_music_copyright_; +} + +const std::vector &MidiSequencer::GetTrackTitles() +{ + return midi_music_track_titles_; +} + +const std::vector &MidiSequencer::GetMarkers() +{ + return midi_music_markers_; +} + +bool MidiSequencer::PositionAtEnd() +{ + return midi_at_end_; +} + +double MidiSequencer::GetTempoMultiplier() +{ + return midi_tempo_multiplier_; +} + +void MidiSequencer::BuildSMFSetupReset(size_t track_count) +{ + midi_full_song_time_length_ = 0.0; + midi_loop_start_time_ = -1.0; + midi_loop_end_time_ = -1.0; + midi_loop_format_ = kLoopDefault; + midi_track_disabled_.clear(); + EPI_CLEAR_MEMORY(m_channelDisable, bool, 16); + midi_track_solo_ = ~(size_t)0; + midi_music_title_.clear(); + midi_music_copyright_.clear(); + midi_music_track_titles_.clear(); + midi_music_markers_.clear(); + midi_track_data_.clear(); + midi_track_data_.resize(track_count, std::list()); + midi_track_disabled_.resize(track_count); + + midi_loop_.Reset(); + midi_loop_.invalid_loop_ = false; + midi_time_.Reset(); + + midi_current_position_.began = false; + midi_current_position_.absolute_time_position = 0.0; + midi_current_position_.wait = 0.0; + midi_current_position_.track.clear(); + midi_current_position_.track.resize(track_count); +} + +bool MidiSequencer::BuildSMFTrackData(const std::vector> &track_data) +{ + const size_t track_count = track_data.size(); + BuildSMFSetupReset(track_count); + + bool gotGlobalLoopStart = false, gotGlobalLoopEnd = false, gotStackLoopStart = false, gotLoopEventInThisRow = false; + + //! Tick position of loop start tag + uint64_t loop_start_ticks = 0; + //! Tick position of loop end tag + uint64_t loop_end_ticks = 0; + //! Full length of song in ticks + uint64_t ticksSongLength = 0; + //! Cache for error message strign + char error[150]; + + //! Caches note on/off states. + bool note_states[16 * 255]; + /* This is required to carefully detect zero-length notes * + * and avoid a move of "note-off" event over "note-on" while sort. * + * Otherwise, after sort those notes will play infinite sound */ + + //! Tempo change events list + std::vector temposList; + + /* + * TODO: Make this be safer for memory in case of broken input data + * which may cause going away of available track data (and then give a + * crash!) + * + * POST: Check this more carefully for possible vulnuabilities are can crash + * this + */ + for (size_t tk = 0; tk < track_count; ++tk) + { + uint64_t abs_position = 0; + int status = 0; + MidiEvent event; + bool ok = false; + const uint8_t *end = track_data[tk].data() + track_data[tk].size(); + const uint8_t *trackPtr = track_data[tk].data(); + EPI_CLEAR_MEMORY(note_states, bool, 4080); + + // Time delay that follows the first event in the track + { + MidiTrackRow evtPos; + if (midi_format_ == kFormatRSXX) + ok = true; + else + evtPos.delay_ = ReadVariableLengthValue(&trackPtr, end, ok); + if (!ok) + { + int len = stbsp_snprintf(error, 150, + "buildTrackData: Can't read variable-length " + "value at begin of track %d.\n", + (int)tk); + if ((len > 0) && (len < 150)) + midi_parsing_errors_string_ += std::string(error, (size_t)len); + return false; + } + + // HACK: Begin every track with "Reset all controllers" event to + // avoid controllers state break came from end of song + if (tk == 0) + { + MidiEvent resetEvent; + resetEvent.type = MidiEvent::kSpecial; + resetEvent.sub_type = MidiEvent::kSongBeginHook; + evtPos.events_.push_back(resetEvent); + } + + evtPos.absolute_position_ = abs_position; + abs_position += evtPos.delay_; + midi_track_data_[tk].push_back(evtPos); + } + + MidiTrackRow evtPos; + do + { + event = ParseEvent(&trackPtr, end, status); + if (!event.is_valid) + { + int len = stbsp_snprintf(error, 150, "buildTrackData: Fail to parse event in the track %d.\n", (int)tk); + if ((len > 0) && (len < 150)) + midi_parsing_errors_string_ += std::string(error, (size_t)len); + return false; + } + + evtPos.events_.push_back(event); + if (event.type == MidiEvent::kSpecial) + { + if (event.sub_type == MidiEvent::kTempoChange) + { + event.absolute_tick_position = abs_position; + temposList.push_back(event); + } + else if (!midi_loop_.invalid_loop_ && (event.sub_type == MidiEvent::kLoopStart)) + { + /* + * loopStart is invalid when: + * - starts together with loopEnd + * - appears more than one time in same MIDI file + */ + if (gotGlobalLoopStart || gotLoopEventInThisRow) + midi_loop_.invalid_loop_ = true; + else + { + gotGlobalLoopStart = true; + loop_start_ticks = abs_position; + } + // In this row we got loop event, register this! + gotLoopEventInThisRow = true; + } + else if (!midi_loop_.invalid_loop_ && (event.sub_type == MidiEvent::kLoopEnd)) + { + /* + * loopEnd is invalid when: + * - starts before loopStart + * - starts together with loopStart + * - appars more than one time in same MIDI file + */ + if (gotGlobalLoopEnd || gotLoopEventInThisRow) + { + midi_loop_.invalid_loop_ = true; + if (midi_output_interface_->onDebugMessage) + { + midi_output_interface_->onDebugMessage( + midi_output_interface_->onDebugMessage_userdata, "== Invalid loop detected! %s %s ==", + (gotGlobalLoopEnd ? "[Caught more than 1 loopEnd!]" : ""), + (gotLoopEventInThisRow ? "[loopEnd in same row as loopStart!]" : "")); + } + } + else + { + gotGlobalLoopEnd = true; + loop_end_ticks = abs_position; + } + // In this row we got loop event, register this! + gotLoopEventInThisRow = true; + } + else if (!midi_loop_.invalid_loop_ && (event.sub_type == MidiEvent::kLoopStackBegin)) + { + if (!gotStackLoopStart) + { + if (!gotGlobalLoopStart) + loop_start_ticks = abs_position; + gotStackLoopStart = true; + } + + midi_loop_.StackUp(); + if (midi_loop_.stack_level_ >= (int)(midi_loop_.stack_.size())) + { + LoopStackEntry e; + e.loops = event.data[0]; + e.infinity = (event.data[0] == 0); + e.start = abs_position; + e.end = abs_position; + midi_loop_.stack_.push_back(e); + } + } + else if (!midi_loop_.invalid_loop_ && ((event.sub_type == MidiEvent::kLoopStackEnd) || + (event.sub_type == MidiEvent::kLoopStackBreak))) + { + if (midi_loop_.stack_level_ <= -1) + { + midi_loop_.invalid_loop_ = true; // Caught loop end without of loop start! + if (midi_output_interface_->onDebugMessage) + { + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "== Invalid loop detected! [Caught loop end " + "without of loop start] =="); + } + } + else + { + if (loop_end_ticks < abs_position) + loop_end_ticks = abs_position; + midi_loop_.GetCurrentStack().end = abs_position; + midi_loop_.StackDown(); + } + } + } + + if (event.sub_type != MidiEvent::kEndTrack) // Don't try to read delta after + // EndOfTrack event! + { + evtPos.delay_ = ReadVariableLengthValue(&trackPtr, end, ok); + if (!ok) + { + /* End of track has been reached! However, there is no EOT + * event presented */ + event.type = MidiEvent::kSpecial; + event.sub_type = MidiEvent::kEndTrack; + } + } + + if ((evtPos.delay_ > 0) || (event.sub_type == MidiEvent::kEndTrack)) + { + evtPos.absolute_position_ = abs_position; + abs_position += evtPos.delay_; + evtPos.SortEvents(note_states); + midi_track_data_[tk].push_back(evtPos); + evtPos.Clear(); + gotLoopEventInThisRow = false; + } + } while ((trackPtr <= end) && (event.sub_type != MidiEvent::kEndTrack)); + + if (ticksSongLength < abs_position) + ticksSongLength = abs_position; + // Set the chain of events begin + if (midi_track_data_[tk].size() > 0) + midi_current_position_.track[tk].pos = midi_track_data_[tk].begin(); + } + + if (gotGlobalLoopStart && !gotGlobalLoopEnd) + { + gotGlobalLoopEnd = true; + loop_end_ticks = ticksSongLength; + } + + // loopStart must be located before loopEnd! + if (loop_start_ticks >= loop_end_ticks) + { + midi_loop_.invalid_loop_ = true; + if (midi_output_interface_->onDebugMessage && (gotGlobalLoopStart || gotGlobalLoopEnd)) + { + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "== Invalid loop detected! [loopEnd is " + "going before loopStart] =="); + } + } + + BuildTimeLine(temposList, loop_start_ticks, loop_end_ticks); + + return true; +} + +void MidiSequencer::BuildTimeLine(const std::vector &tempos, uint64_t loop_start_ticks, + uint64_t loop_end_ticks) +{ + const size_t track_count = midi_track_data_.size(); + /********************************************************************************/ + // Calculate time basing on collected tempo events + /********************************************************************************/ + for (size_t tk = 0; tk < track_count; ++tk) + { + MidiFraction currentTempo = midi_tempo_; + double time = 0.0; + size_t tempo_change_index = 0; + std::list &track = midi_track_data_[tk]; + if (track.empty()) + continue; // Empty track is useless! + + MidiTrackRow *posPrev = &(*(track.begin())); // First element + for (std::list::iterator it = track.begin(); it != track.end(); it++) + { + MidiTrackRow &pos = *it; + if ((posPrev != &pos) && // Skip first event + (!tempos.empty()) && // Only when in-track tempo events are + // available + (tempo_change_index < tempos.size())) + { + // If tempo event is going between of current and previous event + if (tempos[tempo_change_index].absolute_tick_position <= pos.absolute_position_) + { + // Stop points: begin point and tempo change points are + // before end point + std::vector points; + MidiFraction t; + TempoChangePoint firstPoint = {posPrev->absolute_position_, currentTempo}; + points.push_back(firstPoint); + + // Collect tempo change points between previous and current + // events + do + { + TempoChangePoint tempoMarker; + const MidiEvent &tempoPoint = tempos[tempo_change_index]; + tempoMarker.absolute_position = tempoPoint.absolute_tick_position; + tempoMarker.tempo = + midi_individual_tick_delta_ * + MidiFraction(ReadIntBigEndian(tempoPoint.data.data(), tempoPoint.data.size())); + points.push_back(tempoMarker); + tempo_change_index++; + } while ((tempo_change_index < tempos.size()) && + (tempos[tempo_change_index].absolute_tick_position <= pos.absolute_position_)); + + // Re-calculate time delay of previous event + time -= posPrev->time_delay_; + posPrev->time_delay_ = 0.0; + + for (size_t i = 0, j = 1; j < points.size(); i++, j++) + { + /* If one or more tempo events are appears between of + * two events, calculate delays between each tempo + * point, begin and end */ + uint64_t midDelay = 0; + // Delay between points + midDelay = points[j].absolute_position - points[i].absolute_position; + // Time delay between points + t = midDelay * currentTempo; + posPrev->time_delay_ += t.Value(); + + // Apply next tempo + currentTempo = points[j].tempo; + } + // Then calculate time between last tempo change point and + // end point + TempoChangePoint tailTempo = points.back(); + uint64_t postDelay = pos.absolute_position_ - tailTempo.absolute_position; + t = postDelay * currentTempo; + posPrev->time_delay_ += t.Value(); + + // Store Common time delay + posPrev->time_ = time; + time += posPrev->time_delay_; + } + } + + MidiFraction t = pos.delay_ * currentTempo; + pos.time_delay_ = t.Value(); + pos.time_ = time; + time += pos.time_delay_; + + // Capture markers after time value calculation + for (size_t i = 0; i < pos.events_.size(); i++) + { + MidiEvent &e = pos.events_[i]; + if ((e.type == MidiEvent::kSpecial) && (e.sub_type == MidiEvent::kMarker)) + { + MidiMarkerEntry marker; + marker.label = std::string((char *)e.data.data(), e.data.size()); + marker.position_ticks = pos.absolute_position_; + marker.position_time = pos.time_; + midi_music_markers_.push_back(marker); + } + } + + // Capture loop points time positions + if (!midi_loop_.invalid_loop_) + { + // Set loop points times + if (loop_start_ticks == pos.absolute_position_) + midi_loop_start_time_ = pos.time_; + else if (loop_end_ticks == pos.absolute_position_) + midi_loop_end_time_ = pos.time_; + } + posPrev = &pos; + } + + if (time > midi_full_song_time_length_) + midi_full_song_time_length_ = time; + } + + midi_full_song_time_length_ += midi_post_song_wait_delay_; + // Set begin of the music + midi_track_begin_position_ = midi_current_position_; + // Initial loop position will begin at begin of track until passing of the + // loop point + midi_loop_begin_position_ = midi_current_position_; + // Set lowest level of the loop stack + midi_loop_.stack_level_ = -1; + + // Set the count of loops + midi_loop_.loops_count_ = midi_loop_count_; + midi_loop_.loops_left_ = midi_loop_count_; + + /********************************************************************************/ + // Find and set proper loop points + /********************************************************************************/ + if (!midi_loop_.invalid_loop_ && !midi_current_position_.track.empty()) + { + unsigned caughLoopStart = 0; + bool scanDone = false; + const size_t ctrack_count = midi_current_position_.track.size(); + Position rowPosition(midi_current_position_); + + while (!scanDone) + { + const Position rowBeginPosition(rowPosition); + + for (size_t tk = 0; tk < ctrack_count; ++tk) + { + Position::TrackInfo &track = rowPosition.track[tk]; + if ((track.lastHandledEvent >= 0) && (track.delay <= 0)) + { + // Check is an end of track has been reached + if (track.pos == midi_track_data_[tk].end()) + { + track.lastHandledEvent = -1; + continue; + } + + for (size_t i = 0; i < track.pos->events_.size(); i++) + { + const MidiEvent &evt = track.pos->events_[i]; + if (evt.type == MidiEvent::kSpecial && evt.sub_type == MidiEvent::kLoopStart) + { + caughLoopStart++; + scanDone = true; + break; + } + } + + if (track.lastHandledEvent >= 0) + { + track.delay += track.pos->delay_; + track.pos++; + } + } + } + + // Find a shortest delay from all track + uint64_t shortestDelay = 0; + bool shortestDelayNotFound = true; + + for (size_t tk = 0; tk < ctrack_count; ++tk) + { + Position::TrackInfo &track = rowPosition.track[tk]; + if ((track.lastHandledEvent >= 0) && (shortestDelayNotFound || track.delay < shortestDelay)) + { + shortestDelay = track.delay; + shortestDelayNotFound = false; + } + } + + // Schedule the next playevent to be processed after that delay + for (size_t tk = 0; tk < ctrack_count; ++tk) + rowPosition.track[tk].delay -= shortestDelay; + + if (caughLoopStart > 0) + { + midi_loop_begin_position_ = rowBeginPosition; + midi_loop_begin_position_.absolute_time_position = midi_loop_start_time_; + scanDone = true; + } + + if (shortestDelayNotFound) + break; + } + } +} + +bool MidiSequencer::ProcessEvents(bool is_seek) +{ + if (midi_current_position_.track.size() == 0) + midi_at_end_ = true; // No MIDI track data to play + if (midi_at_end_) + return false; // No more events in the queue + + midi_loop_.caught_end_ = false; + const size_t track_count = midi_current_position_.track.size(); + const Position rowBeginPosition(midi_current_position_); + bool doLoopJump = false; + unsigned caughLoopStart = 0; + unsigned caughLoopStackStart = 0; + unsigned caughLoopStackEnds = 0; + double caughLoopStackEndsTime = 0.0; + unsigned caughLoopStackBreaks = 0; + + for (size_t tk = 0; tk < track_count; ++tk) + { + Position::TrackInfo &track = midi_current_position_.track[tk]; + if ((track.lastHandledEvent >= 0) && (track.delay <= 0)) + { + // Check is an end of track has been reached + if (track.pos == midi_track_data_[tk].end()) + { + track.lastHandledEvent = -1; + break; + } + + // Handle event + for (size_t i = 0; i < track.pos->events_.size(); i++) + { + const MidiEvent &evt = track.pos->events_[i]; + + if (is_seek && (evt.type == MidiEvent::kNoteOn)) + continue; + handleEvent(tk, evt, track.lastHandledEvent); + + if (midi_loop_.caught_start_) + { + if (midi_output_interface_->onloopStart) // Loop Start hook + midi_output_interface_->onloopStart(midi_output_interface_->onloopStart_userdata); + + caughLoopStart++; + midi_loop_.caught_start_ = false; + } + + if (midi_loop_.caught_stack_start_) + { + if (midi_output_interface_->onloopStart && + (midi_loop_start_time_ >= track.pos->time_)) // Loop Start hook + midi_output_interface_->onloopStart(midi_output_interface_->onloopStart_userdata); + + caughLoopStackStart++; + midi_loop_.caught_stack_start_ = false; + } + + if (midi_loop_.caught_stack_break_) + { + caughLoopStackBreaks++; + midi_loop_.caught_stack_break_ = false; + } + + if (midi_loop_.caught_end_ || midi_loop_.IsStackEnd()) + { + if (midi_loop_.caught_stack_end_) + { + midi_loop_.caught_stack_end_ = false; + caughLoopStackEnds++; + caughLoopStackEndsTime = track.pos->time_; + } + doLoopJump = true; + break; // Stop event handling on catching loopEnd event! + } + } + + // Read next event time (unless the track just ended) + if (track.lastHandledEvent >= 0) + { + track.delay += track.pos->delay_; + track.pos++; + } + + if (doLoopJump) + break; + } + } + + // Find a shortest delay from all track + uint64_t shortestDelay = 0; + bool shortestDelayNotFound = true; + + for (size_t tk = 0; tk < track_count; ++tk) + { + Position::TrackInfo &track = midi_current_position_.track[tk]; + if ((track.lastHandledEvent >= 0) && (shortestDelayNotFound || track.delay < shortestDelay)) + { + shortestDelay = track.delay; + shortestDelayNotFound = false; + } + } + + // Schedule the next playevent to be processed after that delay + for (size_t tk = 0; tk < track_count; ++tk) + midi_current_position_.track[tk].delay -= shortestDelay; + + MidiFraction t = shortestDelay * midi_tempo_; + + midi_current_position_.wait += t.Value(); + + if (caughLoopStart > 0 && midi_loop_begin_position_.absolute_time_position <= 0.0) + midi_loop_begin_position_ = rowBeginPosition; + + if (caughLoopStackStart > 0) + { + while (caughLoopStackStart > 0) + { + midi_loop_.StackUp(); + LoopStackEntry &s = midi_loop_.GetCurrentStack(); + s.start_position = rowBeginPosition; + caughLoopStackStart--; + } + return true; + } + + if (caughLoopStackBreaks > 0) + { + while (caughLoopStackBreaks > 0) + { + LoopStackEntry &s = midi_loop_.GetCurrentStack(); + s.loops = 0; + s.infinity = false; + // Quit the loop + midi_loop_.StackDown(); + caughLoopStackBreaks--; + } + } + + if (caughLoopStackEnds > 0) + { + while (caughLoopStackEnds > 0) + { + LoopStackEntry &s = midi_loop_.GetCurrentStack(); + if (s.infinity) + { + if (midi_output_interface_->onloopEnd && + (midi_loop_end_time_ >= caughLoopStackEndsTime)) // Loop End hook + { + midi_output_interface_->onloopEnd(midi_output_interface_->onloopEnd_userdata); + if (midi_loop_hooks_only_) // Stop song on reaching loop + // end + { + midi_at_end_ = true; // Don't handle events anymore + midi_current_position_.wait += midi_post_song_wait_delay_; // One second delay + // until stop playing + } + } + + midi_current_position_ = s.start_position; + midi_loop_.skip_stack_start_ = true; + + for (uint8_t i = 0; i < 16; i++) + midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, i, 123, 0); + + return true; + } + else if (s.loops >= 0) + { + s.loops--; + if (s.loops > 0) + { + midi_current_position_ = s.start_position; + midi_loop_.skip_stack_start_ = true; + + for (uint8_t i = 0; i < 16; i++) + midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, i, 123, 0); + + return true; + } + else + { + // Quit the loop + midi_loop_.StackDown(); + } + } + else + { + // Quit the loop + midi_loop_.StackDown(); + } + caughLoopStackEnds--; + } + + return true; + } + + if (shortestDelayNotFound || midi_loop_.caught_end_) + { + if (midi_output_interface_->onloopEnd) // Loop End hook + midi_output_interface_->onloopEnd(midi_output_interface_->onloopEnd_userdata); + + for (uint8_t i = 0; i < 16; i++) + midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, i, 123, 0); + + // Loop if song end or loop end point has reached + midi_loop_.caught_end_ = false; + shortestDelay = 0; + + if (!midi_loop_enabled_ || + (shortestDelayNotFound && midi_loop_.loops_count_ >= 0 && midi_loop_.loops_left_ < 1) || + midi_loop_hooks_only_) + { + midi_at_end_ = true; // Don't handle events anymore + midi_current_position_.wait += midi_post_song_wait_delay_; // One second delay until stop + // playing + return true; // We have caugh end here! + } + + if (midi_loop_.temporary_broken_) + { + midi_current_position_ = midi_track_begin_position_; + midi_loop_.temporary_broken_ = false; + } + else if (midi_loop_.loops_count_ < 0 || midi_loop_.loops_left_ >= 1) + { + midi_current_position_ = midi_loop_begin_position_; + if (midi_loop_.loops_count_ >= 1) + midi_loop_.loops_left_--; + } + } + + return true; // Has events in queue +} + +MidiSequencer::MidiEvent MidiSequencer::ParseEvent(const uint8_t **pptr, const uint8_t *end, int &status) +{ + const uint8_t *&ptr = *pptr; + MidiSequencer::MidiEvent evt; + + if (ptr + 1 > end) + { + // When track doesn't ends on the middle of event data, it's must be + // fine + evt.type = MidiEvent::kSpecial; + evt.sub_type = MidiEvent::kEndTrack; + return evt; + } + + unsigned char byte = *(ptr++); + bool ok = false; + + if (byte == MidiEvent::kSysex || byte == MidiEvent::kSysex2) // Ignore SysEx + { + uint64_t length = ReadVariableLengthValue(pptr, end, ok); + if (!ok || (ptr + length > end)) + { + midi_parsing_errors_string_ += "ParseEvent: Can't read SysEx event - Unexpected end of track " + "data.\n"; + evt.is_valid = 0; + return evt; + } + evt.type = MidiEvent::kSysex; + evt.data.clear(); + evt.data.push_back(byte); + std::copy(ptr, ptr + length, std::back_inserter(evt.data)); + ptr += (size_t)length; + return evt; + } + + if (byte == MidiEvent::kSpecial) + { + // Special event FF + uint8_t evtype = *(ptr++); + uint64_t length = ReadVariableLengthValue(pptr, end, ok); + if (!ok || (ptr + length > end)) + { + midi_parsing_errors_string_ += "ParseEvent: Can't read Special event - Unexpected end of " + "track data.\n"; + evt.is_valid = 0; + return evt; + } + std::string data(length ? (const char *)ptr : nullptr, (size_t)length); + ptr += (size_t)length; + + evt.type = byte; + evt.sub_type = evtype; + evt.data.insert(evt.data.begin(), data.begin(), data.end()); + + /* TODO: Store those meta-strings separately and give ability to read + * them by external functions (to display song title and copyright in + * the player) */ + if (evt.sub_type == MidiEvent::kCopyright) + { + if (midi_music_copyright_.empty()) + { + midi_music_copyright_ = std::string((const char *)evt.data.data(), evt.data.size()); + midi_music_copyright_.push_back('\0'); /* ending fix for UTF16 strings */ + if (midi_output_interface_->onDebugMessage) + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Music copyright: %s", midi_music_copyright_.c_str()); + } + else if (midi_output_interface_->onDebugMessage) + { + std::string str((const char *)evt.data.data(), evt.data.size()); + str.push_back('\0'); /* ending fix for UTF16 strings */ + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Extra copyright event: %s", str.c_str()); + } + } + else if (evt.sub_type == MidiEvent::kSequenceTrackTitle) + { + if (midi_music_title_.empty()) + { + midi_music_title_ = std::string((const char *)evt.data.data(), evt.data.size()); + midi_music_title_.push_back('\0'); /* ending fix for UTF16 strings */ + if (midi_output_interface_->onDebugMessage) + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Music title: %s", midi_music_title_.c_str()); + } + else + { + std::string str((const char *)evt.data.data(), evt.data.size()); + str.push_back('\0'); /* ending fix for UTF16 strings */ + midi_music_track_titles_.push_back(str); + if (midi_output_interface_->onDebugMessage) + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Track title: %s", str.c_str()); + } + } + else if (evt.sub_type == MidiEvent::kInstrumentTitle) + { + if (midi_output_interface_->onDebugMessage) + { + std::string str((const char *)evt.data.data(), evt.data.size()); + str.push_back('\0'); /* ending fix for UTF16 strings */ + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Instrument: %s", str.c_str()); + } + } + else if (evt.sub_type == MidiEvent::kMarker) + { + // To lower + for (size_t i = 0; i < data.size(); i++) + { + if (data[i] <= 'Z' && data[i] >= 'A') + data[i] = (char)(data[i] - ('Z' - 'z')); + } + + if (data == "loopstart") + { + // Return a custom Loop Start event instead of Marker + evt.sub_type = MidiEvent::kLoopStart; + evt.data.clear(); // Data is not needed + return evt; + } + + if (data == "loopend") + { + // Return a custom Loop End event instead of Marker + evt.sub_type = MidiEvent::kLoopEnd; + evt.data.clear(); // Data is not needed + return evt; + } + + if (data.substr(0, 10) == "loopstart=") + { + evt.type = MidiEvent::kSpecial; + evt.sub_type = MidiEvent::kLoopStackBegin; + uint8_t loops = (uint8_t)(atoi(data.substr(10).c_str())); + evt.data.clear(); + evt.data.push_back(loops); + + if (midi_output_interface_->onDebugMessage) + { + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Stack Marker Loop Start at %d to %d level with %d " + "loops", + midi_loop_.stack_level_, midi_loop_.stack_level_ + 1, loops); + } + return evt; + } + + if (data.substr(0, 8) == "loopend=") + { + evt.type = MidiEvent::kSpecial; + evt.sub_type = MidiEvent::kLoopStackEnd; + evt.data.clear(); + + if (midi_output_interface_->onDebugMessage) + { + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Stack Marker Loop %s at %d to %d level", + (evt.sub_type == MidiEvent::kLoopStackEnd ? "End" : "Break"), + midi_loop_.stack_level_, midi_loop_.stack_level_ - 1); + } + return evt; + } + } + + if (evtype == MidiEvent::kEndTrack) + status = -1; // Finalize track + + return evt; + } + + // Any normal event (80..EF) + if (byte < 0x80) + { + byte = (uint8_t)(status | 0x80); + ptr--; + } + + // Sys Com Song Select(Song #) [0-127] + if (byte == MidiEvent::kSysComSongSelect) + { + if (ptr + 1 > end) + { + midi_parsing_errors_string_ += "ParseEvent: Can't read System Command Song Select event - " + "Unexpected end of track data.\n"; + evt.is_valid = 0; + return evt; + } + evt.type = byte; + evt.data.push_back(*(ptr++)); + return evt; + } + + // Sys Com Song Position Pntr [LSB, MSB] + if (byte == MidiEvent::kSysComSongPositionPointer) + { + if (ptr + 2 > end) + { + midi_parsing_errors_string_ += "ParseEvent: Can't read System Command Position Pointer event " + "- Unexpected end of track data.\n"; + evt.is_valid = 0; + return evt; + } + evt.type = byte; + evt.data.push_back(*(ptr++)); + evt.data.push_back(*(ptr++)); + return evt; + } + + uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F; + status = byte; + evt.channel = midCh; + evt.type = evType; + + switch (evType) + { + case MidiEvent::kNoteOff: // 2 byte length + case MidiEvent::kNoteOn: + case MidiEvent::kNoteTouch: + case MidiEvent::kControlChange: + case MidiEvent::kPitchWheel: + if (ptr + 2 > end) + { + midi_parsing_errors_string_ += "ParseEvent: Can't read regular 2-byte event - Unexpected " + "end of track data.\n"; + evt.is_valid = 0; + return evt; + } + + evt.data.push_back(*(ptr++)); + evt.data.push_back(*(ptr++)); + + if ((evType == MidiEvent::kNoteOn) && (evt.data[1] == 0)) + { + evt.type = MidiEvent::kNoteOff; // Note ON with zero velocity + // is Note OFF! + } + else if (evType == MidiEvent::kControlChange) + { + // 111'th loopStart controller (RPG Maker and others) + if (midi_format_ == kFormatMidi) + { + switch (evt.data[0]) + { + case 110: + if (midi_loop_format_ == kLoopDefault) + { + // Change event type to custom Loop Start event + // and clear data + evt.type = MidiEvent::kSpecial; + evt.sub_type = MidiEvent::kLoopStart; + evt.data.clear(); + midi_loop_format_ = kLoopHmi; + } + else if (midi_loop_format_ == kLoopHmi) + { + // Repeating of 110'th point is BAD practice, + // treat as EMIDI + midi_loop_format_ = kLoopEMidi; + } + break; + + case 111: + if (midi_loop_format_ == kLoopHmi) + { + // Change event type to custom Loop End event + // and clear data + evt.type = MidiEvent::kSpecial; + evt.sub_type = MidiEvent::kLoopEnd; + evt.data.clear(); + } + else if (midi_loop_format_ != kLoopEMidi) + { + // Change event type to custom Loop Start event + // and clear data + evt.type = MidiEvent::kSpecial; + evt.sub_type = MidiEvent::kLoopStart; + evt.data.clear(); + } + break; + + case 113: + if (midi_loop_format_ == kLoopEMidi) + { + // EMIDI does using of CC113 with same purpose + // as CC7 + evt.data[0] = 7; + } + break; + } + } +#if EDGE_XMI_SUPPORT + if (midi_format_ == kFormatXMidi) + { + switch (evt.data[0]) + { + case 116: // For Loop Controller + evt.type = MidiEvent::kSpecial; + evt.sub_type = MidiEvent::kLoopStackBegin; + evt.data[0] = evt.data[1]; + evt.data.pop_back(); + + if (midi_output_interface_->onDebugMessage) + { + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Stack XMI Loop Start at %d to %d level " + "with %d loops", + midi_loop_.stack_level_, midi_loop_.stack_level_ + 1, + evt.data[0]); + } + break; + + case 117: // Next/Break Loop Controller + evt.type = MidiEvent::kSpecial; + evt.sub_type = evt.data[1] < 64 ? MidiEvent::kLoopStackBreak : MidiEvent::kLoopStackEnd; + evt.data.clear(); + + if (midi_output_interface_->onDebugMessage) + { + midi_output_interface_->onDebugMessage( + midi_output_interface_->onDebugMessage_userdata, "Stack XMI Loop %s at %d to %d level", + (evt.sub_type == MidiEvent::kLoopStackEnd ? "End" : "Break"), midi_loop_.stack_level_, + midi_loop_.stack_level_ - 1); + } + break; + + case 119: // Callback Trigger + evt.type = MidiEvent::kSpecial; + evt.sub_type = MidiEvent::kCallbackTrigger; + evt.data.assign(1, evt.data[1]); + break; + } + } +#endif + } + + return evt; + case MidiEvent::kPatchChange: // 1 byte length + case MidiEvent::kChannelAftertouch: + if (ptr + 1 > end) + { + midi_parsing_errors_string_ += "ParseEvent: Can't read regular 1-byte event - Unexpected " + "end of track data.\n"; + evt.is_valid = 0; + return evt; + } + evt.data.push_back(*(ptr++)); + return evt; + default: + break; + } + + return evt; +} + +void MidiSequencer::handleEvent(size_t track, const MidiSequencer::MidiEvent &evt, int32_t &status) +{ + if (track == 0 && midi_smf_format_ < 2 && evt.type == MidiEvent::kSpecial && + (evt.sub_type == MidiEvent::kTempoChange || evt.sub_type == MidiEvent::kTimeSignature)) + { + /* never reject track 0 timing events on SMF format != 2 + note: multi-track XMI convert to format 2 SMF */ + } + else + { + if (midi_track_solo_ != ~(size_t)(0) && track != midi_track_solo_) + return; + if (midi_track_disabled_[track]) + return; + } + + if (midi_output_interface_->onEvent) + { + midi_output_interface_->onEvent(midi_output_interface_->onEvent_userdata, evt.type, evt.sub_type, evt.channel, + evt.data.data(), evt.data.size()); + } + + if (evt.type == MidiEvent::kSysex || evt.type == MidiEvent::kSysex2) // Ignore SysEx + { + midi_output_interface_->rt_systemExclusive(midi_output_interface_->rtUserData, evt.data.data(), + evt.data.size()); + return; + } + + if (evt.type == MidiEvent::kSpecial) + { + // Special event FF + uint_fast16_t evtype = evt.sub_type; + uint64_t length = (uint64_t)(evt.data.size()); + const char *data(length ? (const char *)(evt.data.data()) : "\0\0\0\0\0\0\0\0"); + + if (midi_output_interface_->rt_metaEvent) // Meta event hook + midi_output_interface_->rt_metaEvent(midi_output_interface_->rtUserData, evtype, (const uint8_t *)(data), + size_t(length)); + + if (evtype == MidiEvent::kEndTrack) // End Of Track + { + status = -1; + return; + } + + if (evtype == MidiEvent::kTempoChange) // Tempo change + { + midi_tempo_ = + midi_individual_tick_delta_ * MidiFraction(ReadIntBigEndian(evt.data.data(), evt.data.size())); + return; + } + + if (evtype == MidiEvent::kMarker) // Meta event + { + // Do nothing! :-P + return; + } + + if (evtype == MidiEvent::kDeviceSwitch) + { + if (midi_output_interface_->onDebugMessage) + midi_output_interface_->onDebugMessage(midi_output_interface_->onDebugMessage_userdata, + "Switching another device: %s", data); + if (midi_output_interface_->rt_deviceSwitch) + midi_output_interface_->rt_deviceSwitch(midi_output_interface_->rtUserData, track, data, + size_t(length)); + return; + } + + // Turn on Loop handling when loop is enabled + if (midi_loop_enabled_ && !midi_loop_.invalid_loop_) + { + if (evtype == MidiEvent::kLoopStart) // Special non-spec MIDI + // loop Start point + { + midi_loop_.caught_start_ = true; + return; + } + + if (evtype == MidiEvent::kLoopEnd) // Special non-spec MIDI loop End point + { + midi_loop_.caught_end_ = true; + return; + } + + if (evtype == MidiEvent::kLoopStackBegin) + { + if (midi_loop_.skip_stack_start_) + { + midi_loop_.skip_stack_start_ = false; + return; + } + + char x = data[0]; + size_t slevel = (size_t)(midi_loop_.stack_level_ + 1); + while (slevel >= midi_loop_.stack_.size()) + { + LoopStackEntry e; + e.loops = x; + e.infinity = (x == 0); + e.start = 0; + e.end = 0; + midi_loop_.stack_.push_back(e); + } + + LoopStackEntry &s = midi_loop_.stack_[slevel]; + s.loops = (int)(x); + s.infinity = (x == 0); + midi_loop_.caught_stack_start_ = true; + return; + } + + if (evtype == MidiEvent::kLoopStackEnd) + { + midi_loop_.caught_stack_end_ = true; + return; + } + + if (evtype == MidiEvent::kLoopStackBreak) + { + midi_loop_.caught_stack_break_ = true; + return; + } + } + + if (evtype == MidiEvent::kCallbackTrigger) + { + if (midi_trigger_handler_) + midi_trigger_handler_(midi_trigger_userdata_, (unsigned)(data[0]), track); + return; + } + + if (evtype == MidiEvent::kRawOPL) // Special non-spec ADLMIDI special for IMF + // playback: Direct poke to AdLib + { + if (midi_output_interface_->rt_rawOPL) + midi_output_interface_->rt_rawOPL(midi_output_interface_->rtUserData, (uint8_t)(data[0]), + (uint8_t)(data[1])); + return; + } + + if (evtype == MidiEvent::kSongBeginHook) + { + if (midi_output_interface_->onSongStart) + midi_output_interface_->onSongStart(midi_output_interface_->onSongStart_userdata); + return; + } + + return; + } + + if (evt.type == MidiEvent::kSysComSongSelect || evt.type == MidiEvent::kSysComSongPositionPointer) + return; + + size_t midCh = evt.channel; + if (midi_output_interface_->rt_currentDevice) + midCh += midi_output_interface_->rt_currentDevice(midi_output_interface_->rtUserData, track); + status = evt.type; + + switch (evt.type) + { + case MidiEvent::kNoteOff: // Note off + { + if (midCh < 16 && m_channelDisable[midCh]) + break; // Disabled channel + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + if (midi_output_interface_->rt_noteOff) + midi_output_interface_->rt_noteOff(midi_output_interface_->rtUserData, (uint8_t)(midCh), note); + if (midi_output_interface_->rt_noteOffVel) + midi_output_interface_->rt_noteOffVel(midi_output_interface_->rtUserData, (uint8_t)(midCh), note, vol); + break; + } + + case MidiEvent::kNoteOn: // Note on + { + if (midCh < 16 && m_channelDisable[midCh]) + break; // Disabled channel + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + midi_output_interface_->rt_noteOn(midi_output_interface_->rtUserData, (uint8_t)(midCh), note, vol); + break; + } + + case MidiEvent::kNoteTouch: // Note touch + { + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + midi_output_interface_->rt_noteAfterTouch(midi_output_interface_->rtUserData, (uint8_t)(midCh), note, vol); + break; + } + + case MidiEvent::kControlChange: // Controller change + { + uint8_t ctrlno = evt.data[0]; + uint8_t value = evt.data[1]; + midi_output_interface_->rt_controllerChange(midi_output_interface_->rtUserData, (uint8_t)(midCh), ctrlno, + value); + break; + } + + case MidiEvent::kPatchChange: // Patch change + { + midi_output_interface_->rt_patchChange(midi_output_interface_->rtUserData, (uint8_t)(midCh), evt.data[0]); + break; + } + + case MidiEvent::kChannelAftertouch: // Channel after-touch + { + uint8_t chanat = evt.data[0]; + midi_output_interface_->rt_channelAfterTouch(midi_output_interface_->rtUserData, (uint8_t)(midCh), chanat); + break; + } + + case MidiEvent::kPitchWheel: // Wheel/pitch bend + { + uint8_t a = evt.data[0]; + uint8_t b = evt.data[1]; + midi_output_interface_->rt_pitchBend(midi_output_interface_->rtUserData, (uint8_t)(midCh), b, a); + break; + } + + default: + break; + } // switch +} + +double MidiSequencer::Tick(double s, double granularity) +{ + EPI_ASSERT(midi_output_interface_); // MIDI output interface must be defined! + + s *= midi_tempo_multiplier_; + midi_current_position_.wait -= s; + midi_current_position_.absolute_time_position += s; + + int antiFreezeCounter = 10000; // Limit 10000 loops to avoid freezing + while ((midi_current_position_.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) + { + if (!ProcessEvents()) + break; + if (midi_current_position_.wait <= 0.0) + antiFreezeCounter--; + } + + if (antiFreezeCounter <= 0) + midi_current_position_.wait += 1.0; /* Add extra 1 second when over 10000 events + with zero delay are been detected */ + + if (midi_current_position_.wait < 0.0) // Avoid negative delay value! + return 0.0; + + return midi_current_position_.wait; +} + +double MidiSequencer::Seek(double seconds, const double granularity) +{ + if (seconds < 0.0) + return 0.0; // Seeking negative position is forbidden! :-P + const double granualityHalf = granularity * 0.5, + s = seconds; // m_setup.delay_ < m_setup.maxdelay ? + // m_setup.delay_ : m_setup.maxdelay; + + /* Attempt to go away out of song end must rewind position to begin */ + if (seconds > midi_full_song_time_length_) + { + this->Rewind(); + return 0.0; + } + + bool loopFlagState = midi_loop_enabled_; + // Turn loop pooints off because it causes wrong position rememberin on a + // quick seek + midi_loop_enabled_ = false; + + /* + * Seeking search is similar to regular ticking, except of next things: + * - We don't processsing arpeggio and vibrato + * - To keep correctness of the state after seek, begin every search from + * begin + * - All sustaining notes must be killed + * - Ignore Note-On events + */ + this->Rewind(); + + /* + * Set "loop Start" to false to prevent overwrite of loopStart position with + * seek destinition position + * + * TODO: Detect & set loopStart position on load time to don't break loop + * while seeking + */ + midi_loop_.caught_start_ = false; + + midi_loop_.temporary_broken_ = (seconds >= midi_loop_end_time_); + + while ((midi_current_position_.absolute_time_position < seconds) && + (midi_current_position_.absolute_time_position < midi_full_song_time_length_)) + { + midi_current_position_.wait -= s; + midi_current_position_.absolute_time_position += s; + int antiFreezeCounter = 10000; // Limit 10000 loops to avoid freezing + double dstWait = midi_current_position_.wait + granualityHalf; + while ((midi_current_position_.wait <= granualityHalf) /*&& (antiFreezeCounter > 0)*/) + { + // std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); + if (!ProcessEvents(true)) + break; + // Avoid freeze because of no waiting increasing in more than 10000 + // cycles + if (midi_current_position_.wait <= dstWait) + antiFreezeCounter--; + else + { + dstWait = midi_current_position_.wait + granualityHalf; + antiFreezeCounter = 10000; + } + } + if (antiFreezeCounter <= 0) + midi_current_position_.wait += 1.0; /* Add extra 1 second when over 10000 events + with zero delay are been detected */ + } + + if (midi_current_position_.wait < 0.0) + midi_current_position_.wait = 0.0; + + if (midi_at_end_) + { + this->Rewind(); + midi_loop_enabled_ = loopFlagState; + return 0.0; + } + + midi_time_.Reset(); + midi_time_.delay_ = midi_current_position_.wait; + + midi_loop_enabled_ = loopFlagState; + return midi_current_position_.wait; +} + +double MidiSequencer::Tell() +{ + return midi_current_position_.absolute_time_position; +} + +double MidiSequencer::TimeLength() +{ + return midi_full_song_time_length_; +} + +double MidiSequencer::GetLoopStart() +{ + return midi_loop_start_time_; +} + +double MidiSequencer::GetLoopEnd() +{ + return midi_loop_end_time_; +} + +void MidiSequencer::Rewind() +{ + midi_current_position_ = midi_track_begin_position_; + midi_at_end_ = false; + + midi_loop_.loops_count_ = midi_loop_count_; + midi_loop_.Reset(); + midi_loop_.caught_start_ = true; + midi_loop_.temporary_broken_ = false; + midi_time_.Reset(); +} + +void MidiSequencer::SetTempo(double tempo) +{ + midi_tempo_multiplier_ = tempo; +} + +bool MidiSequencer::LoadMidi(const uint8_t *data, size_t size, uint16_t rate) +{ + epi::MemFile *mfr = new epi::MemFile(data, size); + return LoadMidi(mfr, rate); +} + +template class BufferGuard +{ + T *m_ptr; + + public: + BufferGuard() : m_ptr(nullptr) + { + } + + ~BufferGuard() + { + set(); + } + + void set(T *p = nullptr) + { + if (m_ptr) + free(m_ptr); + m_ptr = p; + } +}; + +/** + * @brief Detect the EA-MUS file format + * @param head Header part + * @param fr Context with opened file data + * @return true if given file was identified as EA-MUS + */ +static bool detectRSXX(const char *head, epi::MemFile *mfr) +{ + char headerBuf[7] = ""; + bool ret = false; + + // Try to identify RSXX format + if (head[0] >= 0x5D) + { + mfr->Seek(head[0] - 0x10, epi::File::kSeekpointStart); + mfr->Read(headerBuf, 6); + if (memcmp(headerBuf, "rsxx}u", 6) == 0) + ret = true; + } + + mfr->Seek(0, epi::File::kSeekpointStart); + return ret; +} +#if EDGE_IMF_SUPPORT +/** + * @brief Detect the Id-software Music File format + * @param head Header part + * @param fr Context with opened file data + * @return true if given file was identified as IMF + */ +static bool detectIMF(const char *head, epi::MemFile *mfr) +{ + uint8_t raw[4]; + size_t end = (size_t)(head[0]) + 256 * (size_t)(head[1]); + + if (end & 3) + return false; + + size_t backup_pos = mfr->GetPosition(); + int64_t sum1 = 0, sum2 = 0; + mfr->Seek((end > 0 ? 2 : 0), epi::File::kSeekpointStart); + + for (size_t n = 0; n < 16383; ++n) + { + if (mfr->Read(raw, 4) != 4) + break; + int64_t value1 = raw[0]; + value1 += raw[1] << 8; + sum1 += value1; + int64_t value2 = raw[2]; + value2 += raw[3] << 8; + sum2 += value2; + } + + mfr->Seek((long)(backup_pos), epi::File::kSeekpointStart); + + return (sum1 > sum2); +} +#endif +bool MidiSequencer::LoadMidi(epi::MemFile *mfr, uint16_t rate) +{ + midi_parsing_errors_string_.clear(); + + EPI_ASSERT(midi_output_interface_); // MIDI output interface must be defined! + + midi_at_end_ = false; + midi_loop_.FullReset(); + midi_loop_.caught_start_ = true; + + midi_format_ = kFormatMidi; + midi_smf_format_ = 0; + + midi_raw_songs_data_.clear(); + + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + + size_t fsize = mfr->Read(headerBuf, headerSize); + if (fsize < headerSize) + { + midi_error_string_ = "Unexpected end of file at header!\n"; + delete mfr; + return false; + } + + if (memcmp(headerBuf, "MThd\0\0\0\6", 8) == 0) + { + mfr->Seek(0, epi::File::kSeekpointStart); + return ParseSMF(mfr); + } + + if (memcmp(headerBuf, "RIFF", 4) == 0) + { + mfr->Seek(0, epi::File::kSeekpointStart); + return ParseRMI(mfr); + } + + if (memcmp(headerBuf, "GMF\x1", 4) == 0) + { + mfr->Seek(0, epi::File::kSeekpointStart); + return ParseGMF(mfr); + } +#if EDGE_MUS_SUPPORT + if (memcmp(headerBuf, "MUS\x1A", 4) == 0) + { + mfr->Seek(0, epi::File::kSeekpointStart); + return ParseMUS(mfr); + } +#endif +#if EDGE_XMI_SUPPORT + if ((memcmp(headerBuf, "FORM", 4) == 0) && (memcmp(headerBuf + 8, "XDIR", 4) == 0)) + { + mfr->Seek(0, epi::File::kSeekpointStart); + return ParseXMI(mfr); + } +#endif +#if EDGE_IMF_SUPPORT + if (detectIMF(headerBuf, mfr)) + { + mfr->Seek(0, epi::File::kSeekpointStart); + return ParseIMF(mfr, rate); + } +#endif + if (detectRSXX(headerBuf, mfr)) + { + mfr->Seek(0, epi::File::kSeekpointStart); + return ParseRSXX(mfr); + } + + midi_error_string_ = "Unknown or unsupported file format"; + delete mfr; + return false; +} +#if EDGE_IMF_SUPPORT +bool MidiSequencer::ParseIMF(epi::MemFile *mfr, uint16_t rate) +{ + const size_t deltaTicks = 1; + const size_t track_count = 1; + uint32_t imfTempo = 0; + size_t imfEnd = 0; + uint64_t abs_position = 0; + uint8_t imfRaw[4]; + + MidiTrackRow evtPos; + MidiEvent event; + + switch (rate) + { + case 280: + imfTempo = 3570; + break; + case 560: + imfTempo = 1785; + break; + case 700: + imfTempo = 1428; + break; + default: + imfTempo = 1428; + break; + } + + std::vector temposList; + + midi_format_ = kFormatIMF; + + BuildSMFSetupReset(track_count); + + midi_individual_tick_delta_ = MidiFraction(1, 1000000l * (uint64_t)(deltaTicks)); + midi_tempo_ = MidiFraction(1, (uint64_t)(deltaTicks) * 2); + + mfr->Seek(0, epi::File::kSeekpointStart); + if (mfr->Read(imfRaw, 2) != 2) + { + midi_error_string_ = "Unexpected end of file at header!\n"; + delete mfr; + return false; + } + + imfEnd = (size_t)(imfRaw[0]) + 256 * (size_t)(imfRaw[1]); + + // Define the playing tempo + event.type = MidiEvent::kSpecial; + event.sub_type = MidiEvent::kTempoChange; + event.absolute_tick_position = 0; + event.data.resize(4); + event.data[0] = (uint8_t)((imfTempo >> 24) & 0xFF); + event.data[1] = (uint8_t)((imfTempo >> 16) & 0xFF); + event.data[2] = (uint8_t)((imfTempo >> 8) & 0xFF); + event.data[3] = (uint8_t)((imfTempo & 0xFF)); + evtPos.events_.push_back(event); + temposList.push_back(event); + + // Define the draft for IMF events + event.type = MidiEvent::kSpecial; + event.sub_type = MidiEvent::kRawOPL; + event.absolute_tick_position = 0; + event.data.resize(2); + + mfr->Seek((imfEnd > 0) ? 2 : 0, epi::File::kSeekpointStart); + + if (imfEnd == 0) // IMF Type 0 with unlimited file length + imfEnd = mfr->GetLength(); + + while (mfr->GetPosition() < (int)imfEnd) + { + if (mfr->Read(imfRaw, 4) != 4) + break; + + event.data[0] = imfRaw[0]; // port index + event.data[1] = imfRaw[1]; // port value + event.absolute_tick_position = abs_position; + event.is_valid = 1; + + evtPos.events_.push_back(event); + evtPos.delay_ = (uint64_t)(imfRaw[2]) + 256 * (uint64_t)(imfRaw[3]); + + if (evtPos.delay_ > 0) + { + evtPos.absolute_position_ = abs_position; + abs_position += evtPos.delay_; + midi_track_data_[0].push_back(evtPos); + evtPos.Clear(); + } + } + + // Add final row + evtPos.absolute_position_ = abs_position; + abs_position += evtPos.delay_; + midi_track_data_[0].push_back(evtPos); + + if (!midi_track_data_[0].empty()) + midi_current_position_.track[0].pos = midi_track_data_[0].begin(); + + BuildTimeLine(temposList); + + delete mfr; + + return true; +} +#endif +bool MidiSequencer::ParseRSXX(epi::MemFile *mfr) +{ + const size_t headerSize = 14; + char headerBuf[headerSize] = ""; + size_t fsize = 0; + size_t deltaTicks = 192, track_count = 1; + std::vector> rawTrackData; + + fsize = mfr->Read(headerBuf, headerSize); + if (fsize < headerSize) + { + midi_error_string_ = "Unexpected end of file at header!\n"; + delete mfr; + return false; + } + + // Try to identify RSXX format + char start = headerBuf[0]; + if (start < 0x5D) + { + midi_error_string_ = "RSXX song too short!\n"; + delete mfr; + return false; + } + else + { + mfr->Seek(headerBuf[0] - 0x10, epi::File::kSeekpointStart); + mfr->Read(headerBuf, 6); + if (memcmp(headerBuf, "rsxx}u", 6) == 0) + { + midi_format_ = kFormatRSXX; + mfr->Seek(start, epi::File::kSeekpointStart); + track_count = 1; + deltaTicks = 60; + } + else + { + midi_error_string_ = "Invalid RSXX header!\n"; + delete mfr; + return false; + } + } + + rawTrackData.clear(); + rawTrackData.resize(track_count, std::vector()); + midi_individual_tick_delta_ = MidiFraction(1, 1000000l * (uint64_t)(deltaTicks)); + midi_tempo_ = MidiFraction(1, (uint64_t)(deltaTicks)); + + size_t totalGotten = 0; + + for (size_t tk = 0; tk < track_count; ++tk) + { + // Read track header + size_t trackLength; + + size_t pos = mfr->GetPosition(); + mfr->Seek(0, epi::File::kSeekpointEnd); + trackLength = mfr->GetPosition() - pos; + mfr->Seek((long)(pos), epi::File::kSeekpointStart); + + // Read track data + rawTrackData[tk].resize(trackLength); + fsize = mfr->Read(&rawTrackData[tk][0], trackLength); + if (fsize < trackLength) + { + midi_error_string_ = "MIDI Loader: Unexpected file ending while getting raw track " + "data!\n"; + delete mfr; + return false; + } + totalGotten += fsize; + + // Finalize raw track data with a zero + rawTrackData[tk].push_back(0); + } + + for (size_t tk = 0; tk < track_count; ++tk) + totalGotten += rawTrackData[tk].size(); + + if (totalGotten == 0) + { + midi_error_string_ = "MIDI Loader: Empty track data"; + delete mfr; + return false; + } + + // Build new MIDI events table + if (!BuildSMFTrackData(rawTrackData)) + { + midi_error_string_ = "MIDI Loader: MIDI data parsing error has occouped!\n" + midi_parsing_errors_string_; + delete mfr; + return false; + } + + midi_smf_format_ = 0; + midi_loop_.stack_level_ = -1; + + delete mfr; + + return true; +} + +bool MidiSequencer::ParseGMF(epi::MemFile *mfr) +{ + const size_t headerSize = 14; + char headerBuf[headerSize] = ""; + size_t fsize = 0; + size_t deltaTicks = 192, track_count = 1; + std::vector> rawTrackData; + + fsize = mfr->Read(headerBuf, headerSize); + if (fsize < headerSize) + { + midi_error_string_ = "Unexpected end of file at header!\n"; + delete mfr; + return false; + } + + if (memcmp(headerBuf, "GMF\x1", 4) != 0) + { + midi_error_string_ = "MIDI Loader: Invalid format, GMF\\x1 signature is not found!\n"; + delete mfr; + return false; + } + + mfr->Seek(7 - (long)(headerSize), epi::File::kSeekpointCurrent); + + rawTrackData.clear(); + rawTrackData.resize(track_count, std::vector()); + midi_individual_tick_delta_ = MidiFraction(1, 1000000l * (uint64_t)(deltaTicks)); + midi_tempo_ = MidiFraction(1, (uint64_t)(deltaTicks) * 2); + static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; + size_t totalGotten = 0; + + for (size_t tk = 0; tk < track_count; ++tk) + { + // Read track header + size_t trackLength; + size_t pos = mfr->GetPosition(); + mfr->Seek(0, epi::File::kSeekpointEnd); + trackLength = mfr->GetPosition() - pos; + mfr->Seek((long)(pos), epi::File::kSeekpointStart); + + // Read track data + rawTrackData[tk].resize(trackLength); + fsize = mfr->Read(&rawTrackData[tk][0], trackLength); + if (fsize < trackLength) + { + midi_error_string_ = "MIDI Loader: Unexpected file ending while getting raw track " + "data!\n"; + delete mfr; + return false; + } + totalGotten += fsize; + // Note: GMF does include the track end tag. + rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); + } + + for (size_t tk = 0; tk < track_count; ++tk) + totalGotten += rawTrackData[tk].size(); + + if (totalGotten == 0) + { + midi_error_string_ = "MIDI Loader: Empty track data"; + delete mfr; + return false; + } + + // Build new MIDI events table + if (!BuildSMFTrackData(rawTrackData)) + { + midi_error_string_ = "MIDI Loader: : MIDI data parsing error has occouped!\n" + midi_parsing_errors_string_; + delete mfr; + return false; + } + + delete mfr; + + return true; +} + +bool MidiSequencer::ParseSMF(epi::MemFile *mfr) +{ + const size_t headerSize = 14; // 4 + 4 + 2 + 2 + 2 + char headerBuf[headerSize] = ""; + size_t fsize = 0; + size_t deltaTicks = 192, TrackCount = 1; + unsigned smfFormat = 0; + std::vector> rawTrackData; + + fsize = mfr->Read(headerBuf, headerSize); + if (fsize < headerSize) + { + midi_error_string_ = "Unexpected end of file at header!\n"; + delete mfr; + return false; + } + + if (memcmp(headerBuf, "MThd\0\0\0\6", 8) != 0) + { + midi_error_string_ = "MIDI Loader: Invalid format, MThd signature is not found!\n"; + delete mfr; + return false; + } + + smfFormat = (unsigned)(ReadIntBigEndian(headerBuf + 8, 2)); + TrackCount = (size_t)(ReadIntBigEndian(headerBuf + 10, 2)); + deltaTicks = (size_t)(ReadIntBigEndian(headerBuf + 12, 2)); + + if (smfFormat > 2) + smfFormat = 1; + + rawTrackData.clear(); + rawTrackData.resize(TrackCount, std::vector()); + midi_individual_tick_delta_ = MidiFraction(1, 1000000l * (uint64_t)(deltaTicks)); + midi_tempo_ = MidiFraction(1, (uint64_t)(deltaTicks) * 2); + + size_t totalGotten = 0; + + for (size_t tk = 0; tk < TrackCount; ++tk) + { + // Read track header + size_t trackLength; + + fsize = mfr->Read(headerBuf, 8); + if ((fsize < 8) || (memcmp(headerBuf, "MTrk", 4) != 0)) + { + midi_error_string_ = "MIDI Loader: Invalid format, MTrk signature is not found!\n"; + delete mfr; + return false; + } + trackLength = (size_t)ReadIntBigEndian(headerBuf + 4, 4); + + // Read track data + rawTrackData[tk].resize(trackLength); + fsize = mfr->Read(&rawTrackData[tk][0], trackLength); + if (fsize < trackLength) + { + midi_error_string_ = "MIDI Loader: Unexpected file ending while getting raw track " + "data!\n"; + delete mfr; + return false; + } + + totalGotten += fsize; + } + + for (size_t tk = 0; tk < TrackCount; ++tk) + totalGotten += rawTrackData[tk].size(); + + if (totalGotten == 0) + { + midi_error_string_ = "MIDI Loader: Empty track data"; + delete mfr; + return false; + } + + // Build new MIDI events table + if (!BuildSMFTrackData(rawTrackData)) + { + midi_error_string_ = "MIDI Loader: MIDI data parsing error has occouped!\n" + midi_parsing_errors_string_; + delete mfr; + return false; + } + + midi_smf_format_ = smfFormat; + midi_loop_.stack_level_ = -1; + + delete mfr; + + return true; +} + +bool MidiSequencer::ParseRMI(epi::MemFile *mfr) +{ + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; + + size_t fsize = mfr->Read(headerBuf, headerSize); + if (fsize < headerSize) + { + midi_error_string_ = "Unexpected end of file at header!\n"; + delete mfr; + return false; + } + + if (memcmp(headerBuf, "RIFF", 4) != 0) + { + midi_error_string_ = "MIDI Loader: Invalid format, RIFF signature is not found!\n"; + delete mfr; + return false; + } + + midi_format_ = kFormatMidi; + + mfr->Seek(6l, epi::File::kSeekpointCurrent); + return ParseSMF(mfr); +} +#if EDGE_MUS_SUPPORT +bool MidiSequencer::ParseMUS(epi::MemFile *mfr) +{ + const size_t headerSize = 14; + char headerBuf[headerSize] = ""; + size_t fsize = 0; + BufferGuard cvt_buf; + + fsize = mfr->Read(headerBuf, headerSize); + if (fsize < headerSize) + { + midi_error_string_ = "Unexpected end of file at header!\n"; + delete mfr; + return false; + } + + if (memcmp(headerBuf, "MUS\x1A", 4) != 0) + { + midi_error_string_ = "MIDI Loader: Invalid format, MUS\\x1A signature is not found!\n"; + delete mfr; + return false; + } + + size_t mus_len = mfr->GetLength(); + + mfr->Seek(0, epi::File::kSeekpointStart); + uint8_t *mus = (uint8_t *)malloc(mus_len); + if (!mus) + { + midi_error_string_ = "Out of memory!"; + delete mfr; + return false; + } + fsize = mfr->Read(mus, mus_len); + if (fsize < mus_len) + { + midi_error_string_ = "Failed to read MUS file data!\n"; + delete mfr; + return false; + } + + // Close source stream + delete mfr; + mfr = nullptr; + + uint8_t *mid = nullptr; + uint32_t mid_len = 0; + int m2mret = ConvertMusToMidi(mus, (uint32_t)(mus_len), &mid, &mid_len, 0); + + if (mus) + free(mus); + + if (m2mret < 0) + { + midi_error_string_ = "Invalid MUS/DMX data format!"; + if (mid) + free(mid); + return false; + } + cvt_buf.set(mid); + + // Open converted MIDI file + mfr = new epi::MemFile(mid, (size_t)(mid_len)); + + return ParseSMF(mfr); +} +#endif +#if EDGE_XMI_SUPPORT +bool MidiSequencer::ParseXMI(epi::MemFile *mfr) +{ + const size_t headerSize = 14; + char headerBuf[headerSize] = ""; + size_t fsize = 0; + std::vector> song_buf; + bool ret; + + fsize = mfr->Read(headerBuf, headerSize); + if (fsize < headerSize) + { + midi_error_string_ = "Unexpected end of file at header!\n"; + delete mfr; + return false; + } + + if (memcmp(headerBuf, "FORM", 4) != 0) + { + midi_error_string_ = "MIDI Loader: Invalid format, FORM signature is not found!\n"; + delete mfr; + return false; + } + + if (memcmp(headerBuf + 8, "XDIR", 4) != 0) + { + midi_error_string_ = "MIDI Loader: Invalid format\n"; + delete mfr; + return false; + } + + size_t mus_len = mfr->GetLength(); + mfr->Seek(0, epi::File::kSeekpointStart); + + uint8_t *mus = (uint8_t *)malloc(mus_len + 20); + if (!mus) + { + midi_error_string_ = "Out of memory!"; + delete mfr; + return false; + } + + EPI_CLEAR_MEMORY(mus, uint8_t, mus_len + 20); + + fsize = mfr->Read(mus, mus_len); + if (fsize < mus_len) + { + midi_error_string_ = "Failed to read XMI file data!\n"; + delete mfr; + return false; + } + + // Close source stream + delete mfr; + mfr = nullptr; + + int m2mret = ConvertXMIToMidi(mus, (uint32_t)(mus_len + 20), song_buf, kXMINoConversion); + if (mus) + free(mus); + if (m2mret < 0) + { + song_buf.clear(); + midi_error_string_ = "Invalid XMI data format!"; + return false; + } + + if (midi_load_track_number_ >= (int)song_buf.size()) + midi_load_track_number_ = song_buf.size() - 1; + + for (size_t i = 0; i < song_buf.size(); ++i) + { + midi_raw_songs_data_.push_back(song_buf[i]); + } + + song_buf.clear(); + + // Open converted MIDI file + mfr = new epi::MemFile(midi_raw_songs_data_[midi_load_track_number_].data(), + midi_raw_songs_data_[midi_load_track_number_].size()); + // Set format as XMIDI + midi_format_ = kFormatXMidi; + + ret = ParseSMF(mfr); + + return ret; +} +#endif \ No newline at end of file diff --git a/source_files/edge/s_music.cc b/source_files/edge/s_music.cc index 8b0b7e593..f8161d225 100644 --- a/source_files/edge/s_music.cc +++ b/source_files/edge/s_music.cc @@ -34,7 +34,7 @@ #if EDGE_FLAC_SUPPORT #include "s_flac.h" #endif -#include "s_fluid.h" +#include "s_midi.h" #if EDGE_TRACKER_SUPPORT #include "s_m4p.h" #endif @@ -235,7 +235,7 @@ void ChangeMusic(int entry_number, bool loop) case kSoundMUS: #endif delete F; - music_player = PlayFluidMusic(data, length, loop); + music_player = PlayMIDIMusic(data, length, loop); break; default: diff --git a/source_files/edge/s_sid.cc b/source_files/edge/s_sid.cc index 44b159c74..9046d5328 100644 --- a/source_files/edge/s_sid.cc +++ b/source_files/edge/s_sid.cc @@ -114,7 +114,7 @@ static ma_result ma_crsid_init_internal(const ma_decoding_backend_config *pConfi } EPI_CLEAR_MEMORY(pcrSID, ma_crsid, 1); - pcrSID->format = ma_format_f32; /* Only supporting f32. */ + pcrSID->format = ma_format_s16; dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_ma_crsid_ds_vtable; @@ -238,9 +238,9 @@ static ma_result ma_crsid_read_pcm_frames(ma_crsid *pcrSID, void *pFramesOut, ma ma_crsid_get_data_format(pcrSID, &format, &channels, NULL, NULL, 0); - if (format == ma_format_f32) + if (format == ma_format_s16) { - cRSID_generateFloat(pcrSID->C64, (float *)pFramesOut, frameCount * 2 * sizeof(float)); + cRSID_generateSound(pcrSID->C64, (uint8_t *)pFramesOut, frameCount * 2 * sizeof(int16_t)); totalFramesRead = frameCount; } else @@ -499,7 +499,7 @@ bool SIDPlayer::OpenMemory(uint8_t *data, int length) Close(); ma_decoder_config decode_config = ma_decoder_config_init_default(); - decode_config.format = ma_format_f32; + decode_config.format = ma_format_s16; decode_config.customBackendCount = 1; decode_config.pCustomBackendUserData = NULL; decode_config.ppCustomBackendVTables = &custom_vtable;