From 8326a355c45d1d038934264be5f00c7dc5ac33c7 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Wed, 20 Jun 2018 19:11:51 +0300 Subject: Move file reader into the separated header file --- src/adlmidi_load.cpp | 22 ++-- src/adlmidi_private.hpp | 155 +---------------------------- src/file_reader.hpp | 259 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 163 deletions(-) create mode 100644 src/file_reader.hpp diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 7f69e25..10bea21 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -60,14 +60,14 @@ uint64_t MIDIplay::ReadLEint(const void *buffer, size_t nbytes) bool MIDIplay::LoadBank(const std::string &filename) { - fileReader file; + FileAndMemReader file; file.openFile(filename.c_str()); return LoadBank(file); } bool MIDIplay::LoadBank(const void *data, size_t size) { - fileReader file; + FileAndMemReader file; file.openData(data, size); return LoadBank(file); } @@ -194,7 +194,7 @@ void cvt_FMIns_to_ADLI(ADL_Instrument &ins, const adlinsdata2 &in) cvt_FMIns_to_generic(ins, in); } -bool MIDIplay::LoadBank(MIDIplay::fileReader &fr) +bool MIDIplay::LoadBank(FileAndMemReader &fr) { int err = 0; WOPLFile *wopl = NULL; @@ -295,7 +295,7 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER bool MIDIplay::LoadMIDI(const std::string &filename) { - fileReader file; + FileAndMemReader file; file.openFile(filename.c_str()); if(!LoadMIDI(file)) return false; @@ -304,12 +304,12 @@ bool MIDIplay::LoadMIDI(const std::string &filename) bool MIDIplay::LoadMIDI(const void *data, size_t size) { - fileReader file; + FileAndMemReader file; file.openData(data, size); return LoadMIDI(file); } -bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr) +bool MIDIplay::LoadMIDI(FileAndMemReader &fr) { size_t fsize; ADL_UNUSED(fsize); @@ -406,7 +406,7 @@ riffskip: if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0) { fr.close(); - errorStringOut = fr._fileName + ": Invalid format\n"; + errorStringOut = fr.fileName() + ": Invalid format\n"; return false; } @@ -570,7 +570,7 @@ riffskip: if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0) { fr.close(); - errorStringOut = fr._fileName + ": Invalid format, Header signature is unknown!\n"; + errorStringOut = fr.fileName() + ": Invalid format, Header signature is unknown!\n"; return false; } @@ -667,7 +667,7 @@ riffskip: if(std::memcmp(HeaderBuf, "MTrk", 4) != 0) { fr.close(); - errorStringOut = fr._fileName + ": Invalid format, MTrk signature is not found!\n"; + errorStringOut = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; return false; } TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4); @@ -703,14 +703,14 @@ riffskip: if(totalGotten == 0) { - errorStringOut = fr._fileName + ": Empty track data"; + errorStringOut = fr.fileName() + ": Empty track data"; return false; } //Build new MIDI events table if(!buildTrackData()) { - errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString; + errorStringOut = fr.fileName() + ": MIDI data parsing error has occouped!\n" + errorString; return false; } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index fda629d..13160b2 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -131,6 +131,7 @@ typedef int32_t ssize_t; #define INT32_MAX 0x7fffffff #endif +#include "file_reader.hpp" #include "fraction.hpp" #ifndef ADLMIDI_HW_OPL #include "chips/opl_chip_base.h" @@ -343,156 +344,6 @@ public: /**********************Internal structures and classes**********************/ - /** - * @brief A little class gives able to read filedata from disk and also from a memory segment - */ - class fileReader - { - public: - enum relTo - { - SET = 0, - CUR = 1, - END = 2 - }; - - fileReader() - { - fp = NULL; - mp = NULL; - mp_size = 0; - mp_tell = 0; - } - ~fileReader() - { - close(); - } - - void openFile(const char *path) - { - #if !defined(_WIN32) || defined(__WATCOMC__) - fp = std::fopen(path, "rb"); - #else - wchar_t widePath[MAX_PATH]; - int size = MultiByteToWideChar(CP_UTF8, 0, path, (int)std::strlen(path), widePath, MAX_PATH); - widePath[size] = '\0'; - fp = _wfopen(widePath, L"rb"); - #endif - _fileName = path; - mp = NULL; - mp_size = 0; - mp_tell = 0; - } - - void openData(const void *mem, size_t lenght) - { - fp = NULL; - mp = mem; - mp_size = lenght; - mp_tell = 0; - } - - void seek(long pos, int rel_to) - { - if(fp) - std::fseek(fp, pos, rel_to); - else - { - switch(rel_to) - { - case SET: - mp_tell = static_cast(pos); - break; - - case END: - mp_tell = mp_size - static_cast(pos); - break; - - case CUR: - mp_tell = mp_tell + static_cast(pos); - break; - } - - if(mp_tell > mp_size) - mp_tell = mp_size; - } - } - - inline void seeku(uint64_t pos, int rel_to) - { - seek(static_cast(pos), rel_to); - } - - size_t read(void *buf, size_t num, size_t size) - { - if(fp) - return std::fread(buf, num, size, fp); - else - { - size_t pos = 0; - size_t maxSize = static_cast(size * num); - - while((pos < maxSize) && (mp_tell < mp_size)) - { - reinterpret_cast(buf)[pos] = reinterpret_cast(mp)[mp_tell]; - mp_tell++; - pos++; - } - - return pos; - } - } - - int getc() - { - if(fp) - return std::getc(fp); - else - { - if(mp_tell >= mp_size) return -1; - int x = reinterpret_cast(mp)[mp_tell]; - mp_tell++; - return x; - } - } - - size_t tell() - { - if(fp) - return static_cast(std::ftell(fp)); - else - return mp_tell; - } - - void close() - { - if(fp) std::fclose(fp); - - fp = NULL; - mp = NULL; - mp_size = 0; - mp_tell = 0; - } - - bool isValid() - { - return (fp) || (mp); - } - - bool eof() - { - if(fp) - return (std::feof(fp) != 0); - else - return mp_tell >= mp_size; - } - std::string _fileName; - std::FILE *fp; - const void *mp; - size_t mp_size; - size_t mp_tell; - }; - // Persistent settings for each MIDI channel struct MIDIchannel { @@ -1113,12 +964,12 @@ public: bool LoadBank(const std::string &filename); bool LoadBank(const void *data, size_t size); - bool LoadBank(fileReader &fr); + bool LoadBank(FileAndMemReader &fr); #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER bool LoadMIDI(const std::string &filename); bool LoadMIDI(const void *data, size_t size); - bool LoadMIDI(fileReader &fr); + bool LoadMIDI(FileAndMemReader &fr); /** * @brief Periodic tick handler. diff --git a/src/file_reader.hpp b/src/file_reader.hpp new file mode 100644 index 0000000..ce4307b --- /dev/null +++ b/src/file_reader.hpp @@ -0,0 +1,259 @@ +/* + * FileAndMemoryReader - a tiny helper to utify file reading from a disk and memory block + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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. + */ + +#include +#include +#ifdef _WIN32 +#include // MultiByteToWideChar +#endif + +/** + * @brief A little class gives able to read filedata from disk and also from a memory segment + */ +class FileAndMemReader +{ + //! Currently loaded filename (empty for a memory blocks) + std::string m_file_name; + //! File reader descriptor + std::FILE *m_fp; + + //! Memory pointer descriptor + const void *m_mp; + //! Size of memory block + size_t m_mp_size; + //! Cursor position in the memory block + size_t m_mp_tell; + +public: + /** + * @brief Relation direction + */ + enum relTo + { + //! At begin position + SET = 0, + //! At current position + CUR = 1, + //! At end position + END = 2 + }; + + /** + * @brief C.O.: It's a constructor! + */ + FileAndMemReader() : + m_fp(NULL), + m_mp(NULL), + m_mp_size(0), + m_mp_tell(0) + {} + + /** + * @brief C.O.: It's a destructor! + */ + ~FileAndMemReader() + { + close(); + } + + /** + * @brief Open file from a disk + * @param path Path to the file in UTF-8 (even on Windows!) + */ + void openFile(const char *path) + { + #if !defined(_WIN32) || defined(__WATCOMC__) + m_fp = std::fopen(path, "rb"); + #else + wchar_t widePath[MAX_PATH]; + int size = MultiByteToWideChar(CP_UTF8, 0, path, (int)std::strlen(path), widePath, MAX_PATH); + widePath[size] = '\0'; + fp = _wfopen(widePath, L"rb"); + #endif + m_file_name = path; + m_mp = NULL; + m_mp_size = 0; + m_mp_tell = 0; + } + + /** + * @brief Open file from memory block + * @param mem Pointer to the memory block + * @param lenght Size of given block + */ + void openData(const void *mem, size_t lenght) + { + m_fp = NULL; + m_mp = mem; + m_mp_size = lenght; + m_mp_tell = 0; + } + + /** + * @brief Seek to given position + * @param pos Offset or position + * @param rel_to Relation (at begin, at current, or at end) + */ + void seek(long pos, int rel_to) + { + if(m_fp)//If a file + { + std::fseek(m_fp, pos, rel_to); + } + else//If a memory block + { + switch(rel_to) + { + case SET: + m_mp_tell = static_cast(pos); + break; + + case END: + m_mp_tell = m_mp_size - static_cast(pos); + break; + + case CUR: + m_mp_tell = m_mp_tell + static_cast(pos); + break; + } + + if(m_mp_tell > m_mp_size) + m_mp_tell = m_mp_size; + } + } + + /** + * @brief Seek to given position (unsigned integer 64 as relation. Negative values not supported) + * @param pos Offset or position + * @param rel_to Relation (at begin, at current, or at end) + */ + inline void seeku(uint64_t pos, int rel_to) + { + this->seek(static_cast(pos), rel_to); + } + + /** + * @brief Read the buffer from a file + * @param buf Pointer to the destination memory block + * @param num Number of elements + * @param size Size of one element + * @return Size + */ + size_t read(void *buf, size_t num, size_t size) + { + if(m_fp) + return std::fread(buf, num, size, m_fp); + else + { + size_t pos = 0; + size_t maxSize = static_cast(size * num); + + while((pos < maxSize) && (m_mp_tell < m_mp_size)) + { + reinterpret_cast(buf)[pos] = reinterpret_cast(m_mp)[m_mp_tell]; + m_mp_tell++; + pos++; + } + + return pos / num; + } + } + + /** + * @brief Get one byte and seek forward + * @return Readed byte or EOF (a.k.a. -1) + */ + int getc() + { + if(m_fp)//If a file + { + return std::getc(m_fp); + } + else //If a memory block + { + if(m_mp_tell >= m_mp_size) + return -1; + int x = reinterpret_cast(m_mp)[m_mp_tell]; + m_mp_tell++; + return x; + } + } + + /** + * @brief Returns current offset of cursor in a file + * @return Offset position + */ + size_t tell() + { + if(m_fp)//If a file + return static_cast(std::ftell(m_fp)); + else//If a memory block + return m_mp_tell; + } + + /** + * @brief Close the file + */ + void close() + { + if(m_fp) + std::fclose(m_fp); + + m_fp = NULL; + m_mp = NULL; + m_mp_size = 0; + m_mp_tell = 0; + } + + /** + * @brief Is file instance valid + * @return true if vaild + */ + bool isValid() + { + return (m_fp) || (m_mp); + } + + /** + * @brief Is End Of File? + * @return true if end of file was reached + */ + bool eof() + { + if(m_fp) + return (std::feof(m_fp) != 0); + else + return m_mp_tell >= m_mp_size; + } + + /** + * @brief Get a current file name + * @return File name of currently loaded file + */ + const std::string &fileName() + { + return m_file_name; + } + +}; -- cgit v1.2.3 From a0fa0cded46bdc94293f02d4070a69bcae5478bb Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Wed, 20 Jun 2018 19:17:01 +0300 Subject: Avoid possible failure to compile for Windows --- src/file_reader.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/file_reader.hpp b/src/file_reader.hpp index ce4307b..e0664b4 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -83,14 +83,14 @@ public: */ void openFile(const char *path) { - #if !defined(_WIN32) || defined(__WATCOMC__) +#if !defined(_WIN32) || defined(__WATCOMC__) m_fp = std::fopen(path, "rb"); - #else +#else wchar_t widePath[MAX_PATH]; int size = MultiByteToWideChar(CP_UTF8, 0, path, (int)std::strlen(path), widePath, MAX_PATH); widePath[size] = '\0'; - fp = _wfopen(widePath, L"rb"); - #endif + m_fp = _wfopen(widePath, L"rb"); +#endif m_file_name = path; m_mp = NULL; m_mp_size = 0; -- cgit v1.2.3 From 9f0f9e4e374798851da39335406ac4bafad81297 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:23:14 +0300 Subject: Move MIDI sequencer into completely separated class TODO: - implement C bindings for most of class functions - test it in work on any different synthesizer --- CMakeLists.txt | 18 +- README.md | 23 +- libADLMIDI-test.pro | 10 +- libADLMIDI.pro | 10 +- src/adlmidi.cpp | 246 +++--- src/adlmidi_load.cpp | 450 ++--------- src/adlmidi_midiplay.cpp | 1292 +------------------------------ src/adlmidi_mus2mid.c | 451 ----------- src/adlmidi_mus2mid.h | 49 -- src/adlmidi_private.hpp | 370 +++------ src/adlmidi_sequencer.cpp | 153 ++++ src/adlmidi_xmi2mid.c | 1106 --------------------------- src/adlmidi_xmi2mid.h | 60 -- src/cvt_mus2mid.hpp | 461 +++++++++++ src/cvt_xmi2mid.hpp | 1123 +++++++++++++++++++++++++++ src/file_reader.hpp | 37 + src/midi_sequencer.h | 91 +++ src/midi_sequencer.hpp | 496 ++++++++++++ src/midi_sequencer_impl.hpp | 1780 +++++++++++++++++++++++++++++++++++++++++++ 19 files changed, 4535 insertions(+), 3691 deletions(-) delete mode 100644 src/adlmidi_mus2mid.c delete mode 100644 src/adlmidi_mus2mid.h create mode 100644 src/adlmidi_sequencer.cpp delete mode 100644 src/adlmidi_xmi2mid.c delete mode 100644 src/adlmidi_xmi2mid.h create mode 100644 src/cvt_mus2mid.hpp create mode 100644 src/cvt_xmi2mid.hpp create mode 100644 src/midi_sequencer.h create mode 100644 src/midi_sequencer.hpp create mode 100644 src/midi_sequencer_impl.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d75342f..e51b86d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,20 +203,18 @@ list(APPEND libADLMIDI_SOURCES ${libADLMIDI_SOURCE_DIR}/src/wopl/wopl_file.c ) -if(WITH_MUS_SUPPORT AND WITH_MIDI_SEQUENCER) +if(WITH_MIDI_SEQUENCER) list(APPEND libADLMIDI_SOURCES - ${libADLMIDI_SOURCE_DIR}/src/adlmidi_mus2mid.c + ${libADLMIDI_SOURCE_DIR}/src/adlmidi_sequencer.cpp ) -else() - add_definitions(-DADLMIDI_DISABLE_MUS_SUPPORT) endif() -if(WITH_XMI_SUPPORT AND WITH_MIDI_SEQUENCER) - list(APPEND libADLMIDI_SOURCES - ${libADLMIDI_SOURCE_DIR}/src/adlmidi_xmi2mid.c - ) -else() - add_definitions(-DADLMIDI_DISABLE_XMI_SUPPORT) +if(NOT WITH_MUS_SUPPORT OR NOT WITH_MIDI_SEQUENCER) + add_definitions(-DBWMIDI_DISABLE_MUS_SUPPORT) +endif() + +if(NOT WITH_XMI_SUPPORT OR NOT WITH_MIDI_SEQUENCER) + add_definitions(-DBWMIDI_DISABLE_XMI_SUPPORT) endif() if(NOT DJGPP AND NOT MSDOS) diff --git a/README.md b/README.md index 227900d..49e5d61 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,8 @@ You need to make in the any IDE a library project and put into it next files (or include those files into subfolder of your exist project instead if you want to use it statically): ### Useful macros -* `ADLMIDI_DISABLE_XMI_SUPPORT` - Disables XMI to MIDI converter -* `ADLMIDI_DISABLE_MUS_SUPPORT` - Disables MUS to MIDI converter +* `BWMIDI_DISABLE_XMI_SUPPORT` - Disables XMI to MIDI converter +* `BWMIDI_DISABLE_MUS_SUPPORT` - Disables MUS to MIDI converter * `ADLMIDI_DISABLE_MIDI_SEQUENCER` - Completely disables built-in MIDI sequencer. * `ADLMIDI_DISABLE_DOSBOX_EMULATOR` - Disables DosBox 0.74 OPL3 emulator. * `ADLMIDI_DISABLE_NUKED_EMULATOR` - Disables Nuked OPL3 emulator. @@ -108,7 +108,6 @@ You need to make in the any IDE a library project and put into it next files * chips/* - Various OPL3 chip emulators and commonized interface over them * adldata.hh - bank structures definition * adlmidi_private.hpp - header of internal private APIs -* fraction.hpp - Fraction number handling * adldata.cpp - Automatically generated database of FM banks from "fm_banks" directory via "gen_adldata" tool. **Don't build it if you have defined `DISABLE_EMBEDDED_BANKS` macro!** * adlmidi.cpp - code of library @@ -117,16 +116,16 @@ You need to make in the any IDE a library project and put into it next files * adlmidi_opl3.cpp - OPL3 chips manager * adlmidi_private.cpp - some internal functions sources +#### MIDI Sequencer +To remove MIDI Sequecer, define `ADLMIDI_DISABLE_MIDI_SEQUENCER` macro and remove all those files +* adlmidi_sequencer.cpp - MIDI Sequencer related source +* cvt_mus2mid.hpp - MUS2MID converter source (define `BWMIDI_DISABLE_MUS_SUPPORT` macro to remove MUS support) +* cvt_xmi2mid.hpp - XMI2MID converter source (define `BWMIDI_DISABLE_XMI_SUPPORT` macro to remove XMI support) +* fraction.hpp - Fraction number handling (Used by Sequencer only) +* midi_sequencer.h - MIDI Sequencer C bindings +* midi_sequencer.hpp - MIDI Sequencer C++ declaration +* midi_sequencer_impl.hpp - MIDI Sequencer C++ implementation (must be once included into one of CPP together with interfaces initializations) -#### MUS2MIDI converter -To remove MUS support, define `ADLMIDI_DISABLE_MUS_SUPPORT` macro and remove those files: -* adlmidi_mus2mid.h - MUS2MID converter header -* adlmidi_mus2mid.c - MUS2MID converter source - -#### XMI2MIDI converter -To remove XMI support, define `ADLMIDI_DISABLE_XMI_SUPPORT` macro and remove those files: -* adlmidi_xmi2mid.h - XMI2MID converter header -* adlmidi_xmi2mid.c - XMI2MID converter source **Important**: Please use DosBox emulator on mobile devices because it requires small CPU power. Nuked OPL synthesizer is very accurate (compared to real OPL3 chip), but it requires much more power device and is high probability your device will lag and playback will be choppy. diff --git a/libADLMIDI-test.pro b/libADLMIDI-test.pro index 982a9a2..d26a5f5 100644 --- a/libADLMIDI-test.pro +++ b/libADLMIDI-test.pro @@ -27,9 +27,7 @@ HEADERS += \ include/adlmidi.h \ src/adlbank.h \ src/adldata.hh \ - src/adlmidi_mus2mid.h \ src/adlmidi_private.hpp \ - src/adlmidi_xmi2mid.h \ src/chips/dosbox/dbopl.h \ src/chips/dosbox_opl3.h \ src/chips/nuked/nukedopl3_174.h \ @@ -38,6 +36,11 @@ HEADERS += \ src/chips/nuked_opl3_v174.h \ src/chips/opl_chip_base.h \ src/chips/opl_chip_base.tcc \ + src/cvt_mus2mid.hpp \ + src/cvt_xmi2mid.hpp \ + src/midi_sequencer.h \ + src/midi_sequencer.hpp \ + src/midi_sequencer_impl.hpp \ src/fraction.hpp \ src/midiplay/wave_writer.h @@ -47,10 +50,9 @@ SOURCES += \ src/adlmidi.cpp \ src/adlmidi_load.cpp \ src/adlmidi_midiplay.cpp \ - src/adlmidi_mus2mid.c \ src/adlmidi_opl3.cpp \ src/adlmidi_private.cpp \ - src/adlmidi_xmi2mid.c \ + src/adlmidi_sequencer.cpp \ src/wopl/wopl_file.c \ src/chips/dosbox/dbopl.cpp \ src/chips/dosbox_opl3.cpp \ diff --git a/libADLMIDI.pro b/libADLMIDI.pro index 64c9aa1..92c6009 100644 --- a/libADLMIDI.pro +++ b/libADLMIDI.pro @@ -21,9 +21,7 @@ HEADERS += \ include/adlmidi.h \ src/adlbank.h \ src/adldata.hh \ - src/adlmidi_mus2mid.h \ src/adlmidi_private.hpp \ - src/adlmidi_xmi2mid.h \ src/wopl/wopl_file.h \ src/chips/dosbox/dbopl.h \ src/chips/dosbox_opl3.h \ @@ -33,6 +31,11 @@ HEADERS += \ src/chips/nuked_opl3_v174.h \ src/chips/opl_chip_base.h \ src/chips/opl_chip_base.tcc \ + src/cvt_mus2mid.hpp \ + src/cvt_xmi2mid.hpp \ + src/midi_sequencer.h \ + src/midi_sequencer.hpp \ + src/midi_sequencer_impl.hpp \ src/fraction.hpp SOURCES += \ @@ -41,10 +44,9 @@ SOURCES += \ src/adlmidi.cpp \ src/adlmidi_load.cpp \ src/adlmidi_midiplay.cpp \ - src/adlmidi_mus2mid.c \ src/adlmidi_opl3.cpp \ src/adlmidi_private.cpp \ - src/adlmidi_xmi2mid.c \ + src/adlmidi_sequencer.cpp \ src/wopl/wopl_file.c \ src/chips/dosbox/dbopl.cpp \ src/chips/dosbox_opl3.cpp \ diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 9210b5b..69755d3 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -104,12 +104,12 @@ ADLMIDI_EXPORT int adl_getNumChips(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) { - #ifdef DISABLE_EMBEDDED_BANKS +#ifdef DISABLE_EMBEDDED_BANKS ADL_UNUSED(device); ADL_UNUSED(bank); ADLMIDI_ErrorString = "This build of libADLMIDI has no embedded banks. Please load bank by using of adl_openBankFile() or adl_openBankData() functions instead of adl_setBank()"; return -1; - #else +#else const uint32_t NumBanks = static_cast(maxAdlBanks()); int32_t bankno = bank; @@ -130,7 +130,7 @@ ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) play->applySetup(); return adlRefreshNumCards(device); - #endif +#endif } ADLMIDI_EXPORT int adl_getBanksCount() @@ -355,20 +355,30 @@ ADLMIDI_EXPORT void adl_setFullRangeBrightness(struct ADL_MIDIPlayer *device, in { if(!device) return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return; play->m_setup.fullRangeBrightnessCC74 = (fr_brightness != 0); } ADLMIDI_EXPORT void adl_setLoopEnabled(ADL_MIDIPlayer *device, int loopEn) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - play->m_setup.loopingIsEnabled = (loopEn != 0); + if(!play) + return; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + play->m_sequencer.setLoopEnabled(loopEn != 0); +#else + ADL_UNUSED(loopEn); +#endif } /* !!!DEPRECATED!!! */ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int logvol) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); play->m_setup.LogarithmicVolumes = (logvol != 0); if(play->m_setup.LogarithmicVolumes) @@ -379,7 +389,8 @@ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int ADLMIDI_EXPORT void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int volumeModel) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); play->m_setup.VolumeModel = volumeModel; if(play->m_setup.VolumeModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model @@ -444,7 +455,7 @@ ADLMIDI_EXPORT int adl_openFile(ADL_MIDIPlayer *device, const char *filePath) } else return 0; #else - (void)filePath; + ADL_UNUSED(filePath); play->setErrorString("ADLMIDI: MIDI Sequencer is not supported in this build of library!"); return -1; #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -470,7 +481,8 @@ ADLMIDI_EXPORT int adl_openData(ADL_MIDIPlayer *device, const void *mem, unsigne } else return 0; #else - (void)mem;(void)size; + ADL_UNUSED(mem); + ADL_UNUSED(size); play->setErrorString("ADLMIDI: MIDI Sequencer is not supported in this build of library!"); return -1; #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -564,16 +576,7 @@ ADLMIDI_EXPORT const char *adl_errorInfo(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT const char *adl_getMusicTitle(struct ADL_MIDIPlayer *device) { - if(!device) - return ""; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - if(!play) - return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return play->musTitle.c_str(); - #else - return ""; - #endif + return adl_metaMusicTitle(device); } ADLMIDI_EXPORT void adl_close(struct ADL_MIDIPlayer *device) @@ -601,119 +604,159 @@ ADLMIDI_EXPORT double adl_totalTimeLength(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->timeLength(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast(device->adl_midiPlayer)->m_sequencer.timeLength(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_loopStartTime(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->getLoopStart(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast(device->adl_midiPlayer)->m_sequencer.getLoopStart(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_loopEndTime(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->getLoopEnd(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast(device->adl_midiPlayer)->m_sequencer.getLoopEnd(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_positionTell(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->tell(); - #else + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return -1.0; + return play->m_sequencer.tell(); +#else + ADL_UNUSED(device); return -1.0; - #endif +#endif } ADLMIDI_EXPORT void adl_positionSeek(struct ADL_MIDIPlayer *device, double seconds) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + if(seconds < 0.0) + return;//Seeking negative position is forbidden! :-P if(!device) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast(device->adl_midiPlayer)->seek(seconds); - #endif + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return; + play->realTime_panic(); + play->m_setup.delay = play->m_sequencer.seek(seconds, play->m_setup.mindelay); + play->m_setup.carry = 0.0; +#else + ADL_UNUSED(device); + ADL_UNUSED(seconds); +#endif } ADLMIDI_EXPORT void adl_positionRewind(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast(device->adl_midiPlayer)->rewind(); - #endif + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return; + play->realTime_panic(); + play->m_sequencer.rewind(); +#else + ADL_UNUSED(device); +#endif } ADLMIDI_EXPORT void adl_setTempo(struct ADL_MIDIPlayer *device, double tempo) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device || (tempo <= 0.0)) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast(device->adl_midiPlayer)->setTempo(tempo); - #endif + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return; + play->m_sequencer.setTempo(tempo); +#else + ADL_UNUSED(device); + ADL_UNUSED(tempo); +#endif } ADLMIDI_EXPORT const char *adl_metaMusicTitle(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->musTitle.c_str(); - #else + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return ""; + return play->m_sequencer.getMusicTitle().c_str(); +#else + ADL_UNUSED(device); return ""; - #endif +#endif } ADLMIDI_EXPORT const char *adl_metaMusicCopyright(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->musCopyright.c_str(); - #else + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return ""; + return play->m_sequencer.getMusicCopyright().c_str(); +#else + ADL_UNUSED(device); return ""; - #endif +#endif } ADLMIDI_EXPORT size_t adl_metaTrackTitleCount(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->musTrackTitles.size(); + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return 0; + return play->m_sequencer.getTrackTitles().size(); #else + ADL_UNUSED(device); return 0; #endif } ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, size_t index) { - if(!device) - return 0; #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + if(!device) + return ""; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - if(index >= play->musTrackTitles.size()) + const std::vector &titles = play->m_sequencer.getTrackTitles(); + if(index >= titles.size()) return "INVALID"; - return play->musTrackTitles[index].c_str(); + return titles[index].c_str(); #else - (void)device; (void)index; + ADL_UNUSED(device); + ADL_UNUSED(index); return "NOT SUPPORTED"; #endif } @@ -721,11 +764,15 @@ ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, siz ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->musMarkers.size(); + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return 0; + return play->m_sequencer.getMarkers().size(); #else + ADL_UNUSED(device); return 0; #endif } @@ -733,9 +780,10 @@ ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, size_t index) { struct Adl_MarkerEntry marker; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - if(!device || !play || (index >= play->musMarkers.size())) + const std::vector &markers = play->m_sequencer.getMarkers(); + if(!device || !play || (index >= markers.size())) { marker.label = "INVALID"; marker.pos_time = 0.0; @@ -744,27 +792,34 @@ ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, siz } else { - MIDIplay::MIDI_MarkerEntry &mk = play->musMarkers[index]; + const MidiSequencer::MIDI_MarkerEntry &mk = markers[index]; marker.label = mk.label.c_str(); marker.pos_time = mk.pos_time; marker.pos_ticks = (unsigned long)mk.pos_ticks; } - #else - (void)device; (void)index; +#else + ADL_UNUSED(device); + ADL_UNUSED(index); marker.label = "NOT SUPPORTED"; marker.pos_time = 0.0; marker.pos_ticks = 0; - #endif +#endif return marker; } ADLMIDI_EXPORT void adl_setRawEventHook(struct ADL_MIDIPlayer *device, ADL_RawEventHook rawEventHook, void *userData) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - play->hooks.onEvent = rawEventHook; - play->hooks.onEvent_userData = userData; + play->m_sequencerInterface.onEvent = rawEventHook; + play->m_sequencerInterface.onEvent_userData = userData; +#else + ADL_UNUSED(device); + ADL_UNUSED(rawEventHook); + ADL_UNUSED(userData); +#endif } /* Set note hook */ @@ -785,6 +840,10 @@ ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_D MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); play->hooks.onDebugMessage = debugMessageHook; play->hooks.onDebugMessage_userData = userData; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + play->m_sequencerInterface.onDebugMessage = debugMessageHook; + play->m_sequencerInterface.onDebugMessage_userData = userData; +#endif } #ifndef ADLMIDI_HW_OPL @@ -930,15 +989,16 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, ADL_UInt8 *out_left, ADL_UInt8 *out_right, const ADLMIDI_AudioFormat *format) { - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - #ifdef ADLMIDI_HW_OPL - (void)device; - (void)sampleCount; - (void)out_left; - (void)out_right; - (void)format; +#if defined(ADLMIDI_DISABLE_MIDI_SEQUENCER) || defined(ADLMIDI_HW_OPL) + ADL_UNUSED(device); + ADL_UNUSED(sampleCount); + ADL_UNUSED(out_left); + ADL_UNUSED(out_right); + ADL_UNUSED(format); return 0; - #else +#endif + +#if !defined(ADLMIDI_DISABLE_MIDI_SEQUENCER) && !defined(ADLMIDI_HW_OPL) sampleCount -= sampleCount % 2; //Avoid even sample requests if(sampleCount < 0) return 0; @@ -975,7 +1035,7 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, // setup.SkipForward -= 1; //else { - if((player->atEnd) && (setup.delay <= 0.0)) + if((player->m_sequencer.positionAtEnd()) && (setup.delay <= 0.0)) break;//Stop to fetch samples at reaching the song end with disabled loop ssize_t leftSamples = left / 2; @@ -1024,10 +1084,7 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, } return static_cast(gotten_len); - #endif - #else - return 0; - #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER } @@ -1040,14 +1097,14 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC ADL_UInt8 *out_left, ADL_UInt8 *out_right, const ADLMIDI_AudioFormat *format) { - #ifdef ADLMIDI_HW_OPL - (void)device; - (void)sampleCount; - (void)out_left; - (void)out_right; - (void)format; +#ifdef ADLMIDI_HW_OPL + ADL_UNUSED(device); + ADL_UNUSED(sampleCount); + ADL_UNUSED(out_left); + ADL_UNUSED(out_right); + ADL_UNUSED(format); return 0; - #else +#else sampleCount -= sampleCount % 2; //Avoid even sample requests if(sampleCount < 0) return 0; @@ -1101,12 +1158,12 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC gotten_len += (in_generatedPhys) /* - setup.stored_samples*/; } - player->TickIteratos(eat_delay); + player->TickIterators(eat_delay); }//... } return static_cast(gotten_len); - #endif +#endif } ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double seconds, double granuality) @@ -1119,7 +1176,9 @@ ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double secon return -1.0; return player->Tick(seconds, granuality); #else - (void)seconds; (void)granuality; + ADL_UNUSED(device); + ADL_UNUSED(seconds); + ADL_UNUSED(granuality); return -1.0; #endif } @@ -1132,8 +1191,9 @@ ADLMIDI_EXPORT int adl_atEnd(struct ADL_MIDIPlayer *device) MIDIplay *player = reinterpret_cast(device->adl_midiPlayer); if(!player) return 1; - return (int)player->atEnd; + return (int)player->m_sequencer.positionAtEnd(); #else + ADL_UNUSED(device); return 1; #endif } diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 10bea21..3e51b77 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -24,40 +24,6 @@ #include "adlmidi_private.hpp" #include "wopl/wopl_file.h" -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -# ifndef ADLMIDI_DISABLE_MUS_SUPPORT -# include "adlmidi_mus2mid.h" -# endif//MUS -# ifndef ADLMIDI_DISABLE_XMI_SUPPORT -# include "adlmidi_xmi2mid.h" -# endif//XMI -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -uint64_t MIDIplay::ReadBEint(const void *buffer, size_t nbytes) -{ - uint64_t result = 0; - const unsigned char *data = reinterpret_cast(buffer); - - for(unsigned n = 0; n < nbytes; ++n) - result = (result << 8) + data[n]; - - return result; -} - -uint64_t MIDIplay::ReadLEint(const void *buffer, size_t nbytes) -{ - uint64_t result = 0; - const unsigned char *data = reinterpret_cast(buffer); - - for(unsigned n = 0; n < nbytes; ++n) - result = result + static_cast(data[n] << (n * 8)); - - return result; -} - -#endif - bool MIDIplay::LoadBank(const std::string &filename) { FileAndMemReader file; @@ -293,185 +259,44 @@ bool MIDIplay::LoadBank(FileAndMemReader &fr) } #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::LoadMIDI(const std::string &filename) -{ - FileAndMemReader file; - file.openFile(filename.c_str()); - if(!LoadMIDI(file)) - return false; - return true; -} - -bool MIDIplay::LoadMIDI(const void *data, size_t size) -{ - FileAndMemReader file; - file.openData(data, size); - return LoadMIDI(file); -} -bool MIDIplay::LoadMIDI(FileAndMemReader &fr) +bool MIDIplay::LoadMIDI_pre() { - size_t fsize; - ADL_UNUSED(fsize); - //! Temp buffer for conversion - AdlMIDI_CPtr cvt_buf; - errorString.clear(); - - #ifdef DISABLE_EMBEDDED_BANKS +#ifdef DISABLE_EMBEDDED_BANKS if((opl.AdlBank != ~0u) || opl.dynamic_banks.empty()) { errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!"; return false; } - #endif - - if(!fr.isValid()) - { - errorStringOut = "Invalid data stream!\n"; - #ifndef _WIN32 - errorStringOut += std::strerror(errno); - #endif - return false; - } - +#endif /**** Set all properties BEFORE starting of actial file reading! ****/ applySetup(); - atEnd = false; - loopStart = true; - invalidLoop = false; - - bool is_GMF = false; // GMD/MUS files (ScummVM) - //bool is_MUS = false; // MUS/DMX files (Doom) - bool is_IMF = false; // IMF - bool is_CMF = false; // Creative Music format (CMF/CTMF) - bool is_RSXX = false; // RSXX, such as Cartooners - - const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 - char HeaderBuf[HeaderSize] = ""; - size_t DeltaTicks = 192, TrackCount = 1; - -riffskip: - fsize = fr.read(HeaderBuf, 1, HeaderSize); + caugh_missing_instruments.clear(); + caugh_missing_banks_melodic.clear(); + caugh_missing_banks_percussion.clear(); - if(std::memcmp(HeaderBuf, "RIFF", 4) == 0) - { - fr.seek(6l, SEEK_CUR); - goto riffskip; - } + return true; +} - if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0) - { - // GMD/MUS files (ScummVM) - fr.seek(7 - static_cast(HeaderSize), SEEK_CUR); - is_GMF = true; - } - #ifndef ADLMIDI_DISABLE_MUS_SUPPORT - else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0) - { - // MUS/DMX files (Doom) - fr.seek(0, SEEK_END); - size_t mus_len = fr.tell(); - fr.seek(0, SEEK_SET); - uint8_t *mus = (uint8_t *)malloc(mus_len); - if(!mus) - { - errorStringOut = "Out of memory!"; - return false; - } - fr.read(mus, 1, mus_len); - //Close source stream - fr.close(); - - uint8_t *mid = NULL; - uint32_t mid_len = 0; - int m2mret = AdlMidi_mus2midi(mus, static_cast(mus_len), - &mid, &mid_len, 0); - if(mus) free(mus); - if(m2mret < 0) - { - errorStringOut = "Invalid MUS/DMX data format!"; - return false; - } - cvt_buf.reset(mid); - //Open converted MIDI file - fr.openData(mid, static_cast(mid_len)); - //Re-Read header again! - goto riffskip; - } - #endif //ADLMIDI_DISABLE_MUS_SUPPORT - #ifndef ADLMIDI_DISABLE_XMI_SUPPORT - else if(std::memcmp(HeaderBuf, "FORM", 4) == 0) +bool MIDIplay::LoadMIDI_post() +{ + MidiSequencer::FileFormat format = m_sequencer.getFormat(); + if(format == MidiSequencer::Format_CMF) { - if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0) - { - fr.close(); - errorStringOut = fr.fileName() + ": Invalid format\n"; - return false; - } + const std::vector &instruments = m_sequencer.getRawCmfInstruments(); + opl.dynamic_banks.clear();//Clean up old banks - fr.seek(0, SEEK_END); - size_t mus_len = fr.tell(); - fr.seek(0, SEEK_SET); - uint8_t *mus = (uint8_t*)malloc(mus_len); - if(!mus) + uint16_t ins_count = static_cast(instruments.size()); + for(uint16_t i = 0; i < ins_count; ++i) { - errorStringOut = "Out of memory!"; - return false; - } - fr.read(mus, 1, mus_len); - //Close source stream - fr.close(); - - uint8_t *mid = NULL; - uint32_t mid_len = 0; - int m2mret = AdlMidi_xmi2midi(mus, static_cast(mus_len), - &mid, &mid_len, XMIDI_CONVERT_NOCONVERSION); - if(mus) free(mus); - if(m2mret < 0) - { - errorStringOut = "Invalid XMI data format!"; - return false; - } - cvt_buf.reset(mid); - //Open converted MIDI file - fr.openData(mid, static_cast(mid_len)); - //Re-Read header again! - goto riffskip; - } - #endif //ADLMIDI_DISABLE_XMI_SUPPORT - else if(std::memcmp(HeaderBuf, "CTMF", 4) == 0) - { - opl.dynamic_banks.clear(); - // Creative Music Format (CMF). - // When playing CTMF files, use the following commandline: - // adlmidi song8.ctmf -p -v 1 1 0 - // i.e. enable percussion mode, deeper vibrato, and use only 1 card. - is_CMF = true; - //unsigned version = ReadLEint(HeaderBuf+4, 2); - uint64_t ins_start = ReadLEint(HeaderBuf + 6, 2); - uint64_t mus_start = ReadLEint(HeaderBuf + 8, 2); - //unsigned deltas = ReadLEint(HeaderBuf+10, 2); - uint64_t ticks = ReadLEint(HeaderBuf + 12, 2); - // Read title, author, remarks start offsets in file - fr.read(HeaderBuf, 1, 6); - //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; - fr.seek(16, SEEK_CUR); // Skip the channels-in-use table - fr.read(HeaderBuf, 1, 4); - uint64_t ins_count = ReadLEint(HeaderBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); - fr.seek(static_cast(ins_start), SEEK_SET); - - //std::printf("%u instruments\n", ins_count); - for(unsigned i = 0; i < ins_count; ++i) - { - unsigned bank = i / 256; + const uint8_t *InsData = instruments[i].data; + uint16_t bank = i / 256; bank = (bank & 127) + ((bank >> 7) << 8); if(bank > 127 + (127 << 8)) break; bank += (i % 256 < 128) ? 0 : OPL3::PercussionTag; - unsigned char InsData[16]; - fr.read(InsData, 1, 16); /*std::printf("Ins %3u: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", i, InsData[0],InsData[1],InsData[2],InsData[3], InsData[4],InsData[5],InsData[6],InsData[7], InsData[8],InsData[9],InsData[10],InsData[11], InsData[12],InsData[13],InsData[14],InsData[15]);*/ @@ -500,224 +325,63 @@ riffskip: adlins.voice2_fine_tune = 0.0; } - fr.seeku(mus_start, SEEK_SET); - TrackCount = 1; - DeltaTicks = (size_t)ticks; opl.AdlBank = ~0u; // Ignore AdlBank number, use dynamic banks instead //std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo); opl.AdlPercussionMode = true; opl.m_musicMode = OPL3::MODE_CMF; opl.m_volumeScale = OPL3::VOLUME_NATIVE; } - else + else if(format == MidiSequencer::Format_RSXX) { - // Try to identify RSXX format - if(HeaderBuf[0] == 0x7D) - { - fr.seek(0x6D, SEEK_SET); - fr.read(HeaderBuf, 6, 1); - if(std::memcmp(HeaderBuf, "rsxx}u", 6) == 0) - { - is_RSXX = true; - fr.seek(0x7D, SEEK_SET); - TrackCount = 1; - DeltaTicks = 60; - //opl.CartoonersVolumes = true; - opl.m_musicMode = OPL3::MODE_RSXX; - opl.m_volumeScale = OPL3::VOLUME_NATIVE; - } - } - - // Try parsing as an IMF file - if(!is_RSXX) - { - do - { - uint8_t raw[4]; - size_t end = static_cast(HeaderBuf[0]) + 256 * static_cast(HeaderBuf[1]); - - if(!end || (end & 3)) - break; - - size_t backup_pos = fr.tell(); - int64_t sum1 = 0, sum2 = 0; - fr.seek(2, SEEK_SET); - - for(unsigned n = 0; n < 42; ++n) - { - if(fr.read(raw, 1, 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; - } - - fr.seek(static_cast(backup_pos), SEEK_SET); - - if(sum1 > sum2) - { - is_IMF = true; - DeltaTicks = 1; - } - } while(false); - } - - if(!is_IMF && !is_RSXX) - { - if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0) - { - fr.close(); - errorStringOut = fr.fileName() + ": Invalid format, Header signature is unknown!\n"; - return false; - } - - /*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/ - TrackCount = (size_t)ReadBEint(HeaderBuf + 10, 2); - DeltaTicks = (size_t)ReadBEint(HeaderBuf + 12, 2); - } + //opl.CartoonersVolumes = true; + opl.m_musicMode = OPL3::MODE_RSXX; + opl.m_volumeScale = OPL3::VOLUME_NATIVE; } - - TrackData.clear(); - TrackData.resize(TrackCount, std::vector()); - InvDeltaTicks = fraction(1, 1000000l * static_cast(DeltaTicks)); - if(is_CMF || is_RSXX) - Tempo = fraction(1, static_cast(DeltaTicks)); - else - Tempo = fraction(1, static_cast(DeltaTicks) * 2); - static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; - size_t totalGotten = 0; - - for(size_t tk = 0; tk < TrackCount; ++tk) + else if(format == MidiSequencer::Format_IMF) { - // Read track header - size_t TrackLength; - - if(is_IMF) - { - //std::fprintf(stderr, "Reading IMF file...\n"); - size_t end = static_cast(HeaderBuf[0]) + 256 * static_cast(HeaderBuf[1]); - unsigned IMF_tempo = 1428; - static const unsigned char imf_tempo[] = {0x0,//Zero delay! - MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4, - static_cast(IMF_tempo >> 24), - static_cast(IMF_tempo >> 16), - static_cast(IMF_tempo >> 8), - static_cast(IMF_tempo) - }; - TrackData[tk].insert(TrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo)); - TrackData[tk].push_back(0x00); - fr.seek(2, SEEK_SET); - - while(fr.tell() < end && !fr.eof()) - { - uint8_t special_event_buf[5]; - uint8_t raw[4]; - special_event_buf[0] = MidiEvent::T_SPECIAL; - special_event_buf[1] = MidiEvent::ST_RAWOPL; - special_event_buf[2] = 0x02; - if(fr.read(raw, 1, 4) != 4) - break; - special_event_buf[3] = raw[0]; // port index - special_event_buf[4] = raw[1]; // port value - uint32_t delay = static_cast(raw[2]); - delay += 256 * static_cast(raw[3]); - totalGotten += 4; - //if(special_event_buf[3] <= 8) continue; - //fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay); - TrackData[tk].insert(TrackData[tk].end(), special_event_buf, special_event_buf + 5); - //if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) ); - if(delay >> 14) - TrackData[tk].push_back(0x80 | ((delay >> 14) & 0x7F)); - if(delay >> 7) - TrackData[tk].push_back(0x80 | ((delay >> 7) & 0x7F)); - TrackData[tk].push_back(((delay >> 0) & 0x7F)); - } - - TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); - //CurrentPosition.track[tk].delay = 0; - //CurrentPosition.began = true; - //std::fprintf(stderr, "Done reading IMF file\n"); - opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing! - opl.m_musicMode = OPL3::MODE_IMF; - } - else - { - // Take the rest of the file - if(is_GMF || is_CMF || is_RSXX) - { - size_t pos = fr.tell(); - fr.seek(0, SEEK_END); - TrackLength = fr.tell() - pos; - fr.seek(static_cast(pos), SEEK_SET); - } - //else if(is_MUS) // Read TrackLength from file position 4 - //{ - // size_t pos = fr.tell(); - // fr.seek(4, SEEK_SET); - // TrackLength = static_cast(fr.getc()); - // TrackLength += static_cast(fr.getc() << 8); - // fr.seek(static_cast(pos), SEEK_SET); - //} - else - { - fsize = fr.read(HeaderBuf, 1, 8); - if(std::memcmp(HeaderBuf, "MTrk", 4) != 0) - { - fr.close(); - errorStringOut = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; - return false; - } - TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4); - } - - // Read track data - TrackData[tk].resize(TrackLength); - fsize = fr.read(&TrackData[tk][0], 1, TrackLength); - totalGotten += fsize; - - if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag. - TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); - if(is_RSXX)//Finalize raw track data with a zero - TrackData[tk].push_back(0); - - //bool ok = false; - //// Read next event time - //uint64_t tkDelay = ReadVarLenEx(tk, ok); - //if(ok) - // CurrentPosition.track[tk].delay = tkDelay; - //else - //{ - // std::stringstream msg; - // msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")"; - // ADLMIDI_ErrorString = msg.str(); - // return false; - //} - } + //std::fprintf(stderr, "Done reading IMF file\n"); + opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing! + opl.m_musicMode = OPL3::MODE_IMF; } - for(size_t tk = 0; tk < TrackCount; ++tk) - totalGotten += TrackData[tk].size(); + opl.Reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip + //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) + ch.clear(); + ch.resize(opl.NumChannels); + + return true; +} - if(totalGotten == 0) +bool MIDIplay::LoadMIDI(const std::string &filename) +{ + FileAndMemReader file; + file.openFile(filename.c_str()); + if(!LoadMIDI_pre()) + return false; + if(!m_sequencer.loadMIDI(file)) { - errorStringOut = fr.fileName() + ": Empty track data"; + errorStringOut = m_sequencer.getErrorString(); return false; } + if(!LoadMIDI_post()) + return false; + return true; +} - //Build new MIDI events table - if(!buildTrackData()) +bool MIDIplay::LoadMIDI(const void *data, size_t size) +{ + FileAndMemReader file; + file.openData(data, size); + if(!LoadMIDI_pre()) + return false; + if(!m_sequencer.loadMIDI(file)) { - errorStringOut = fr.fileName() + ": MIDI data parsing error has occouped!\n" + errorString; + errorStringOut = m_sequencer.getErrorString(); return false; } - - opl.Reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip - //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) - ch.clear(); - ch.resize(opl.NumChannels); + if(!LoadMIDI_post()) + return false; return true; } -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER + +#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 2f186a6..5b7f0d9 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -115,586 +115,12 @@ void MIDIplay::AdlChannel::AddAge(int64_t ms) } } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - -MIDIplay::MidiEvent::MidiEvent() : - type(T_UNKNOWN), - subtype(T_UNKNOWN), - channel(0), - isValid(1), - absPosition(0) -{} - - -MIDIplay::MidiTrackRow::MidiTrackRow() : - time(0.0), - delay(0), - absPos(0), - timeDelay(0.0) -{} - -void MIDIplay::MidiTrackRow::reset() -{ - time = 0.0; - delay = 0; - absPos = 0; - timeDelay = 0.0; - events.clear(); -} - -void MIDIplay::MidiTrackRow::sortEvents(bool *noteStates) -{ - typedef std::vector EvtArr; - EvtArr metas; - EvtArr noteOffs; - EvtArr controllers; - EvtArr anyOther; - - metas.reserve(events.size()); - noteOffs.reserve(events.size()); - controllers.reserve(events.size()); - anyOther.reserve(events.size()); - - for(size_t i = 0; i < events.size(); i++) - { - if(events[i].type == MidiEvent::T_NOTEOFF) - noteOffs.push_back(events[i]); - else if((events[i].type == MidiEvent::T_CTRLCHANGE) - || (events[i].type == MidiEvent::T_PATCHCHANGE) - || (events[i].type == MidiEvent::T_WHEEL) - || (events[i].type == MidiEvent::T_CHANAFTTOUCH)) - { - controllers.push_back(events[i]); - } - else if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_MARKER)) - metas.push_back(events[i]); - else - 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(noteStates) - { - std::set markAsOn; - for(size_t i = 0; i < anyOther.size(); i++) - { - const MidiEvent e = anyOther[i]; - if(e.type == MidiEvent::T_NOTEON) - { - const size_t note_i = (e.channel * 255) + (e.data[0] & 0x7F); - //Check, was previously note is on or off - bool wasOn = noteStates[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 = (j->channel * 255) + (j->data[0] & 0x7F); - noteStates[note_i] = false; - } - - for(std::set::iterator j = markAsOn.begin(); j != markAsOn.end(); j++) - noteStates[*j] = true; - } - /***********************************************************************************/ - - events.clear(); - events.insert(events.end(), noteOffs.begin(), noteOffs.end()); - events.insert(events.end(), metas.begin(), metas.end()); - events.insert(events.end(), controllers.begin(), controllers.end()); - events.insert(events.end(), anyOther.begin(), anyOther.end()); -} -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::buildTrackData() -{ - fullSongTimeLength = 0.0; - loopStartTime = -1.0; - loopEndTime = -1.0; - musTitle.clear(); - musCopyright.clear(); - musTrackTitles.clear(); - musMarkers.clear(); - caugh_missing_instruments.clear(); - caugh_missing_banks_melodic.clear(); - caugh_missing_banks_percussion.clear(); - trackDataNew.clear(); - const size_t trackCount = TrackData.size(); - trackDataNew.resize(trackCount, MidiTrackQueue()); - - invalidLoop = false; - bool gotLoopStart = false, gotLoopEnd = false, gotLoopEventInThisRow = false; - //! Tick position of loop start tag - uint64_t loopStartTicks = 0; - //! Tick position of loop end tag - uint64_t loopEndTicks = 0; - //! Full length of song in ticks - uint64_t ticksSongLength = 0; - //! Cache for error message strign - char error[150]; - - CurrentPositionNew.track.clear(); - CurrentPositionNew.track.resize(trackCount); - - //! Caches note on/off states. - bool noteStates[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 - std::vector tempos; - - /* - * 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 < trackCount; ++tk) - { - uint64_t abs_position = 0; - int status = 0; - MidiEvent event; - bool ok = false; - uint8_t *end = TrackData[tk].data() + TrackData[tk].size(); - uint8_t *trackPtr = TrackData[tk].data(); - std::memset(noteStates, 0, sizeof(noteStates)); - - //Time delay that follows the first event in the track - { - MidiTrackRow evtPos; - if(opl.m_musicMode == OPL3::MODE_RSXX) - ok = true; - else - evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); - if(!ok) - { - int len = snprintf(error, 150, "buildTrackData: Can't read variable-length value at begin of track %d.\n", (int)tk); - if((len > 0) && (len < 150)) - errorString += 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 - for(uint8_t chan = 0; chan < 16; chan++) - { - MidiEvent event; - event.type = MidiEvent::T_CTRLCHANGE; - event.channel = chan; - event.data.push_back(121); - event.data.push_back(0); - evtPos.events.push_back(event); - } - - evtPos.absPos = abs_position; - abs_position += evtPos.delay; - trackDataNew[tk].push_back(evtPos); - } - - MidiTrackRow evtPos; - do - { - event = parseEvent(&trackPtr, end, status); - if(!event.isValid) - { - int len = snprintf(error, 150, "buildTrackData: Fail to parse event in the track %d.\n", (int)tk); - if((len > 0) && (len < 150)) - errorString += std::string(error, (size_t)len); - return false; - } - - evtPos.events.push_back(event); - if(event.type == MidiEvent::T_SPECIAL) - { - if(event.subtype == MidiEvent::ST_TEMPOCHANGE) - { - event.absPosition = abs_position; - tempos.push_back(event); - } - else if(!invalidLoop && (event.subtype == MidiEvent::ST_LOOPSTART)) - { - /* - * loopStart is invalid when: - * - starts together with loopEnd - * - appears more than one time in same MIDI file - */ - if(gotLoopStart || gotLoopEventInThisRow) - invalidLoop = true; - else - { - gotLoopStart = true; - loopStartTicks = abs_position; - } - //In this row we got loop event, register this! - gotLoopEventInThisRow = true; - } - else if(!invalidLoop && (event.subtype == MidiEvent::ST_LOOPEND)) - { - /* - * loopEnd is invalid when: - * - starts before loopStart - * - starts together with loopStart - * - appars more than one time in same MIDI file - */ - if(gotLoopEnd || gotLoopEventInThisRow) - invalidLoop = true; - else - { - gotLoopEnd = true; - loopEndTicks = abs_position; - } - //In this row we got loop event, register this! - gotLoopEventInThisRow = true; - } - } - - if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event! - { - evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); - if(!ok) - { - /* End of track has been reached! However, there is no EOT event presented */ - event.type = MidiEvent::T_SPECIAL; - event.subtype = MidiEvent::ST_ENDTRACK; - } - } - - if((evtPos.delay > 0) || (event.subtype == MidiEvent::ST_ENDTRACK)) - { - evtPos.absPos = abs_position; - abs_position += evtPos.delay; - evtPos.sortEvents(noteStates); - trackDataNew[tk].push_back(evtPos); - evtPos.reset(); - gotLoopEventInThisRow = false; - } - } - while((trackPtr <= end) && (event.subtype != MidiEvent::ST_ENDTRACK)); - - if(ticksSongLength < abs_position) - ticksSongLength = abs_position; - //Set the chain of events begin - if(trackDataNew[tk].size() > 0) - CurrentPositionNew.track[tk].pos = trackDataNew[tk].begin(); - } - - if(gotLoopStart && !gotLoopEnd) - { - gotLoopEnd = true; - loopEndTicks = ticksSongLength; - } - - //loopStart must be located before loopEnd! - if(loopStartTicks >= loopEndTicks) - invalidLoop = true; - - /********************************************************************************/ - //Calculate time basing on collected tempo events - /********************************************************************************/ - for(size_t tk = 0; tk < trackCount; ++tk) - { - fraction currentTempo = Tempo; - double time = 0.0; - uint64_t abs_position = 0; - size_t tempo_change_index = 0; - MidiTrackQueue &track = trackDataNew[tk]; - if(track.empty()) - continue;//Empty track is useless! - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, "\n============Track %" PRIuPTR "=============\n", tk); - std::fflush(stdout); -#endif - - MidiTrackRow *posPrev = &(*(track.begin()));//First element - for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) - { -#ifdef DEBUG_TIME_CALCULATION - bool tempoChanged = false; -#endif - 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].absPosition <= pos.absPos) - { - //Stop points: begin point and tempo change points are before end point - std::vector points; - fraction t; - TempoChangePoint firstPoint = {posPrev->absPos, currentTempo}; - points.push_back(firstPoint); - - //Collect tempo change points between previous and current events - do - { - TempoChangePoint tempoMarker; - MidiEvent &tempoPoint = tempos[tempo_change_index]; - tempoMarker.absPos = tempoPoint.absPosition; - tempoMarker.tempo = InvDeltaTicks * fraction(ReadBEint(tempoPoint.data.data(), tempoPoint.data.size())); - points.push_back(tempoMarker); - tempo_change_index++; - } - while((tempo_change_index < tempos.size()) && - (tempos[tempo_change_index].absPosition <= pos.absPos)); - - // Re-calculate time delay of previous event - time -= posPrev->timeDelay; - posPrev->timeDelay = 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].absPos - points[i].absPos; - //Time delay between points - t = midDelay * currentTempo; - posPrev->timeDelay += t.value(); - - //Apply next tempo - currentTempo = points[j].tempo; -#ifdef DEBUG_TIME_CALCULATION - tempoChanged = true; -#endif - } - //Then calculate time between last tempo change point and end point - TempoChangePoint tailTempo = points.back(); - uint64_t postDelay = pos.absPos - tailTempo.absPos; - t = postDelay * currentTempo; - posPrev->timeDelay += t.value(); - - //Store Common time delay - posPrev->time = time; - time += posPrev->timeDelay; - } - } - - fraction t = pos.delay * currentTempo; - pos.timeDelay = t.value(); - pos.time = time; - time += pos.timeDelay; - - //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::T_SPECIAL) && (e.subtype == MidiEvent::ST_MARKER)) - { - MIDI_MarkerEntry marker; - marker.label = std::string((char *)e.data.data(), e.data.size()); - marker.pos_ticks = pos.absPos; - marker.pos_time = pos.time; - musMarkers.push_back(marker); - } - } - - //Capture loop points time positions - if(!invalidLoop) - { - // Set loop points times - if(loopStartTicks == pos.absPos) - loopStartTime = pos.time; - else if(loopEndTicks == pos.absPos) - loopEndTime = pos.time; - } - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, "= %10" PRId64 " = %10f%s\n", pos.absPos, pos.time, tempoChanged ? " <----TEMPO CHANGED" : ""); - std::fflush(stdout); -#endif - - abs_position += pos.delay; - posPrev = &pos; - } - - if(time > fullSongTimeLength) - fullSongTimeLength = time; - } - - fullSongTimeLength += postSongWaitDelay; - //Set begin of the music - trackBeginPositionNew = CurrentPositionNew; - //Initial loop position will begin at begin of track until passing of the loop point - LoopBeginPositionNew = CurrentPositionNew; - - /********************************************************************************/ - //Resolve "hell of all times" of too short drum notes: - //move too short percussion note-offs far far away as possible - /********************************************************************************/ -#if 1 //Use this to record WAVEs for comparison before/after implementing of this - if(opl.m_musicMode == OPL3::MODE_MIDI)//Percussion fix is needed for MIDI only, not for IMF/RSXX or CMF - { - //! Minimal real time in seconds -#define DRUM_NOTE_MIN_TIME 0.03 - //! Minimal ticks count -#define DRUM_NOTE_MIN_TICKS 15 - struct NoteState - { - double delay; - uint64_t delayTicks; - bool isOn; - char ___pad[7]; - } drNotes[255]; - uint16_t banks[16]; - - for(size_t tk = 0; tk < trackCount; ++tk) - { - std::memset(drNotes, 0, sizeof(drNotes)); - std::memset(banks, 0, sizeof(banks)); - MidiTrackQueue &track = trackDataNew[tk]; - if(track.empty()) - continue;//Empty track is useless! - - for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) - { - MidiTrackRow &pos = *it; - - for(ssize_t e = 0; e < (ssize_t)pos.events.size(); e++) - { - MidiEvent *et = &pos.events[(size_t)e]; - - /* Set MSB/LSB bank */ - if(et->type == MidiEvent::T_CTRLCHANGE) - { - uint8_t ctrlno = et->data[0]; - uint8_t value = et->data[1]; - switch(ctrlno) - { - case 0: // Set bank msb (GM bank) - banks[et->channel] = uint16_t(uint16_t(value) << 8) | uint16_t(banks[et->channel] & 0x00FF); - break; - case 32: // Set bank lsb (XG bank) - banks[et->channel] = (banks[et->channel] & 0xFF00) | (uint16_t(value) & 0x00FF); - break; - } - continue; - } - - bool percussion = (et->channel == 9) || - banks[et->channel] == 0x7E00 || //XG SFX1/SFX2 channel (16128 signed decimal) - banks[et->channel] == 0x7F00; //XG Percussion channel (16256 signed decimal) - if(!percussion) - continue; - - if(et->type == MidiEvent::T_NOTEON) - { - uint8_t note = et->data[0] & 0x7F; - NoteState &ns = drNotes[note]; - ns.isOn = true; - ns.delay = 0.0; - ns.delayTicks = 0; - } - else if(et->type == MidiEvent::T_NOTEOFF) - { - uint8_t note = et->data[0] & 0x7F; - NoteState &ns = drNotes[note]; - if(ns.isOn) - { - ns.isOn = false; - if(ns.delayTicks < DRUM_NOTE_MIN_TICKS || ns.delay < DRUM_NOTE_MIN_TIME)//If note is too short - { - //Move it into next event position if that possible - for(MidiTrackQueue::iterator itNext = it; - itNext != track.end(); - itNext++) - { - MidiTrackRow &posN = *itNext; - if(ns.delayTicks > DRUM_NOTE_MIN_TICKS && ns.delay > DRUM_NOTE_MIN_TIME) - { - //Put note-off into begin of next event list - posN.events.insert(posN.events.begin(), pos.events[(size_t)e]); - //Renive this event from a current row - pos.events.erase(pos.events.begin() + (int)e); - e--; - break; - } - ns.delay += posN.timeDelay; - ns.delayTicks += posN.delay; - } - } - ns.delay = 0.0; - ns.delayTicks = 0; - } - } - } - - //Append time delays to sustaining notes - for(size_t no = 0; no < 128; no++) - { - NoteState &ns = drNotes[no]; - if(ns.isOn) - { - ns.delay += pos.timeDelay; - ns.delayTicks += pos.delay; - } - } - } - } -#undef DRUM_NOTE_MIN_TIME -#undef DRUM_NOTE_MIN_TICKS - } -#endif - - return true; -} -#endif - - MIDIplay::MIDIplay(unsigned long sampleRate): cmf_percussion_mode(false), m_arpeggioCounter(0) #if defined(ADLMIDI_AUDIO_TICK_HANDLER) , m_audioTickCounter(0) #endif -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - , fullSongTimeLength(0.0), - postSongWaitDelay(1.0), - loopStartTime(-1.0), - loopEndTime(-1.0), - tempoMultiplier(1.0), - atEnd(false), - loopStart(false), - loopEnd(false), - invalidLoop(false) -#endif { devices.clear(); @@ -714,13 +140,15 @@ MIDIplay::MIDIplay(unsigned long sampleRate): m_setup.LogarithmicVolumes = false; m_setup.VolumeModel = ADLMIDI_VolumeModel_AUTO; //m_setup.SkipForward = 0; - m_setup.loopingIsEnabled = false; m_setup.ScaleModulators = -1; m_setup.fullRangeBrightnessCC74 = false; m_setup.delay = 0.0; m_setup.carry = 0.0; m_setup.tick_skip_samples_delay = 0; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + initSequencerInterface(); +#endif applySetup(); ChooseDevice("none"); realTime_ResetState(); @@ -766,79 +194,7 @@ void MIDIplay::applySetup() m_arpeggioCounter = 0; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -uint64_t MIDIplay::ReadVarLen(uint8_t **ptr) -{ - uint64_t result = 0; - for(;;) - { - uint8_t byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; - } - return result; -} - -uint64_t MIDIplay::ReadVarLenEx(uint8_t **ptr, 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; -} - -double MIDIplay::Tick(double s, double granularity) -{ - s *= tempoMultiplier; -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(CurrentPositionNew.began) -#endif - CurrentPositionNew.wait -= s; - CurrentPositionNew.absTimePosition += s; - - int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing - while((CurrentPositionNew.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) - { - //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if(!ProcessEventsNew()) - break; - if(CurrentPositionNew.wait <= 0.0) - antiFreezeCounter--; - } - - if(antiFreezeCounter <= 0) - CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events - with zero delay are been detected */ - - for(uint16_t c = 0; c < opl.NumChannels; ++c) - ch[c].AddAge(static_cast(s * 1000.0)); - - UpdateVibrato(s); - UpdateArpeggio(s); -#if !defined(ADLMIDI_AUDIO_TICK_HANDLER) - UpdateGlide(s); -#endif - - if(CurrentPositionNew.wait < 0.0)//Avoid negative delay value! - return 0.0; - - return CurrentPositionNew.wait; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - -void MIDIplay::TickIteratos(double s) +void MIDIplay::TickIterators(double s) { for(uint16_t c = 0; c < opl.NumChannels; ++c) ch[c].AddAge(static_cast(s * 1000.0)); @@ -849,115 +205,6 @@ void MIDIplay::TickIteratos(double s) #endif } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - -void MIDIplay::seek(double seconds) -{ - if(seconds < 0.0) - return;//Seeking negative position is forbidden! :-P - const double granularity = m_setup.mindelay, - 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 > fullSongTimeLength) - { - rewind(); - return; - } - - bool loopFlagState = m_setup.loopingIsEnabled; - // Turn loop pooints off because it causes wrong position rememberin on a quick seek - m_setup.loopingIsEnabled = 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 - */ - 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 - */ - loopStart = false; - - while((CurrentPositionNew.absTimePosition < seconds) && - (CurrentPositionNew.absTimePosition < fullSongTimeLength)) - { - CurrentPositionNew.wait -= s; - CurrentPositionNew.absTimePosition += s; - int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing - double dstWait = CurrentPositionNew.wait + granualityHalf; - while((CurrentPositionNew.wait <= granualityHalf)/*&& (antiFreezeCounter > 0)*/) - { - //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if(!ProcessEventsNew(true)) - break; - //Avoid freeze because of no waiting increasing in more than 10000 cycles - if(CurrentPositionNew.wait <= dstWait) - antiFreezeCounter--; - else - { - dstWait = CurrentPositionNew.wait + granualityHalf; - antiFreezeCounter = 10000; - } - } - if(antiFreezeCounter <= 0) - CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events - with zero delay are been detected */ - } - - if(CurrentPositionNew.wait < 0.0) - CurrentPositionNew.wait = 0.0; - - m_setup.loopingIsEnabled = loopFlagState; - m_setup.delay = CurrentPositionNew.wait; - m_setup.carry = 0.0; -} - -double MIDIplay::tell() -{ - return CurrentPositionNew.absTimePosition; -} - -double MIDIplay::timeLength() -{ - return fullSongTimeLength; -} - -double MIDIplay::getLoopStart() -{ - return loopStartTime; -} - -double MIDIplay::getLoopEnd() -{ - return loopEndTime; -} - -void MIDIplay::rewind() -{ - Panic(); - KillSustainingNotes(-1, -1); - CurrentPositionNew = trackBeginPositionNew; - atEnd = false; - loopStart = true; - loopEnd = false; - //invalidLoop = false;//No more needed here as this flag is set on load time -} - -void MIDIplay::setTempo(double tempo) -{ - tempoMultiplier = tempo; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - void MIDIplay::realTime_ResetState() { for(size_t ch = 0; ch < Ch.size(); ch++) @@ -1465,6 +712,26 @@ void MIDIplay::realTime_panic() KillSustainingNotes(-1, -1); } +void MIDIplay::realTime_deviceSwitch(size_t track, const char *data, size_t length) +{ + const std::string indata(data, length); + current_device[track] = ChooseDevice(indata); +} + +uint64_t MIDIplay::realTime_currentDevice(size_t track) +{ + return current_device[track]; +} + +void MIDIplay::realTime_rawOPL(uint8_t reg, uint8_t value) +{ + if((reg & 0xF0) == 0xC0) + value |= 0x30; + //std::printf("OPL poke %02X, %02X\n", reg, value); + //std::fflush(stdout); + opl.Poke(0, reg, value); +} + #if defined(ADLMIDI_AUDIO_TICK_HANDLER) void MIDIplay::AudioTick(uint32_t chipId, uint32_t rate) { @@ -1652,17 +919,17 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } /* DEBUG ONLY!!! - static uint32_t max = 0; + static uint32_t max = 0; - if(volume == 0) - max = 0; + if(volume == 0) + max = 0; - if(volume > max) - max = volume; + if(volume > max) + max = volume; - printf("%d\n", max); - fflush(stdout); - */ + printf("%d\n", max); + fflush(stdout); + */ } if(props_mask & Upd_Pitch) @@ -1707,339 +974,6 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::ProcessEventsNew(bool isSeek) -{ - if(CurrentPositionNew.track.size() == 0) - atEnd = true;//No MIDI track data to play - if(atEnd) - return false;//No more events in the queue - - loopEnd = false; - const size_t TrackCount = CurrentPositionNew.track.size(); - const PositionNew RowBeginPosition(CurrentPositionNew); - -#ifdef DEBUG_TIME_CALCULATION - double maxTime = 0.0; -#endif - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; - if((track.status >= 0) && (track.delay <= 0)) - { - //Check is an end of track has been reached - if(track.pos == trackDataNew[tk].end()) - { - track.status = -1; - break; - } - - // Handle event - for(size_t i = 0; i < track.pos->events.size(); i++) - { - const MidiEvent &evt = track.pos->events[i]; -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON)) - CurrentPositionNew.began = true; -#endif - if(isSeek && (evt.type == MidiEvent::T_NOTEON)) - continue; - HandleEvent(tk, evt, track.status); - if(loopEnd) - break;//Stop event handling on catching loopEnd event! - } - -#ifdef DEBUG_TIME_CALCULATION - if(maxTime < track.pos->time) - maxTime = track.pos->time; -#endif - // Read next event time (unless the track just ended) - if(track.status >= 0) - { - track.delay += track.pos->delay; - track.pos++; - } - } - } - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, " \r"); - std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); - std::fflush(stdout); -#endif - - // Find shortest delay from all track - uint64_t shortest = 0; - bool shortest_no = true; - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; - if((track.status >= 0) && (shortest_no || track.delay < shortest)) - { - shortest = track.delay; - shortest_no = false; - } - } - - //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest); - - // Schedule the next playevent to be processed after that delay - for(size_t tk = 0; tk < TrackCount; ++tk) - CurrentPositionNew.track[tk].delay -= shortest; - - fraction t = shortest * Tempo; - -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(CurrentPositionNew.began) -#endif - CurrentPositionNew.wait += t.value(); - - //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); - if(loopStart) - { - LoopBeginPositionNew = RowBeginPosition; - loopStart = false; - } - - if(shortest_no || loopEnd) - { - //Loop if song end or loop end point has reached - loopEnd = false; - shortest = 0; - if(!m_setup.loopingIsEnabled) - { - atEnd = true; //Don't handle events anymore - CurrentPositionNew.wait += postSongWaitDelay;//One second delay until stop playing - return true;//We have caugh end here! - } - CurrentPositionNew = LoopBeginPositionNew; - } - - return true;//Has events in queue -} - -MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t **pptr, uint8_t *end, int &status) -{ - uint8_t *&ptr = *pptr; - MIDIplay::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::T_SPECIAL; - evt.subtype = MidiEvent::ST_ENDTRACK; - return evt; - } - - unsigned char byte = *(ptr++); - bool ok = false; - - if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx - { - uint64_t length = ReadVarLenEx(pptr, end, ok); - if(!ok || (ptr + length > end)) - { - errorString += "parseEvent: Can't read SysEx event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - ptr += (size_t)length; - return evt; - } - - if(byte == MidiEvent::T_SPECIAL) - { - // Special event FF - uint8_t evtype = *(ptr++); - uint64_t length = ReadVarLenEx(pptr, end, ok); - if(!ok || (ptr + length > end)) - { - errorString += "parseEvent: Can't read Special event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - std::string data(length ? (const char *)ptr : 0, (size_t)length); - ptr += (size_t)length; - - evt.type = byte; - evt.subtype = evtype; - evt.data.insert(evt.data.begin(), data.begin(), data.end()); - -#if 0 /* Print all tempo events */ - if(evt.subtype == MidiEvent::ST_TEMPOCHANGE) - { - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Temp Change: %02X%02X%02X", evt.data[0], evt.data[1], evt.data[2]); - } -#endif - - /* 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.subtype == MidiEvent::ST_COPYRIGHT) - { - if(musCopyright.empty()) - { - musCopyright = std::string((const char *)evt.data.data(), evt.data.size()); - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Music copyright: %s", musCopyright.c_str()); - } - else if(hooks.onDebugMessage) - { - std::string str((const char *)evt.data.data(), evt.data.size()); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Extra copyright event: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_SQTRKTITLE) - { - if(musTitle.empty()) - { - musTitle = std::string((const char *)evt.data.data(), evt.data.size()); - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Music title: %s", musTitle.c_str()); - } - else if(hooks.onDebugMessage) - { - //TODO: Store track titles and associate them with each track and make API to retreive them - std::string str((const char *)evt.data.data(), evt.data.size()); - musTrackTitles.push_back(str); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Track title: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_INSTRTITLE) - { - if(hooks.onDebugMessage) - { - std::string str((const char *)evt.data.data(), evt.data.size()); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Instrument: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_MARKER) - { - //To lower - for(size_t i = 0; i < data.size(); i++) - { - if(data[i] <= 'Z' && data[i] >= 'A') - data[i] = data[i] - ('Z' - 'z'); - } - - if(data == "loopstart") - { - //Return a custom Loop Start event instead of Marker - evt.subtype = MidiEvent::ST_LOOPSTART; - evt.data.clear();//Data is not needed - return evt; - } - - if(data == "loopend") - { - //Return a custom Loop End event instead of Marker - evt.subtype = MidiEvent::ST_LOOPEND; - evt.data.clear();//Data is not needed - return evt; - } - } - - if(evtype == MidiEvent::ST_ENDTRACK) - status = -1;//Finalize track - - return evt; - } - - // Any normal event (80..EF) - if(byte < 0x80) - { - byte = static_cast(status | 0x80); - ptr--; - } - - //Sys Com Song Select(Song #) [0-127] - if(byte == MidiEvent::T_SYSCOMSNGSEL) - { - if(ptr + 1 > end) - { - errorString += "parseEvent: Can't read System Command Song Select event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - return evt; - } - - //Sys Com Song Position Pntr [LSB, MSB] - if(byte == MidiEvent::T_SYSCOMSPOSPTR) - { - if(ptr + 2 > end) - { - errorString += "parseEvent: Can't read System Command Position Pointer event - Unexpected end of track data.\n"; - evt.isValid = 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::T_NOTEOFF://2 byte length - case MidiEvent::T_NOTEON: - case MidiEvent::T_NOTETOUCH: - case MidiEvent::T_CTRLCHANGE: - case MidiEvent::T_WHEEL: - if(ptr + 2 > end) - { - errorString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - - /* TODO: Implement conversion of RSXX's note volumes out of synthesizer */ - /*if((opl.m_musicMode == OPL3::MODE_RSXX) && (evType == MidiEvent::T_NOTEON) && (evt.data[1] != 0)) - { - //NOT WORKING YET - evt.type = MidiEvent::T_NOTETOUCH; - } - else */if((evType == MidiEvent::T_NOTEON) && (evt.data[1] == 0)) - { - evt.type = MidiEvent::T_NOTEOFF; // Note ON with zero velocity is Note OFF! - } //111'th loopStart controller (RPG Maker and others) - else if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111)) - { - //Change event type to custom Loop Start event and clear data - evt.type = MidiEvent::T_SPECIAL; - evt.subtype = MidiEvent::ST_LOOPSTART; - evt.data.clear(); - } - - return evt; - case MidiEvent::T_PATCHCHANGE://1 byte length - case MidiEvent::T_CHANAFTTOUCH: - if(ptr + 1 > end) - { - errorString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.data.push_back(*(ptr++)); - return evt; - } - - return evt; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - const std::string &MIDIplay::getErrorString() { return errorStringOut; @@ -2050,165 +984,6 @@ void MIDIplay::setErrorString(const std::string &err) errorStringOut = err; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -void MIDIplay::HandleEvent(size_t tk, const MIDIplay::MidiEvent &evt, int &status) -{ - if(hooks.onEvent) - { - hooks.onEvent(hooks.onEvent_userData, - evt.type, - evt.subtype, - evt.channel, - evt.data.data(), - evt.data.size()); - } - - if(evt.type == MidiEvent::T_SYSEX || evt.type == MidiEvent::T_SYSEX2) // Ignore SysEx - { - //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length ); - //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/); - return; - } - - if(evt.type == MidiEvent::T_SPECIAL) - { - // Special event FF - uint8_t evtype = evt.subtype; - uint64_t length = (uint64_t)evt.data.size(); - std::string data(length ? (const char *)evt.data.data() : 0, (size_t)length); - - if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track - { - status = -1; - return; - } - - if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change - { - Tempo = InvDeltaTicks * fraction(ReadBEint(evt.data.data(), evt.data.size())); - return; - } - - if(evtype == MidiEvent::ST_MARKER)//Meta event - { - //Do nothing! :-P - return; - } - - if(evtype == MidiEvent::ST_DEVICESWITCH) - { - current_device[tk] = ChooseDevice(data); - return; - } - - //if(evtype >= 1 && evtype <= 6) - // UI.PrintLn("Meta %d: %s", evtype, data.c_str()); - - //Turn on Loop handling when loop is enabled - if(m_setup.loopingIsEnabled && !invalidLoop) - { - if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - loopStart = true; - return; - } - - if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - loopEnd = true; - return; - } - } - - if(evtype == MidiEvent::ST_RAWOPL) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - uint8_t i = static_cast(data[0]), v = static_cast(data[1]); - if((i & 0xF0) == 0xC0) - v |= 0x30; - //std::printf("OPL poke %02X, %02X\n", i, v); - //std::fflush(stdout); - opl.Poke(0, i, v); - return; - } - - return; - } - - // Any normal event (80..EF) - // if(evt.type < 0x80) - // { - // byte = static_cast(CurrentPosition.track[tk].status | 0x80); - // CurrentPosition.track[tk].ptr--; - // } - - if(evt.type == MidiEvent::T_SYSCOMSNGSEL || - evt.type == MidiEvent::T_SYSCOMSPOSPTR) - return; - - /*UI.PrintLn("@%X Track %u: %02X %02X", - CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte, - TrackData[tk][CurrentPosition.track[tk].ptr]);*/ - uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4; - midCh += (uint8_t)current_device[tk]; - status = evt.type; - - switch(evt.type) - { - case MidiEvent::T_NOTEOFF: // Note off - { - uint8_t note = evt.data[0]; - realTime_NoteOff(midCh, note); - break; - } - - case MidiEvent::T_NOTEON: // Note on - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - /*if(*/ realTime_NoteOn(midCh, note, vol); /*)*/ - //CurrentPosition.began = true; - break; - } - - case MidiEvent::T_NOTETOUCH: // Note touch - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - realTime_NoteAfterTouch(midCh, note, vol); - break; - } - - case MidiEvent::T_CTRLCHANGE: // Controller change - { - uint8_t ctrlno = evt.data[0]; - uint8_t value = evt.data[1]; - realTime_Controller(midCh, ctrlno, value); - break; - } - - case MidiEvent::T_PATCHCHANGE: // Patch change - realTime_PatchChange(midCh, evt.data[0]); - break; - - case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch - { - // TODO: Verify, is this correct action? - uint8_t vol = evt.data[0]; - realTime_ChannelAfterTouch(midCh, vol); - break; - } - - case MidiEvent::T_WHEEL: // Wheel/pitch bend - { - uint8_t a = evt.data[0]; - uint8_t b = evt.data[1]; - realTime_PitchBend(midCh, b, a); - break; - } - } -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t) const { int64_t s = -ch[c].koff_time_until_neglible; @@ -2508,9 +1283,6 @@ void MIDIplay::UpdateVibrato(double amount) } } - - - uint64_t MIDIplay::ChooseDevice(const std::string &name) { std::map::iterator i = devices.find(name); diff --git a/src/adlmidi_mus2mid.c b/src/adlmidi_mus2mid.c deleted file mode 100644 index 3f3e1b8..0000000 --- a/src/adlmidi_mus2mid.c +++ /dev/null @@ -1,451 +0,0 @@ -/* - * MUS2MIDI: MUS to MIDI Library - * - * Copyright (C) 2014 Bret Curtis - * Copyright (C) WildMIDI Developers 2015-2016 - * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include -#include -#include -#include "adlmidi_mus2mid.h" - -#define FREQUENCY 140 /* default Hz or BPM */ - -#if 0 /* older units: */ -#define TEMPO 0x001aa309 /* MPQN: 60000000 / 34.37Hz = 1745673 */ -#define DIVISION 0x0059 /* 89 -- used by many mus2midi converters */ -#endif - -#define TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ - /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ - -#define DIVISION 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) - * - */ - -#define MUSEVENT_KEYOFF 0 -#define MUSEVENT_KEYON 1 -#define MUSEVENT_PITCHWHEEL 2 -#define MUSEVENT_CHANNELMODE 3 -#define MUSEVENT_CONTROLLERCHANGE 4 -#define MUSEVENT_END 6 - -#define MIDI_MAXCHANNELS 16 - -static char MUS_ID[] = { 'M', 'U', 'S', 0x1A }; - -static uint8_t midimap[] = -{/* 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 */ -}; - -typedef 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; -} MUSHeader ; -#define MUS_HEADERSIZE 14 - -typedef struct MidiHeaderChunk { - char name[4]; - int32_t length; - int16_t format; /* make 0 */ - int16_t ntracks;/* make 1 */ - int16_t division; /* 0xe250 ?? */ -} MidiHeaderChunk; -#define MIDI_HEADERSIZE 14 - -typedef struct MidiTrackChunk { - char name[4]; - int32_t length; -} MidiTrackChunk; -#define TRK_CHUNKSIZE 8 - -struct mus_ctx { - uint8_t *src, *src_ptr; - uint32_t srcsize; - uint32_t datastart; - uint8_t *dst, *dst_ptr; - uint32_t dstsize, dstrem; -}; - -#define DST_CHUNK 8192 -static void resize_dst(struct mus_ctx *ctx) { - uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - ctx->dst = realloc(ctx->dst, ctx->dstsize + DST_CHUNK); - ctx->dstsize += DST_CHUNK; - ctx->dstrem += DST_CHUNK; - ctx->dst_ptr = ctx->dst + pos; -} - -static void write1(struct mus_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 1) - resize_dst(ctx); - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem--; -} - -static void write2(struct mus_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 2) - resize_dst(ctx); - *ctx->dst_ptr++ = (val>>8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 2; -} - -static void write4(struct mus_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 4) - resize_dst(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 seekdst(struct mus_ctx *ctx, uint32_t pos) { - ctx->dst_ptr = ctx->dst + pos; - while (ctx->dstsize < pos) - resize_dst(ctx); - ctx->dstrem = ctx->dstsize - pos; -} - -static void skipdst(struct mus_ctx *ctx, int32_t pos) { - size_t newpos; - ctx->dst_ptr += pos; - newpos = ctx->dst_ptr - ctx->dst; - while (ctx->dstsize < newpos) - resize_dst(ctx); - ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); -} - -static uint32_t getdstpos(struct mus_ctx *ctx) { - return (uint32_t)(ctx->dst_ptr - ctx->dst); -} - -/* writes a variable length integer to a buffer, and returns bytes written */ -static int32_t writevarlen(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 READ_INT16(b) ((b)[0] | ((b)[1] << 8)) -#define READ_INT32(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24)) - -int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint16_t frequency) { - struct mus_ctx 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[MIDI_MAXCHANNELS]; - int channelMap[MIDI_MAXCHANNELS], currentChannel; - - if (insize < MUS_HEADERSIZE) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ - return (-1); - } - - if (!frequency) - frequency = FREQUENCY; - - /* read the MUS header and set our location */ - memcpy(header.ID, in, 4); - header.scoreLen = READ_INT16(&in[4]); - header.scoreStart = READ_INT16(&in[6]); - header.channels = READ_INT16(&in[8]); - header.sec_channels = READ_INT16(&in[10]); - header.instrCnt = READ_INT16(&in[12]); - - if (memcmp(header.ID, MUS_ID, 4)) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, NULL, 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 > MIDI_MAXCHANNELS - 1) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, NULL, 0);*/ - return (-1); - } - - memset(&ctx, 0, sizeof(struct mus_ctx)); - ctx.src = ctx.src_ptr = in; - ctx.srcsize = insize; - - ctx.dst = calloc(DST_CHUNK, sizeof(uint8_t)); - ctx.dst_ptr = ctx.dst; - ctx.dstsize = DST_CHUNK; - ctx.dstrem = DST_CHUNK; - - /* Map channel 15 to 9 (percussions) */ - for (temp = 0; temp < MIDI_MAXCHANNELS; ++temp) { - channelMap[temp] = -1; - channel_volume[temp] = 0x40; - } - channelMap[15] = 9; - - /* Header is 14 bytes long and add the rest as well */ - write1(&ctx, 'M'); - write1(&ctx, 'T'); - write1(&ctx, 'h'); - write1(&ctx, 'd'); - write4(&ctx, 6); /* length of header */ - write2(&ctx, 0); /* MIDI type (always 0) */ - write2(&ctx, 1); /* MUS files only have 1 track */ - write2(&ctx, DIVISION); /* division */ - - /* Write out track header and track length position for later */ - begin_track_pos = getdstpos(&ctx); - write1(&ctx, 'M'); - write1(&ctx, 'T'); - write1(&ctx, 'r'); - write1(&ctx, 'k'); - track_size_pos = getdstpos(&ctx); - skipdst(&ctx, 4); - - /* write tempo: microseconds per quarter note */ - write1(&ctx, 0x00); /* delta time */ - write1(&ctx, 0xff); /* sys command */ - write2(&ctx, 0x5103); /* command - set tempo */ - write1(&ctx, TEMPO & 0x000000ff); - write1(&ctx, (TEMPO & 0x0000ff00) >> 8); - write1(&ctx, (TEMPO & 0x00ff0000) >> 16); - - /* Percussions channel starts out at full volume */ - write1(&ctx, 0x00); - write1(&ctx, 0xB9); - write1(&ctx, 0x07); - write1(&ctx, 127); - - /* 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 += writevarlen(delta_time, out_local); - - /* set all channels to 127 (max) volume */ - if (channelMap[channel] < 0) { - *out_local++ = 0xB0 + currentChannel; - *out_local++ = 0x07; - *out_local++ = 127; - *out_local++ = 0x00; - channelMap[channel] = currentChannel++; - if (currentChannel == 9) - ++currentChannel; - } - status = channelMap[channel]; - - /* handle events */ - switch ((event & 122) >> 4){ - case MUSEVENT_KEYOFF: - status |= 0x80; - bit1 = *cur++; - bit2 = 0x40; - break; - case MUSEVENT_KEYON: - status |= 0x90; - bit1 = *cur & 127; - if (*cur++ & 128) /* volume bit? */ - channel_volume[channelMap[channel]] = *cur++; - bit2 = channel_volume[channelMap[channel]]; - break; - case MUSEVENT_PITCHWHEEL: - status |= 0xE0; - bit1 = (*cur & 1) >> 6; - bit2 = (*cur++ >> 1) & 127; - break; - case MUSEVENT_CHANNELMODE: - status |= 0xB0; - if (*cur >= sizeof(midimap) / sizeof(midimap[0])) { - /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", - __FUNCTION__, __LINE__, *cur);*/ - goto _end; - } - bit1 = midimap[*cur++]; - bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; - break; - case MUSEVENT_CONTROLLERCHANGE: - if (*cur == 0) { - cur++; - status |= 0xC0; - bit1 = *cur++; - bit2 = 0;/* silence bogus warnings */ - bitc = 1; - } else { - status |= 0xB0; - if (*cur >= sizeof(midimap) / sizeof(midimap[0])) { - /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", - __FUNCTION__, __LINE__, *cur);*/ - goto _end; - } - bit1 = midimap[*cur++]; - bit2 = *cur++; - } - break; - case MUSEVENT_END: /* 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)) - resize_dst(&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 = getdstpos(&ctx); - seekdst(&ctx, track_size_pos); - write4(&ctx, current_pos - begin_track_pos - TRK_CHUNKSIZE); - seekdst(&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 = NULL; - *outsize = 0; - } - - return (ret); -} - diff --git a/src/adlmidi_mus2mid.h b/src/adlmidi_mus2mid.h deleted file mode 100644 index cc41b87..0000000 --- a/src/adlmidi_mus2mid.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * MUS2MIDI: DMX (DOOM) MUS to MIDI Library Header - * - * Copyright (C) 2014-2016 Bret Curtis - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef MUSLIB_H -#define MUSLIB_H - -#include - -#ifdef __DJGPP__ -typedef signed char int8_t; -typedef unsigned char uint8_t; -typedef signed short int16_t; -typedef unsigned short uint16_t; -typedef signed long int32_t; -typedef unsigned long uint32_t; -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint16_t frequency); - -#ifdef __cplusplus -} -#endif - -#endif /* MUSLIB_H */ diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 13160b2..446bae0 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -132,7 +132,14 @@ typedef int32_t ssize_t; #endif #include "file_reader.hpp" -#include "fraction.hpp" + +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER +// Rename class to avoid ABI conflicts +#define BW_MidiSequencer AdlMidiSequencer +#include "midi_sequencer.hpp" +typedef BW_MidiSequencer MidiSequencer; +#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER + #ifndef ADLMIDI_HW_OPL #include "chips/opl_chip_base.h" #endif @@ -307,17 +314,11 @@ public: struct MIDIEventHooks { MIDIEventHooks() : - onEvent(NULL), - onEvent_userData(NULL), onNote(NULL), onNote_userData(NULL), onDebugMessage(NULL), onDebugMessage_userData(NULL) {} - //! 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); - RawEventHook onEvent; - void *onEvent_userData; //! Note on/off hooks typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend); @@ -669,130 +670,20 @@ public: #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER /** - * @brief MIDI Event utility container + * @brief MIDI files player sequencer */ - class MidiEvent - { - public: - MidiEvent(); - - enum Types - { - T_UNKNOWN = 0x00, - T_NOTEOFF = 0x08,//size == 2 - T_NOTEON = 0x09,//size == 2 - T_NOTETOUCH = 0x0A,//size == 2 - T_CTRLCHANGE = 0x0B,//size == 2 - T_PATCHCHANGE = 0x0C,//size == 1 - T_CHANAFTTOUCH = 0x0D,//size == 1 - T_WHEEL = 0x0E,//size == 2 - - T_SYSEX = 0xF0,//size == len - T_SYSCOMSPOSPTR = 0xF2,//size == 2 - T_SYSCOMSNGSEL = 0xF3,//size == 1 - T_SYSEX2 = 0xF7,//size == len - T_SPECIAL = 0xFF - }; - enum SubTypes - { - ST_SEQNUMBER = 0x00,//size == 2 - ST_TEXT = 0x01,//size == len - ST_COPYRIGHT = 0x02,//size == len - ST_SQTRKTITLE = 0x03,//size == len - ST_INSTRTITLE = 0x04,//size == len - ST_LYRICS = 0x05,//size == len - ST_MARKER = 0x06,//size == len - ST_CUEPOINT = 0x07,//size == len - ST_DEVICESWITCH = 0x09,//size == len - ST_MIDICHPREFIX = 0x20,//size == 1 - - ST_ENDTRACK = 0x2F,//size == 0 - ST_TEMPOCHANGE = 0x51,//size == 3 - ST_SMPTEOFFSET = 0x54,//size == 5 - ST_TIMESIGNATURE = 0x55, //size == 4 - ST_KEYSIGNATURE = 0x59,//size == 2 - ST_SEQUENCERSPEC = 0x7F, //size == len - - /* Non-standard, internal ADLMIDI usage only */ - ST_LOOPSTART = 0xE1,//size == 0 - ST_LOOPEND = 0xE2,//size == 0 - ST_RAWOPL = 0xE3//size == 0 - }; - //! Main type of event - uint8_t type; - //! Sub-type of the event - uint8_t subtype; - //! Targeted MIDI channel - uint8_t channel; - //! Is valid event - uint8_t isValid; - //! Reserved 5 bytes padding - uint8_t __padding[4]; - //! Absolute tick position (Used for the tempo calculation only) - uint64_t absPosition; - //! Raw data of this event - std::vector data; - }; + MidiSequencer m_sequencer; /** - * @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) + * @brief Interface between MIDI sequencer and this library */ - class MidiTrackRow - { - public: - MidiTrackRow(); - void reset(); - //! Absolute time position in seconds - double time; - //! Delay to next event in ticks - uint64_t delay; - //! Absolute position in ticks - uint64_t absPos; - //! Delay to next event in seconds - double timeDelay; - std::vector events; - /** - * @brief Sort events in this position - */ - void sortEvents(bool *noteStates = NULL); - }; + BW_MidiRtInterface m_sequencerInterface; /** - * @brief Tempo change point entry. Used in the MIDI data building function only. + * @brief Initialize MIDI sequencer interface */ - struct TempoChangePoint - { - uint64_t absPos; - fraction tempo; - }; - //P.S. I declared it here instead of local in-function because C++99 can't process templates with locally-declared structures - - typedef std::list MidiTrackQueue; - - // Information about each track - struct PositionNew - { - bool began; - char padding[7]; - double wait; - double absTimePosition; - struct TrackInfo - { - size_t ptr; - uint64_t delay; - int status; - char padding2[4]; - MidiTrackQueue::iterator pos; - TrackInfo(): ptr(0), delay(0), status(0) {} - }; - std::vector track; - PositionNew(): began(false), wait(0.0), absTimePosition(0.0), track() - {} - }; -#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER + void initSequencerInterface(); +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER struct Setup { @@ -807,7 +698,6 @@ public: bool LogarithmicVolumes; int VolumeModel; //unsigned int SkipForward; - bool loopingIsEnabled; int ScaleModulators; bool fullRangeBrightnessCC74; @@ -854,31 +744,9 @@ private: uint32_t m_audioTickCounter; #endif -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - std::vector > TrackData; - - PositionNew CurrentPositionNew, LoopBeginPositionNew, trackBeginPositionNew; - - //! Full song length in seconds - double fullSongTimeLength; - //! Delay after song playd before rejecting the output stream requests - double postSongWaitDelay; - - //! Loop start time - double loopStartTime; - //! Loop end time - double loopEndTime; -#endif - //! Local error string - std::string errorString; //! Local error string std::string errorStringOut; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - //! Pre-processed track data storage - std::vector trackDataNew; -#endif - //! Missing instruments catches std::set caugh_missing_instruments; //! Missing melodic banks catches @@ -886,90 +754,49 @@ private: //! Missing percussion banks catches std::set caugh_missing_banks_percussion; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - /** - * @brief Build MIDI track data from the raw track data storage - * @return true if everything successfully processed, or false on any error - */ - bool buildTrackData(); - - /** - * @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(uint8_t **ptr, uint8_t *end, int &status); -#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER - public: const std::string &getErrorString(); void setErrorString(const std::string &err); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - std::string musTitle; - std::string musCopyright; - std::vector musTrackTitles; - std::vector musMarkers; - - fraction InvDeltaTicks, Tempo; - //! Tempo multiplier - double tempoMultiplier; - bool atEnd, - loopStart, - loopEnd, - invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/ - char ____padding2[2]; -#endif OPL3 opl; int32_t outBuf[1024]; Setup m_setup; + bool LoadBank(const std::string &filename); + bool LoadBank(const void *data, size_t size); + bool LoadBank(FileAndMemReader &fr); + #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER /** - * @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 + * @brief MIDI file loading pre-process + * @return true on success, false on failure */ - static uint64_t ReadBEint(const void *buffer, size_t nbytes); + bool LoadMIDI_pre(); /** - * @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 + * @brief MIDI file loading post-process + * @return true on success, false on failure */ - static uint64_t ReadLEint(const void *buffer, size_t nbytes); + bool LoadMIDI_post(); /** - * @brief Standard MIDI Variable-Length numeric value parser without of validation - * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value - * @return Unsigned integer that conains parsed variable-length value - */ - uint64_t ReadVarLen(uint8_t **ptr); - /** - * @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 + * @brief Load music file from a file + * @param filename Path to music file + * @return true on success, false on failure */ - uint64_t ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok); -#endif - - bool LoadBank(const std::string &filename); - bool LoadBank(const void *data, size_t size); - bool LoadBank(FileAndMemReader &fr); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER bool LoadMIDI(const std::string &filename); + + /** + * @brief Load music file from the memory block + * @param data pointer to the memory block + * @param size size of memory block + * @return true on success, false on failure + */ bool LoadMIDI(const void *data, size_t size); - bool LoadMIDI(FileAndMemReader &fr); /** * @brief Periodic tick handler. @@ -978,79 +805,129 @@ public: * @return desired number of seconds until next call */ double Tick(double s, double granularity); -#endif +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER /** * @brief Process extra iterators like vibrato or arpeggio * @param s seconds since last call */ - void TickIteratos(double s); + void TickIterators(double s); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - /** - * @brief Change current position to specified time position in seconds - * @param seconds Absolute time position in seconds - */ - void seek(double seconds); + /* RealTime event triggers */ /** - * @brief Gives current time position in seconds - * @return Current time position in seconds + * @brief Reset state of all channels */ - double tell(); + void realTime_ResetState(); /** - * @brief Gives time length of current song in seconds - * @return Time length of current song in seconds + * @brief Note On event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) + * @param velocity Velocity level (from 0 to 127) + * @return true if Note On event was accepted */ - double timeLength(); + bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity); /** - * @brief Gives loop start time position in seconds - * @return Loop start time position in seconds or -1 if song has no loop points + * @brief Note Off event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) */ - double getLoopStart(); + void realTime_NoteOff(uint8_t channel, uint8_t note); /** - * @brief Gives loop end time position in seconds - * @return Loop end time position in seconds or -1 if song has no loop points + * @brief Note aftertouch event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) + * @param atVal After-Touch level (from 0 to 127) */ - double getLoopEnd(); + void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal); /** - * @brief Return to begin of current song + * @brief Channel aftertouch event + * @param channel MIDI channel + * @param atVal After-Touch level (from 0 to 127) */ - void rewind(); + void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal); /** - * @brief Set tempo multiplier - * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower + * @brief Controller Change event + * @param channel MIDI channel + * @param type Type of controller + * @param value Value of the controller (from 0 to 127) */ - void setTempo(double tempo); -#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER - - /* RealTime event triggers */ - void realTime_ResetState(); - - bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity); - void realTime_NoteOff(uint8_t channel, uint8_t note); - - void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal); - void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal); - void realTime_Controller(uint8_t channel, uint8_t type, uint8_t value); + /** + * @brief Patch change + * @param channel MIDI channel + * @param patch Patch Number (from 0 to 127) + */ void realTime_PatchChange(uint8_t channel, uint8_t patch); + /** + * @brief Pitch bend change + * @param channel MIDI channel + * @param pitch Concoctated raw pitch value + */ void realTime_PitchBend(uint8_t channel, uint16_t pitch); + + /** + * @brief Pitch bend change + * @param channel MIDI channel + * @param msb MSB of pitch value + * @param lsb LSB of pitch value + */ void realTime_PitchBend(uint8_t channel, uint8_t msb, uint8_t lsb); + /** + * @brief LSB Bank Change CC + * @param channel MIDI channel + * @param lsb LSB value of bank number + */ void realTime_BankChangeLSB(uint8_t channel, uint8_t lsb); + + /** + * @brief MSB Bank Change CC + * @param channel MIDI channel + * @param lsb MSB value of bank number + */ void realTime_BankChangeMSB(uint8_t channel, uint8_t msb); + + /** + * @brief Bank Change (united value) + * @param channel MIDI channel + * @param bank Bank number value + */ void realTime_BankChange(uint8_t channel, uint16_t bank); + /** + * @brief Turn off all notes and mute the sound of releasing notes + */ void realTime_panic(); + /** + * @brief Device switch (to extend 16-channels limit of MIDI standard) + * @param track MIDI track index + * @param data Device name + * @param length Length of device name string + */ + void realTime_deviceSwitch(size_t track, const char *data, size_t length); + /** + * @brief Currently selected device index + * @param track MIDI track index + * @return Multiple 16 value + */ + uint64_t realTime_currentDevice(size_t track); + + /** + * @brief Send raw OPL chip command + * @param reg OPL Register + * @param value Value to write + */ + void realTime_rawOPL(uint8_t reg, uint8_t value); + #if defined(ADLMIDI_AUDIO_TICK_HANDLER) // Audio rate tick handler void AudioTick(uint32_t chipId, uint32_t rate); @@ -1074,11 +951,6 @@ private: unsigned props_mask, int32_t select_adlchn = -1); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - bool ProcessEventsNew(bool isSeek = false); - void HandleEvent(size_t tk, const MidiEvent &evt, int &status); -#endif - // Determine how good a candidate this adlchannel // would be for playing a note from this instrument. int64_t CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t /*MidCh*/) const; diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp new file mode 100644 index 0000000..8bc3569 --- /dev/null +++ b/src/adlmidi_sequencer.cpp @@ -0,0 +1,153 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma + * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov + * + * Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation: + * http://iki.fi/bisqwit/source/adlmidi.html + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + +#include "adlmidi_private.hpp" + +// Inlucde MIDI sequencer class implementation +#include "midi_sequencer_impl.hpp" + + +/**************************************************** + * Real-Time MIDI calls proxies * + ****************************************************/ + +static void rtNoteOn(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_NoteOn(channel, note, velocity); +} + +static void rtNoteOff(void *userdata, uint8_t channel, uint8_t note) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_NoteOff(channel, note); +} + +static void rtNoteAfterTouch(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_NoteAfterTouch(channel, note, atVal); +} + +static void rtChannelAfterTouch(void *userdata, uint8_t channel, uint8_t atVal) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_ChannelAfterTouch(channel, atVal); +} + +static void rtControllerChange(void *userdata, uint8_t channel, uint8_t type, uint8_t value) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_Controller(channel, type, value); +} + +static void rtPatchChange(void *userdata, uint8_t channel, uint8_t patch) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_PatchChange(channel, patch); +} + +static void rtPitchBend(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_PitchBend(channel, msb, lsb); +} + +static void rtSysEx(void *userdata, const uint8_t *msg, size_t size) +{ + MIDIplay *context = reinterpret_cast(userdata); + ADL_UNUSED(context); + ADL_UNUSED(msg); + ADL_UNUSED(size); + /* TODO: pass SysEx HERE! */ +} + + +/* NonStandard calls */ +static void rtRawOPL(void *userdata, uint8_t reg, uint8_t value) +{ + MIDIplay *context = reinterpret_cast(userdata); + return context->realTime_rawOPL(reg, value); +} + +static void rtDeviceSwitch(void *userdata, size_t track, const char *data, size_t length) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_deviceSwitch(track, data, length); +} + +static uint64_t rtCurrentDevice(void *userdata, size_t track) +{ + MIDIplay *context = reinterpret_cast(userdata); + return context->realTime_currentDevice(track); +} +/* NonStandard calls End */ + + +void MIDIplay::initSequencerInterface() +{ + std::memset(&m_sequencerInterface, 0, sizeof(BW_MidiRtInterface)); + + m_sequencerInterface.onDebugMessage = hooks.onDebugMessage; + m_sequencerInterface.onDebugMessage_userData = hooks.onDebugMessage_userData; + + /* MIDI Real-Time calls */ + m_sequencerInterface.rtUserData = this; + m_sequencerInterface.rt_noteOn = rtNoteOn; + m_sequencerInterface.rt_noteOff = rtNoteOff; + m_sequencerInterface.rt_noteAfterTouch = rtNoteAfterTouch; + m_sequencerInterface.rt_channelAfterTouch = rtChannelAfterTouch; + m_sequencerInterface.rt_controllerChange = rtControllerChange; + m_sequencerInterface.rt_patchChange = rtPatchChange; + m_sequencerInterface.rt_pitchBend = rtPitchBend; + m_sequencerInterface.rt_systemExclusive = rtSysEx; + + /* NonStandard calls */ + m_sequencerInterface.rt_rawOPL = rtRawOPL; + m_sequencerInterface.rt_deviceSwitch = rtDeviceSwitch; + m_sequencerInterface.rt_currentDevice = rtCurrentDevice; + /* NonStandard calls End */ + + m_sequencer.setInterface(&m_sequencerInterface); +} + +double MIDIplay::Tick(double s, double granularity) +{ + double ret = m_sequencer.Tick(s, granularity); + + s *= m_sequencer.getTempoMultiplier(); + for(uint16_t c = 0; c < opl.NumChannels; ++c) + ch[c].AddAge(static_cast(s * 1000.0)); + + UpdateVibrato(s); + UpdateArpeggio(s); +#if !defined(ADLMIDI_AUDIO_TICK_HANDLER) + UpdateGlide(s); +#endif + + return ret; +} + +#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ diff --git a/src/adlmidi_xmi2mid.c b/src/adlmidi_xmi2mid.c deleted file mode 100644 index 3389709..0000000 --- a/src/adlmidi_xmi2mid.c +++ /dev/null @@ -1,1106 +0,0 @@ -/* - * XMIDI: Miles XMIDI to MID Library - * - * Copyright (C) 2001 Ryan Nunn - * Copyright (C) 2014 Bret Curtis - * Copyright (C) WildMIDI Developers 2015-2016 - * Copyright (c) 2015-2018 Vitaly Novichkov - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -/* XMIDI Converter */ - -#include -#include -#include -#include - -#include "adlmidi_xmi2mid.h" - -/* Midi Status Bytes */ -#define MIDI_STATUS_NOTE_OFF 0x8 -#define MIDI_STATUS_NOTE_ON 0x9 -#define MIDI_STATUS_AFTERTOUCH 0xA -#define MIDI_STATUS_CONTROLLER 0xB -#define MIDI_STATUS_PROG_CHANGE 0xC -#define MIDI_STATUS_PRESSURE 0xD -#define MIDI_STATUS_PITCH_WHEEL 0xE -#define MIDI_STATUS_SYSEX 0xF - -typedef struct _midi_event { - int32_t time; - uint8_t status; - uint8_t data[2]; - uint32_t len; - uint8_t *buffer; - struct _midi_event *next; -} midi_event; - -typedef struct { - uint16_t type; - uint16_t tracks; -} midi_descriptor; - -struct xmi_ctx { - uint8_t *src, *src_ptr; - uint32_t srcsize; - uint32_t datastart; - uint8_t *dst, *dst_ptr; - uint32_t dstsize, dstrem; - uint32_t convert_type; - midi_descriptor info; - int bank127[16]; - midi_event **events; - signed short *timing; - midi_event *list; - midi_event *current; -}; - -/* forward declarations of private functions */ -static void DeleteEventList(midi_event *mlist); -static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time); /* List manipulation */ -static int GetVLQ(struct xmi_ctx *ctx, uint32_t *quant); /* Variable length quantity */ -static int GetVLQ2(struct xmi_ctx *ctx, uint32_t *quant);/* Variable length quantity */ -static int PutVLQ(struct xmi_ctx *ctx, uint32_t value); /* Variable length quantity */ -static int ConvertEvent(struct xmi_ctx *ctx, - const int32_t time, const uint8_t status, const int size); -static int32_t ConvertSystemMessage(struct xmi_ctx *ctx, - const int32_t time, const uint8_t status); -static int32_t ConvertFiletoList(struct xmi_ctx *ctx); -static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist); -static int ParseXMI(struct xmi_ctx *ctx); -static int ExtractTracks(struct xmi_ctx *ctx); -static uint32_t ExtractTracksFromXmi(struct xmi_ctx *ctx); - -static uint32_t read1(struct xmi_ctx *ctx) -{ - uint8_t b0; - b0 = *ctx->src_ptr++; - return (b0); -} - -static uint32_t read2(struct xmi_ctx *ctx) -{ - uint8_t b0, b1; - b0 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - return (b0 + ((uint32_t)b1 << 8)); -} - -static uint32_t read4(struct xmi_ctx *ctx) -{ - uint8_t b0, b1, b2, b3; - 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 void copy(struct xmi_ctx *ctx, char *b, uint32_t len) -{ - memcpy(b, ctx->src_ptr, len); - ctx->src_ptr += len; -} - -#define DST_CHUNK 8192 -static void resize_dst(struct xmi_ctx *ctx) { - uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - ctx->dst = realloc(ctx->dst, ctx->dstsize + DST_CHUNK); - ctx->dstsize += DST_CHUNK; - ctx->dstrem += DST_CHUNK; - ctx->dst_ptr = ctx->dst + pos; -} - -static void write1(struct xmi_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 1) - resize_dst(ctx); - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem--; -} - -static void write2(struct xmi_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 2) - resize_dst(ctx); - *ctx->dst_ptr++ = (val>>8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 2; -} - -static void write4(struct xmi_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 4) - resize_dst(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 seeksrc(struct xmi_ctx *ctx, uint32_t pos) { - ctx->src_ptr = ctx->src + pos; -} - -static void seekdst(struct xmi_ctx *ctx, uint32_t pos) { - ctx->dst_ptr = ctx->dst + pos; - while (ctx->dstsize < pos) - resize_dst(ctx); - ctx->dstrem = ctx->dstsize - pos; -} - -static void skipsrc(struct xmi_ctx *ctx, int32_t pos) { - ctx->src_ptr += pos; -} - -static void skipdst(struct xmi_ctx *ctx, int32_t pos) { - size_t newpos; - ctx->dst_ptr += pos; - newpos = ctx->dst_ptr - ctx->dst; - while (ctx->dstsize < newpos) - resize_dst(ctx); - ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); -} - -static uint32_t getsrcsize(struct xmi_ctx *ctx) { - return (ctx->srcsize); -} - -static uint32_t getsrcpos(struct xmi_ctx *ctx) { - return (uint32_t)(ctx->src_ptr - ctx->src); -} - -static uint32_t getdstpos(struct xmi_ctx *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 const char mt32asgm[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 const char mt32asgs[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 */ -}; - -int AdlMidi_xmi2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint32_t convert_type) { - struct xmi_ctx ctx; - unsigned int i; - int ret = -1; - - if (convert_type > XMIDI_CONVERT_MT32_TO_GS) { - /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", __FUNCTION__, __LINE__, convert_type);*/ - return (ret); - } - - memset(&ctx, 0, sizeof(struct xmi_ctx)); - ctx.src = ctx.src_ptr = in; - ctx.srcsize = insize; - ctx.convert_type = convert_type; - - if (ParseXMI(&ctx) < 0) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, NULL, 0);*/ - goto _end; - } - - if (ExtractTracks(&ctx) < 0) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, NULL, 0);*/ - goto _end; - } - - ctx.dst = malloc(DST_CHUNK); - ctx.dst_ptr = ctx.dst; - ctx.dstsize = DST_CHUNK; - ctx.dstrem = DST_CHUNK; - - /* Header is 14 bytes long and add the rest as well */ - write1(&ctx, 'M'); - write1(&ctx, 'T'); - write1(&ctx, 'h'); - write1(&ctx, 'd'); - - write4(&ctx, 6); - - write2(&ctx, ctx.info.type); - write2(&ctx, ctx.info.tracks); - write2(&ctx, ctx.timing[0]);/* write divisions from track0 */ - - for (i = 0; i < ctx.info.tracks; i++) - ConvertListToMTrk(&ctx, ctx.events[i]); - *out = ctx.dst; - *outsize = ctx.dstsize - ctx.dstrem; - ret = 0; - -_end: /* cleanup */ - if (ret < 0) { - free(ctx.dst); - *out = NULL; - *outsize = 0; - } - if (ctx.events) { - for (i = 0; i < ctx.info.tracks; i++) - DeleteEventList(ctx.events[i]); - free(ctx.events); - } - free(ctx.timing); - - return (ret); -} - -static void DeleteEventList(midi_event *mlist) { - midi_event *event; - midi_event *next; - - next = mlist; - - while ((event = next) != NULL) { - next = event->next; - free(event->buffer); - free(event); - } -} - -/* Sets current to the new event and updates list */ -static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time) { - if (!ctx->list) { - ctx->list = ctx->current = calloc(1, sizeof(midi_event)); - ctx->current->time = (time < 0)? 0 : time; - return; - } - - if (time < 0) { - midi_event *event = calloc(1, sizeof(midi_event)); - 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) { - midi_event *event = calloc(1, sizeof(midi_event)); - 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 = calloc(1, sizeof(midi_event)); - ctx->current = ctx->current->next; - ctx->current->time = time; -} - -/* Conventional Variable Length Quantity */ -static int GetVLQ(struct xmi_ctx *ctx, uint32_t *quant) { - int i; - uint32_t data; - - *quant = 0; - for (i = 0; i < 4; i++) { - data = read1(ctx); - *quant <<= 7; - *quant |= data & 0x7F; - - if (!(data & 0x80)) { - i++; - break; - } - } - return (i); -} - -/* XMIDI Delta Variable Length Quantity */ -static int GetVLQ2(struct xmi_ctx *ctx, uint32_t *quant) { - int i; - int32_t data; - - *quant = 0; - for (i = 0; i < 4; i++) { - data = read1(ctx); - if (data & 0x80) { - skipsrc(ctx, -1); - break; - } - *quant += data; - } - return (i); -} - -static int PutVLQ(struct xmi_ctx *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++) { - write1(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 ConvertEvent(struct xmi_ctx *ctx, const int32_t time, - const uint8_t status, const int size) { - uint32_t delta = 0; - int32_t data; - midi_event *prev; - int i; - - data = read1(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 = read1(ctx); - - ctx->bank127[status & 0xF] = 0; - - if ( ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM || - ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS || - ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127 || - (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM - && (status & 0xF) == 9) ) - return (2); - - CreateNewEvent(ctx, time); - ctx->current->status = status; - ctx->current->data[0] = 0; - ctx->current->data[1] = data == 127 ? 0 : data;/*HACK:*/ - - if (ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && 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 != XMIDI_CONVERT_NOCONVERSION) - { - if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM) - { - data = mt32asgm[data]; - } - else if ((ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && ctx->bank127[status&0xF]) || - ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS || - ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM) - { - CreateNewEvent (ctx, time); - ctx->current->status = 0xB0 | (status&0xF); - ctx->current->data[0] = 0; - ctx->current->data[1] = mt32asgs[data*2+1]; - - data = mt32asgs[data*2]; - } - else if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) - { - CreateNewEvent (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 == XMIDI_CONVERT_MT32_TO_GS127DRUM || ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127)) - { - CreateNewEvent (ctx, time); - ctx->current->status = 0xB9; - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - - CreateNewEvent(ctx, time); - ctx->current->status = status; - - ctx->current->data[0] = data; - - if (size == 1) - return (1); - - ctx->current->data[1] = read1(ctx); - - if (size == 2) - return (2); - - /* XMI Note On handling */ - prev = ctx->current; - i = GetVLQ(ctx, &delta); - CreateNewEvent(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 ConvertSystemMessage(struct xmi_ctx *ctx, const int32_t time, - const uint8_t status) { - int32_t i = 0; - - CreateNewEvent(ctx, time); - ctx->current->status = status; - - /* Handling of Meta events */ - if (status == 0xFF) { - ctx->current->data[0] = read1(ctx); - i++; - } - - i += GetVLQ(ctx, &ctx->current->len); - - if (!ctx->current->len) - return (i); - - ctx->current->buffer = malloc(sizeof(uint8_t)*ctx->current->len); - copy(ctx, (char *) ctx->current->buffer, ctx->current->len); - - return (i + ctx->current->len); -} - -/* XMIDI and Midi to List - * Returns XMIDI PPQN */ -static int32_t ConvertFiletoList(struct xmi_ctx *ctx) { - 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 = getsrcsize(ctx); - - /* Set Drum track to correct setting if required */ - if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) { - CreateNewEvent(ctx, 0); - ctx->current->status = 0xB9; - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - - while (!end && getsrcpos(ctx) < file_size) { - GetVLQ2(ctx, &data); - time += data * 3; - - status = read1(ctx); - - switch (status >> 4) { - case MIDI_STATUS_NOTE_ON: - ConvertEvent(ctx, time, status, 3); - break; - - /* 2 byte data */ - case MIDI_STATUS_NOTE_OFF: - case MIDI_STATUS_AFTERTOUCH: - case MIDI_STATUS_CONTROLLER: - case MIDI_STATUS_PITCH_WHEEL: - ConvertEvent(ctx, time, status, 2); - break; - - /* 1 byte data */ - case MIDI_STATUS_PROG_CHANGE: - case MIDI_STATUS_PRESSURE: - ConvertEvent(ctx, time, status, 1); - break; - - case MIDI_STATUS_SYSEX: - if (status == 0xFF) { - int32_t pos = getsrcpos(ctx); - uint32_t dat = read1(ctx); - - if (dat == 0x2F) /* End */ - end = 1; - else if (dat == 0x51 && !tempo_set) /* Tempo. Need it for PPQN */ - { - skipsrc(ctx, 1); - tempo = read1(ctx) << 16; - tempo += read1(ctx) << 8; - tempo += read1(ctx); - tempo *= 3; - tempo_set = 1; - } else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */ - { - GetVLQ(ctx, &dat); - skipsrc(ctx, dat); - break; - } - - seeksrc(ctx, pos); - } - ConvertSystemMessage(ctx, time, status); - break; - - default: - break; - } - } - return ((tempo * 3) / 25000); -} - -/* Converts and event list to a MTrk - * Returns bytes of the array - * buf can be NULL */ -static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist) { - int32_t time = 0; - midi_event *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; - - write1(ctx, 'M'); - write1(ctx, 'T'); - write1(ctx, 'r'); - write1(ctx, 'k'); - - size_pos = getdstpos(ctx); - skipdst(ctx, 4); - - for (event = mlist; event && !end; event = event->next) { - delta = (event->time - time); - time = event->time; - - i += PutVLQ(ctx, delta); - - if ((event->status != last_status) || (event->status >= 0xF0)) { - write1(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: - write1(ctx, event->data[0]); - write1(ctx, event->data[1]); - i += 2; - break; - - /* 1 bytes data - * Program Change and Channel Pressure */ - case 0xC: - case 0xD: - write1(ctx, event->data[0]); - i++; - break; - - /* Variable length - * SysEx */ - case 0xF: - if (event->status == 0xFF) { - if (event->data[0] == 0x2f) - end = 1; - write1(ctx, event->data[0]); - i++; - } - i += PutVLQ(ctx, event->len); - if (event->len) { - for (j = 0; j < event->len; j++) { - write1(ctx, event->buffer[j]); - i++; - } - } - break; - - /* Never occur */ - default: - /*_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);*/ - break; - } - } - - cur_pos = getdstpos(ctx); - seekdst(ctx, size_pos); - write4(ctx, i - 8); - seekdst(ctx, cur_pos); - - return (i); -} - -/* Assumes correct xmidi */ -static uint32_t ExtractTracksFromXmi(struct xmi_ctx *ctx) { - uint32_t num = 0; - signed short ppqn; - uint32_t len = 0; - int32_t begin; - char buf[32]; - - while (getsrcpos(ctx) < getsrcsize(ctx) && num != ctx->info.tracks) { - /* Read first 4 bytes of name */ - copy(ctx, buf, 4); - len = read4(ctx); - - /* Skip the FORM entries */ - if (!memcmp(buf, "FORM", 4)) { - skipsrc(ctx, 4); - copy(ctx, buf, 4); - len = read4(ctx); - } - - if (memcmp(buf, "EVNT", 4)) { - skipsrc(ctx, (len + 1) & ~1); - continue; - } - - ctx->list = NULL; - begin = getsrcpos(ctx); - - /* Convert it */ - if (!(ppqn = ConvertFiletoList(ctx))) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, NULL, 0);*/ - break; - } - ctx->timing[num] = ppqn; - ctx->events[num] = ctx->list; - - /* Increment Counter */ - num++; - - /* go to start of next track */ - seeksrc(ctx, begin + ((len + 1) & ~1)); - } - - /* Return how many were converted */ - return (num); -} - -static int ParseXMI(struct xmi_ctx *ctx) { - uint32_t i; - uint32_t start; - uint32_t len; - uint32_t chunk_len; - uint32_t file_size; - char buf[32]; - - file_size = getsrcsize(ctx); - if (getsrcpos(ctx) + 8 > file_size) { -badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ - return (-1); - } - - /* Read first 4 bytes of header */ - copy(ctx, buf, 4); - - /* Could be XMIDI */ - if (!memcmp(buf, "FORM", 4)) { - /* Read length of */ - len = read4(ctx); - - start = getsrcpos(ctx); - if (start + 4 > file_size) - goto badfile; - - /* Read 4 bytes of type */ - copy(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 (getsrcpos(ctx) + 10 > file_size) - break; - - /* Read 4 bytes of type */ - copy(ctx, buf, 4); - - /* Read length of chunk */ - chunk_len = read4(ctx); - - /* Add eight bytes */ - i += 8; - - if (memcmp(buf, "INFO", 4)) { - /* Must align */ - skipsrc(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 = read2(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 */ - seeksrc(ctx, start + ((len + 1) & ~1)); - if (getsrcpos(ctx) + 12 > file_size) - goto badfile; - - /* Read 4 bytes of type */ - copy(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 */ - read4(ctx); - - /* Read 4 bytes of type */ - copy(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 = getsrcpos(ctx); - return (0); - } - } - - return (-1); -} - -static int ExtractTracks(struct xmi_ctx *ctx) { - uint32_t i; - - ctx->events = calloc(ctx->info.tracks, sizeof(midi_event*)); - ctx->timing = calloc(ctx->info.tracks, sizeof(int16_t)); - /* type-2 for multi-tracks, type-0 otherwise */ - ctx->info.type = (ctx->info.tracks > 1)? 2 : 0; - - seeksrc(ctx, ctx->datastart); - i = ExtractTracksFromXmi(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); -} - diff --git a/src/adlmidi_xmi2mid.h b/src/adlmidi_xmi2mid.h deleted file mode 100644 index 950d58c..0000000 --- a/src/adlmidi_xmi2mid.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * XMIDI: Miles XMIDI to MID Library Header - * - * Copyright (C) 2001 Ryan Nunn - * Copyright (C) 2014-2016 Bret Curtis - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -/* XMIDI Converter */ - -#ifndef XMIDILIB_H -#define XMIDILIB_H - -#include - -#ifdef __DJGPP__ -typedef signed char int8_t; -typedef unsigned char uint8_t; -typedef signed short int16_t; -typedef unsigned short uint16_t; -typedef signed long int32_t; -typedef unsigned long uint32_t; -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* Conversion types for Midi files */ -#define XMIDI_CONVERT_NOCONVERSION 0x00 -#define XMIDI_CONVERT_MT32_TO_GM 0x01 -#define XMIDI_CONVERT_MT32_TO_GS 0x02 -#define XMIDI_CONVERT_MT32_TO_GS127 0x03 /* This one is broken, don't use */ -#define XMIDI_CONVERT_MT32_TO_GS127DRUM 0x04 /* This one is broken, don't use */ -#define XMIDI_CONVERT_GS127_TO_GS 0x05 - -int AdlMidi_xmi2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint32_t convert_type); - -#ifdef __cplusplus -} -#endif - -#endif /* XMIDILIB_H */ diff --git a/src/cvt_mus2mid.hpp b/src/cvt_mus2mid.hpp new file mode 100644 index 0000000..5a465c2 --- /dev/null +++ b/src/cvt_mus2mid.hpp @@ -0,0 +1,461 @@ +/* + * MUS2MIDI: MUS to MIDI Library + * + * Copyright (C) 2014 Bret Curtis + * Copyright (C) WildMIDI Developers 2015-2016 + * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#ifdef __DJGPP__ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed long int32_t; +typedef unsigned long uint32_t; +#endif + +#define MUS_FREQUENCY 140 /* default Hz or BPM */ + +#if 0 /* older units: */ +#define MUS_TEMPO 0x001aa309 /* MPQN: 60000000 / 34.37Hz = 1745673 */ +#define MUS_DIVISION 0x0059 /* 89 -- used by many mus2midi converters */ +#endif + +#define MUS_TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ + /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ + +#define MUS_DIVISION 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) + * + */ + +#define MUSEVENT_KEYOFF 0 +#define MUSEVENT_KEYON 1 +#define MUSEVENT_PITCHWHEEL 2 +#define MUSEVENT_CHANNELMODE 3 +#define MUSEVENT_CONTROLLERCHANGE 4 +#define MUSEVENT_END 6 + +#define MUS_MIDI_MAXCHANNELS 16 + +static char MUS_ID[] = { 'M', 'U', 'S', 0x1A }; + +static uint8_t mus_midimap[] = +{/* 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 */ +}; + +typedef 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; +} MUSHeader ; +#define MUS_HEADERSIZE 14 + +typedef struct MidiHeaderChunk { + char name[4]; + int32_t length; + int16_t format; /* make 0 */ + int16_t ntracks;/* make 1 */ + int16_t division; /* 0xe250 ?? */ +} MidiHeaderChunk; +#define MIDI_HEADERSIZE 14 + +typedef struct MidiTrackChunk { + char name[4]; + int32_t length; +} MidiTrackChunk; +#define TRK_CHUNKSIZE 8 + +struct mus_ctx { + uint8_t *src, *src_ptr; + uint32_t srcsize; + uint32_t datastart; + uint8_t *dst, *dst_ptr; + uint32_t dstsize, dstrem; +}; + +#define DST_CHUNK 8192 +static void mus2mid_resize_dst(struct mus_ctx *ctx) { + uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); + ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + DST_CHUNK); + ctx->dstsize += DST_CHUNK; + ctx->dstrem += DST_CHUNK; + ctx->dst_ptr = ctx->dst + pos; +} + +static void mus2mid_write1(struct mus_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 1) + mus2mid_resize_dst(ctx); + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem--; +} + +static void mus2mid_write2(struct mus_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 2) + mus2mid_resize_dst(ctx); + *ctx->dst_ptr++ = (val>>8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 2; +} + +static void mus2mid_write4(struct mus_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 4) + mus2mid_resize_dst(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 mus2mid_seekdst(struct mus_ctx *ctx, uint32_t pos) { + ctx->dst_ptr = ctx->dst + pos; + while (ctx->dstsize < pos) + mus2mid_resize_dst(ctx); + ctx->dstrem = ctx->dstsize - pos; +} + +static void mus2mid_skipdst(struct mus_ctx *ctx, int32_t pos) { + size_t newpos; + ctx->dst_ptr += pos; + newpos = ctx->dst_ptr - ctx->dst; + while (ctx->dstsize < newpos) + mus2mid_resize_dst(ctx); + ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); +} + +static uint32_t mus2mid_getdstpos(struct mus_ctx *ctx) { + return (uint32_t)(ctx->dst_ptr - ctx->dst); +} + +/* writes a variable length integer to a buffer, and returns bytes written */ +static int32_t mus2mid_writevarlen(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 MUS_READ_INT16(b) ((b)[0] | ((b)[1] << 8)) +#define MUS_READ_INT32(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24)) + +static int Convert_mus2midi(uint8_t *in, uint32_t insize, + uint8_t **out, uint32_t *outsize, + uint16_t frequency) +{ + struct mus_ctx 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[MUS_MIDI_MAXCHANNELS]; + int channelMap[MUS_MIDI_MAXCHANNELS], currentChannel; + + if (insize < MUS_HEADERSIZE) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ + return (-1); + } + + if (!frequency) + frequency = MUS_FREQUENCY; + + /* read the MUS header and set our location */ + memcpy(header.ID, in, 4); + header.scoreLen = MUS_READ_INT16(&in[4]); + header.scoreStart = MUS_READ_INT16(&in[6]); + header.channels = MUS_READ_INT16(&in[8]); + header.sec_channels = MUS_READ_INT16(&in[10]); + header.instrCnt = MUS_READ_INT16(&in[12]); + + if (memcmp(header.ID, MUS_ID, 4)) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, NULL, 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 > MUS_MIDI_MAXCHANNELS - 1) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, NULL, 0);*/ + return (-1); + } + + memset(&ctx, 0, sizeof(struct mus_ctx)); + ctx.src = ctx.src_ptr = in; + ctx.srcsize = insize; + + ctx.dst = (uint8_t*)calloc(DST_CHUNK, sizeof(uint8_t)); + ctx.dst_ptr = ctx.dst; + ctx.dstsize = DST_CHUNK; + ctx.dstrem = DST_CHUNK; + + /* Map channel 15 to 9 (percussions) */ + for (temp = 0; temp < MUS_MIDI_MAXCHANNELS; ++temp) { + channelMap[temp] = -1; + channel_volume[temp] = 0x40; + } + channelMap[15] = 9; + + /* Header is 14 bytes long and add the rest as well */ + mus2mid_write1(&ctx, 'M'); + mus2mid_write1(&ctx, 'T'); + mus2mid_write1(&ctx, 'h'); + mus2mid_write1(&ctx, 'd'); + mus2mid_write4(&ctx, 6); /* length of header */ + mus2mid_write2(&ctx, 0); /* MIDI type (always 0) */ + mus2mid_write2(&ctx, 1); /* MUS files only have 1 track */ + mus2mid_write2(&ctx, MUS_DIVISION); /* division */ + + /* Write out track header and track length position for later */ + begin_track_pos = mus2mid_getdstpos(&ctx); + mus2mid_write1(&ctx, 'M'); + mus2mid_write1(&ctx, 'T'); + mus2mid_write1(&ctx, 'r'); + mus2mid_write1(&ctx, 'k'); + track_size_pos = mus2mid_getdstpos(&ctx); + mus2mid_skipdst(&ctx, 4); + + /* write tempo: microseconds per quarter note */ + mus2mid_write1(&ctx, 0x00); /* delta time */ + mus2mid_write1(&ctx, 0xff); /* sys command */ + mus2mid_write2(&ctx, 0x5103); /* command - set tempo */ + mus2mid_write1(&ctx, MUS_TEMPO & 0x000000ff); + mus2mid_write1(&ctx, (MUS_TEMPO & 0x0000ff00) >> 8); + mus2mid_write1(&ctx, (MUS_TEMPO & 0x00ff0000) >> 16); + + /* Percussions channel starts out at full volume */ + mus2mid_write1(&ctx, 0x00); + mus2mid_write1(&ctx, 0xB9); + mus2mid_write1(&ctx, 0x07); + mus2mid_write1(&ctx, 127); + + /* 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 += mus2mid_writevarlen(delta_time, out_local); + + /* set all channels to 127 (max) volume */ + if (channelMap[channel] < 0) { + *out_local++ = 0xB0 + currentChannel; + *out_local++ = 0x07; + *out_local++ = 127; + *out_local++ = 0x00; + channelMap[channel] = currentChannel++; + if (currentChannel == 9) + ++currentChannel; + } + status = channelMap[channel]; + + /* handle events */ + switch ((event & 122) >> 4){ + case MUSEVENT_KEYOFF: + status |= 0x80; + bit1 = *cur++; + bit2 = 0x40; + break; + case MUSEVENT_KEYON: + status |= 0x90; + bit1 = *cur & 127; + if (*cur++ & 128) /* volume bit? */ + channel_volume[channelMap[channel]] = *cur++; + bit2 = channel_volume[channelMap[channel]]; + break; + case MUSEVENT_PITCHWHEEL: + status |= 0xE0; + bit1 = (*cur & 1) >> 6; + bit2 = (*cur++ >> 1) & 127; + break; + case MUSEVENT_CHANNELMODE: + status |= 0xB0; + if (*cur >= sizeof(mus_midimap) / sizeof(mus_midimap[0])) { + /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", + __FUNCTION__, __LINE__, *cur);*/ + goto _end; + } + bit1 = mus_midimap[*cur++]; + bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; + break; + case MUSEVENT_CONTROLLERCHANGE: + if (*cur == 0) { + cur++; + status |= 0xC0; + bit1 = *cur++; + bit2 = 0;/* silence bogus warnings */ + bitc = 1; + } else { + status |= 0xB0; + if (*cur >= sizeof(mus_midimap) / sizeof(mus_midimap[0])) { + /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", + __FUNCTION__, __LINE__, *cur);*/ + goto _end; + } + bit1 = mus_midimap[*cur++]; + bit2 = *cur++; + } + break; + case MUSEVENT_END: /* 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)) + mus2mid_resize_dst(&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 = mus2mid_getdstpos(&ctx); + mus2mid_seekdst(&ctx, track_size_pos); + mus2mid_write4(&ctx, current_pos - begin_track_pos - TRK_CHUNKSIZE); + mus2mid_seekdst(&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 = NULL; + *outsize = 0; + } + + return (ret); +} + diff --git a/src/cvt_xmi2mid.hpp b/src/cvt_xmi2mid.hpp new file mode 100644 index 0000000..c164e3d --- /dev/null +++ b/src/cvt_xmi2mid.hpp @@ -0,0 +1,1123 @@ +/* + * XMIDI: Miles XMIDI to MID Library + * + * Copyright (C) 2001 Ryan Nunn + * Copyright (C) 2014 Bret Curtis + * Copyright (C) WildMIDI Developers 2015-2016 + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* XMIDI Converter */ + +#include +#include +#include +#include +#include + +#ifdef __DJGPP__ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed long int32_t; +typedef unsigned long uint32_t; +#endif + +/* Conversion types for Midi files */ +#define XMIDI_CONVERT_NOCONVERSION 0x00 +#define XMIDI_CONVERT_MT32_TO_GM 0x01 +#define XMIDI_CONVERT_MT32_TO_GS 0x02 +#define XMIDI_CONVERT_MT32_TO_GS127 0x03 /* This one is broken, don't use */ +#define XMIDI_CONVERT_MT32_TO_GS127DRUM 0x04 /* This one is broken, don't use */ +#define XMIDI_CONVERT_GS127_TO_GS 0x05 + +/* Midi Status Bytes */ +#define XMI2MID_MIDI_STATUS_NOTE_OFF 0x8 +#define XMI2MID_MIDI_STATUS_NOTE_ON 0x9 +#define XMI2MID_MIDI_STATUS_AFTERTOUCH 0xA +#define XMI2MID_MIDI_STATUS_CONTROLLER 0xB +#define XMI2MID_MIDI_STATUS_PROG_CHANGE 0xC +#define XMI2MID_MIDI_STATUS_PRESSURE 0xD +#define XMI2MID_MIDI_STATUS_PITCH_WHEEL 0xE +#define XMI2MID_MIDI_STATUS_SYSEX 0xF + +typedef struct _xmi2mid_midi_event { + int32_t time; + uint8_t status; + uint8_t data[2]; + uint32_t len; + uint8_t *buffer; + struct _xmi2mid_midi_event *next; +} midi_event; + +typedef struct { + uint16_t type; + uint16_t tracks; +} midi_descriptor; + +struct xmi2mid_xmi_ctx { + uint8_t *src, *src_ptr; + uint32_t srcsize; + uint32_t datastart; + uint8_t *dst, *dst_ptr; + uint32_t dstsize, dstrem; + uint32_t convert_type; + midi_descriptor info; + int bank127[16]; + midi_event **events; + int16_t *timing; + midi_event *list; + midi_event *current; +}; + +/* forward declarations of private functions */ +static void xmi2mid_DeleteEventList(midi_event *mlist); +static void xmi2mid_CreateNewEvent(struct xmi2mid_xmi_ctx *ctx, int32_t time); /* List manipulation */ +static int xmi2mid_GetVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant); /* Variable length quantity */ +static int xmi2mid_GetVLQ2(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant);/* Variable length quantity */ +static int xmi2mid_PutVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t value); /* Variable length quantity */ +static int xmi2mid_ConvertEvent(struct xmi2mid_xmi_ctx *ctx, + const int32_t time, const uint8_t status, const int size); +static int32_t xmi2mid_ConvertSystemMessage(struct xmi2mid_xmi_ctx *ctx, + const int32_t time, const uint8_t status); +static int32_t xmi2mid_ConvertFiletoList(struct xmi2mid_xmi_ctx *ctx); +static uint32_t xmi2mid_ConvertListToMTrk(struct xmi2mid_xmi_ctx *ctx, midi_event *mlist); +static int xmi2mid_ParseXMI(struct xmi2mid_xmi_ctx *ctx); +static int xmi2mid_ExtractTracks(struct xmi2mid_xmi_ctx *ctx); +static uint32_t xmi2mid_ExtractTracksFromXmi(struct xmi2mid_xmi_ctx *ctx); + +static uint32_t xmi2mid_read1(struct xmi2mid_xmi_ctx *ctx) +{ + uint8_t b0; + b0 = *ctx->src_ptr++; + return (b0); +} + +static uint32_t xmi2mid_read2(struct xmi2mid_xmi_ctx *ctx) +{ + uint8_t b0, b1; + b0 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + return (b0 + ((uint32_t)b1 << 8)); +} + +static uint32_t xmi2mid_read4(struct xmi2mid_xmi_ctx *ctx) +{ + uint8_t b0, b1, b2, b3; + 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 void xmi2mid_copy(struct xmi2mid_xmi_ctx *ctx, char *b, uint32_t len) +{ + memcpy(b, ctx->src_ptr, len); + ctx->src_ptr += len; +} + +#define DST_CHUNK 8192 +static void xmi2mid_resize_dst(struct xmi2mid_xmi_ctx *ctx) { + uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); + ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + DST_CHUNK); + ctx->dstsize += DST_CHUNK; + ctx->dstrem += DST_CHUNK; + ctx->dst_ptr = ctx->dst + pos; +} + +static void xmi2mid_write1(struct xmi2mid_xmi_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 1) + xmi2mid_resize_dst(ctx); + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem--; +} + +static void xmi2mid_write2(struct xmi2mid_xmi_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 2) + xmi2mid_resize_dst(ctx); + *ctx->dst_ptr++ = (val>>8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 2; +} + +static void xmi2mid_write4(struct xmi2mid_xmi_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 4) + xmi2mid_resize_dst(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 xmi2mid_seeksrc(struct xmi2mid_xmi_ctx *ctx, uint32_t pos) { + ctx->src_ptr = ctx->src + pos; +} + +static void xmi2mid_seekdst(struct xmi2mid_xmi_ctx *ctx, uint32_t pos) { + ctx->dst_ptr = ctx->dst + pos; + while (ctx->dstsize < pos) + xmi2mid_resize_dst(ctx); + ctx->dstrem = ctx->dstsize - pos; +} + +static void xmi2mid_skipsrc(struct xmi2mid_xmi_ctx *ctx, int32_t pos) { + ctx->src_ptr += pos; +} + +static void xmi2mid_skipdst(struct xmi2mid_xmi_ctx *ctx, int32_t pos) { + size_t newpos; + ctx->dst_ptr += pos; + newpos = ctx->dst_ptr - ctx->dst; + while (ctx->dstsize < newpos) + xmi2mid_resize_dst(ctx); + ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); +} + +static uint32_t xmi2mid_getsrcsize(struct xmi2mid_xmi_ctx *ctx) { + return (ctx->srcsize); +} + +static uint32_t xmi2mid_getsrcpos(struct xmi2mid_xmi_ctx *ctx) { + return (uint32_t)(ctx->src_ptr - ctx->src); +} + +static uint32_t xmi2mid_getdstpos(struct xmi2mid_xmi_ctx *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 const char xmi2mid_mt32asgm[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 const char xmi2mid_mt32asgs[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 Convert_xmi2midi(uint8_t *in, uint32_t insize, + uint8_t **out, uint32_t *outsize, + uint32_t convert_type) +{ + struct xmi2mid_xmi_ctx ctx; + unsigned int i; + int ret = -1; + + if (convert_type > XMIDI_CONVERT_MT32_TO_GS) { + /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", __FUNCTION__, __LINE__, convert_type);*/ + return (ret); + } + + memset(&ctx, 0, sizeof(struct xmi2mid_xmi_ctx)); + ctx.src = ctx.src_ptr = in; + ctx.srcsize = insize; + ctx.convert_type = convert_type; + + if (xmi2mid_ParseXMI(&ctx) < 0) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, NULL, 0);*/ + goto _end; + } + + if (xmi2mid_ExtractTracks(&ctx) < 0) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, NULL, 0);*/ + goto _end; + } + + ctx.dst = (uint8_t*)malloc(DST_CHUNK); + ctx.dst_ptr = ctx.dst; + ctx.dstsize = DST_CHUNK; + ctx.dstrem = DST_CHUNK; + + /* Header is 14 bytes long and add the rest as well */ + xmi2mid_write1(&ctx, 'M'); + xmi2mid_write1(&ctx, 'T'); + xmi2mid_write1(&ctx, 'h'); + xmi2mid_write1(&ctx, 'd'); + + xmi2mid_write4(&ctx, 6); + + xmi2mid_write2(&ctx, ctx.info.type); + xmi2mid_write2(&ctx, ctx.info.tracks); + xmi2mid_write2(&ctx, ctx.timing[0]);/* write divisions from track0 */ + + for (i = 0; i < ctx.info.tracks; i++) + xmi2mid_ConvertListToMTrk(&ctx, ctx.events[i]); + *out = ctx.dst; + *outsize = ctx.dstsize - ctx.dstrem; + ret = 0; + +_end: /* cleanup */ + if (ret < 0) { + free(ctx.dst); + *out = NULL; + *outsize = 0; + } + if (ctx.events) { + for (i = 0; i < ctx.info.tracks; i++) + xmi2mid_DeleteEventList(ctx.events[i]); + free(ctx.events); + } + free(ctx.timing); + + return (ret); +} + +static void xmi2mid_DeleteEventList(midi_event *mlist) { + midi_event *event; + midi_event *next; + + next = mlist; + + while ((event = next) != NULL) { + next = event->next; + free(event->buffer); + free(event); + } +} + +/* Sets current to the new event and updates list */ +static void xmi2mid_CreateNewEvent(struct xmi2mid_xmi_ctx *ctx, int32_t time) { + if (!ctx->list) { + ctx->list = ctx->current = (struct _xmi2mid_midi_event *)calloc(1, sizeof(midi_event)); + ctx->current->time = (time < 0)? 0 : time; + return; + } + + if (time < 0) { + midi_event *event = (midi_event *)calloc(1, sizeof(midi_event)); + 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) { + midi_event *event = (midi_event *)calloc(1, sizeof(midi_event)); + 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 = (struct _xmi2mid_midi_event *)calloc(1, sizeof(midi_event)); + ctx->current = ctx->current->next; + ctx->current->time = time; +} + +/* Conventional Variable Length Quantity */ +static int xmi2mid_GetVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant) { + int i; + uint32_t data; + + *quant = 0; + for (i = 0; i < 4; i++) { + data = xmi2mid_read1(ctx); + *quant <<= 7; + *quant |= data & 0x7F; + + if (!(data & 0x80)) { + i++; + break; + } + } + return (i); +} + +/* XMIDI Delta Variable Length Quantity */ +static int xmi2mid_GetVLQ2(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant) { + int i; + int32_t data; + + *quant = 0; + for (i = 0; i < 4; i++) { + data = xmi2mid_read1(ctx); + if (data & 0x80) { + xmi2mid_skipsrc(ctx, -1); + break; + } + *quant += data; + } + return (i); +} + +static int xmi2mid_PutVLQ(struct xmi2mid_xmi_ctx *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++) { + xmi2mid_write1(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 xmi2mid_ConvertEvent(struct xmi2mid_xmi_ctx *ctx, const int32_t time, + const uint8_t status, const int size) { + uint32_t delta = 0; + int32_t data; + midi_event *prev; + int i; + + data = xmi2mid_read1(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 = xmi2mid_read1(ctx); + + ctx->bank127[status & 0xF] = 0; + + if ( ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM || + ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS || + ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127 || + (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM + && (status & 0xF) == 9) ) + return (2); + + xmi2mid_CreateNewEvent(ctx, time); + ctx->current->status = status; + ctx->current->data[0] = 0; + ctx->current->data[1] = data == 127 ? 0 : data;/*HACK:*/ + + if (ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && 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 != XMIDI_CONVERT_NOCONVERSION) + { + if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM) + { + data = xmi2mid_mt32asgm[data]; + } + else if ((ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && ctx->bank127[status&0xF]) || + ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS || + ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM) + { + xmi2mid_CreateNewEvent (ctx, time); + ctx->current->status = 0xB0 | (status&0xF); + ctx->current->data[0] = 0; + ctx->current->data[1] = xmi2mid_mt32asgs[data*2+1]; + + data = xmi2mid_mt32asgs[data*2]; + } + else if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) + { + xmi2mid_CreateNewEvent (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 == XMIDI_CONVERT_MT32_TO_GS127DRUM || ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127)) + { + xmi2mid_CreateNewEvent (ctx, time); + ctx->current->status = 0xB9; + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + + xmi2mid_CreateNewEvent(ctx, time); + ctx->current->status = status; + + ctx->current->data[0] = data; + + if (size == 1) + return (1); + + ctx->current->data[1] = xmi2mid_read1(ctx); + + if (size == 2) + return (2); + + /* XMI Note On handling */ + prev = ctx->current; + i = xmi2mid_GetVLQ(ctx, &delta); + xmi2mid_CreateNewEvent(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 xmi2mid_ConvertSystemMessage(struct xmi2mid_xmi_ctx *ctx, const int32_t time, + const uint8_t status) { + int32_t i = 0; + + xmi2mid_CreateNewEvent(ctx, time); + ctx->current->status = status; + + /* Handling of Meta events */ + if (status == 0xFF) { + ctx->current->data[0] = xmi2mid_read1(ctx); + i++; + } + + i += xmi2mid_GetVLQ(ctx, &ctx->current->len); + + if (!ctx->current->len) + return (i); + + ctx->current->buffer = (uint8_t *)malloc(sizeof(uint8_t)*ctx->current->len); + xmi2mid_copy(ctx, (char *) ctx->current->buffer, ctx->current->len); + + return (i + ctx->current->len); +} + +/* XMIDI and Midi to List + * Returns XMIDI PPQN */ +static int32_t xmi2mid_ConvertFiletoList(struct xmi2mid_xmi_ctx *ctx) { + 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 = xmi2mid_getsrcsize(ctx); + + /* Set Drum track to correct setting if required */ + if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) { + xmi2mid_CreateNewEvent(ctx, 0); + ctx->current->status = 0xB9; + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + + while (!end && xmi2mid_getsrcpos(ctx) < file_size) { + xmi2mid_GetVLQ2(ctx, &data); + time += data * 3; + + status = xmi2mid_read1(ctx); + + switch (status >> 4) { + case XMI2MID_MIDI_STATUS_NOTE_ON: + xmi2mid_ConvertEvent(ctx, time, status, 3); + break; + + /* 2 byte data */ + case XMI2MID_MIDI_STATUS_NOTE_OFF: + case XMI2MID_MIDI_STATUS_AFTERTOUCH: + case XMI2MID_MIDI_STATUS_CONTROLLER: + case XMI2MID_MIDI_STATUS_PITCH_WHEEL: + xmi2mid_ConvertEvent(ctx, time, status, 2); + break; + + /* 1 byte data */ + case XMI2MID_MIDI_STATUS_PROG_CHANGE: + case XMI2MID_MIDI_STATUS_PRESSURE: + xmi2mid_ConvertEvent(ctx, time, status, 1); + break; + + case XMI2MID_MIDI_STATUS_SYSEX: + if (status == 0xFF) { + int32_t pos = xmi2mid_getsrcpos(ctx); + uint32_t dat = xmi2mid_read1(ctx); + + if (dat == 0x2F) /* End */ + end = 1; + else if (dat == 0x51 && !tempo_set) /* Tempo. Need it for PPQN */ + { + xmi2mid_skipsrc(ctx, 1); + tempo = xmi2mid_read1(ctx) << 16; + tempo += xmi2mid_read1(ctx) << 8; + tempo += xmi2mid_read1(ctx); + tempo *= 3; + tempo_set = 1; + } else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */ + { + xmi2mid_GetVLQ(ctx, &dat); + xmi2mid_skipsrc(ctx, dat); + break; + } + + xmi2mid_seeksrc(ctx, pos); + } + xmi2mid_ConvertSystemMessage(ctx, time, status); + break; + + default: + break; + } + } + return ((tempo * 3) / 25000); +} + +/* Converts and event list to a MTrk + * Returns bytes of the array + * buf can be NULL */ +static uint32_t xmi2mid_ConvertListToMTrk(struct xmi2mid_xmi_ctx *ctx, midi_event *mlist) { + int32_t time = 0; + midi_event *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; + + xmi2mid_write1(ctx, 'M'); + xmi2mid_write1(ctx, 'T'); + xmi2mid_write1(ctx, 'r'); + xmi2mid_write1(ctx, 'k'); + + size_pos = xmi2mid_getdstpos(ctx); + xmi2mid_skipdst(ctx, 4); + + for (event = mlist; event && !end; event = event->next) { + delta = (event->time - time); + time = event->time; + + i += xmi2mid_PutVLQ(ctx, delta); + + if ((event->status != last_status) || (event->status >= 0xF0)) { + xmi2mid_write1(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: + xmi2mid_write1(ctx, event->data[0]); + xmi2mid_write1(ctx, event->data[1]); + i += 2; + break; + + /* 1 bytes data + * Program Change and Channel Pressure */ + case 0xC: + case 0xD: + xmi2mid_write1(ctx, event->data[0]); + i++; + break; + + /* Variable length + * SysEx */ + case 0xF: + if (event->status == 0xFF) { + if (event->data[0] == 0x2f) + end = 1; + xmi2mid_write1(ctx, event->data[0]); + i++; + } + i += xmi2mid_PutVLQ(ctx, event->len); + if (event->len) { + for (j = 0; j < event->len; j++) { + xmi2mid_write1(ctx, event->buffer[j]); + i++; + } + } + break; + + /* Never occur */ + default: + /*_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);*/ + break; + } + } + + cur_pos = xmi2mid_getdstpos(ctx); + xmi2mid_seekdst(ctx, size_pos); + xmi2mid_write4(ctx, i - 8); + xmi2mid_seekdst(ctx, cur_pos); + + return (i); +} + +/* Assumes correct xmidi */ +static uint32_t xmi2mid_ExtractTracksFromXmi(struct xmi2mid_xmi_ctx *ctx) { + uint32_t num = 0; + signed short ppqn; + uint32_t len = 0; + int32_t begin; + char buf[32]; + + while (xmi2mid_getsrcpos(ctx) < xmi2mid_getsrcsize(ctx) && num != ctx->info.tracks) { + /* Read first 4 bytes of name */ + xmi2mid_copy(ctx, buf, 4); + len = xmi2mid_read4(ctx); + + /* Skip the FORM entries */ + if (!memcmp(buf, "FORM", 4)) { + xmi2mid_skipsrc(ctx, 4); + xmi2mid_copy(ctx, buf, 4); + len = xmi2mid_read4(ctx); + } + + if (memcmp(buf, "EVNT", 4)) { + xmi2mid_skipsrc(ctx, (len + 1) & ~1); + continue; + } + + ctx->list = NULL; + begin = xmi2mid_getsrcpos(ctx); + + /* Convert it */ + if (!(ppqn = xmi2mid_ConvertFiletoList(ctx))) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, NULL, 0);*/ + break; + } + ctx->timing[num] = ppqn; + ctx->events[num] = ctx->list; + + /* Increment Counter */ + num++; + + /* go to start of next track */ + xmi2mid_seeksrc(ctx, begin + ((len + 1) & ~1)); + } + + /* Return how many were converted */ + return (num); +} + +static int xmi2mid_ParseXMI(struct xmi2mid_xmi_ctx *ctx) { + uint32_t i; + uint32_t start; + uint32_t len; + uint32_t chunk_len; + uint32_t file_size; + char buf[32]; + + file_size = xmi2mid_getsrcsize(ctx); + if (xmi2mid_getsrcpos(ctx) + 8 > file_size) { +badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ + return (-1); + } + + /* Read first 4 bytes of header */ + xmi2mid_copy(ctx, buf, 4); + + /* Could be XMIDI */ + if (!memcmp(buf, "FORM", 4)) { + /* Read length of */ + len = xmi2mid_read4(ctx); + + start = xmi2mid_getsrcpos(ctx); + if (start + 4 > file_size) + goto badfile; + + /* Read 4 bytes of type */ + xmi2mid_copy(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 (xmi2mid_getsrcpos(ctx) + 10 > file_size) + break; + + /* Read 4 bytes of type */ + xmi2mid_copy(ctx, buf, 4); + + /* Read length of chunk */ + chunk_len = xmi2mid_read4(ctx); + + /* Add eight bytes */ + i += 8; + + if (memcmp(buf, "INFO", 4)) { + /* Must align */ + xmi2mid_skipsrc(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 = xmi2mid_read2(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 */ + xmi2mid_seeksrc(ctx, start + ((len + 1) & ~1)); + if (xmi2mid_getsrcpos(ctx) + 12 > file_size) + goto badfile; + + /* Read 4 bytes of type */ + xmi2mid_copy(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 */ + xmi2mid_read4(ctx); + + /* Read 4 bytes of type */ + xmi2mid_copy(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 = xmi2mid_getsrcpos(ctx); + return (0); + } + } + + return (-1); +} + +static int xmi2mid_ExtractTracks(struct xmi2mid_xmi_ctx *ctx) { + uint32_t i; + + ctx->events = (midi_event **)calloc(ctx->info.tracks, sizeof(midi_event*)); + 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)? 2 : 0; + + xmi2mid_seeksrc(ctx, ctx->datastart); + i = xmi2mid_ExtractTracksFromXmi(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); +} + diff --git a/src/file_reader.hpp b/src/file_reader.hpp index e0664b4..c2cfe7b 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -22,6 +22,10 @@ * DEALINGS IN THE SOFTWARE. */ +#pragma once +#ifndef FILE_AND_MEM_READER_HHHH +#define FILE_AND_MEM_READER_HHHH + #include #include #ifdef _WIN32 @@ -83,6 +87,8 @@ public: */ void openFile(const char *path) { + if(m_fp) + this->close();//Close previously opened file first! #if !defined(_WIN32) || defined(__WATCOMC__) m_fp = std::fopen(path, "rb"); #else @@ -104,6 +110,8 @@ public: */ void openData(const void *mem, size_t lenght) { + if(m_fp) + this->close();//Close previously opened file first! m_fp = NULL; m_mp = mem; m_mp_size = lenght; @@ -117,6 +125,9 @@ public: */ void seek(long pos, int rel_to) { + if(!this->isValid()) + return; + if(m_fp)//If a file { std::fseek(m_fp, pos, rel_to); @@ -162,6 +173,8 @@ public: */ size_t read(void *buf, size_t num, size_t size) { + if(!this->isValid()) + return 0; if(m_fp) return std::fread(buf, num, size, m_fp); else @@ -186,6 +199,8 @@ public: */ int getc() { + if(!this->isValid()) + return -1; if(m_fp)//If a file { return std::getc(m_fp); @@ -206,6 +221,8 @@ public: */ size_t tell() { + if(!this->isValid()) + return 0; if(m_fp)//If a file return static_cast(std::ftell(m_fp)); else//If a memory block @@ -241,6 +258,8 @@ public: */ bool eof() { + if(!this->isValid()) + return true; if(m_fp) return (std::feof(m_fp) != 0); else @@ -256,4 +275,22 @@ public: return m_file_name; } + /** + * @brief Retrieve file size + * @return Size of file in bytes + */ + size_t fileSize() + { + if(!this->isValid()) + return 0; + if(!m_fp) + return m_mp_size; //Size of memory block is well known + size_t old_pos = tell(); + seek(0, FileAndMemReader::END); + size_t file_size = tell(); + seek(old_pos, FileAndMemReader::SET); + return file_size; + } }; + +#endif /* FILE_AND_MEM_READER_HHHH */ diff --git a/src/midi_sequencer.h b/src/midi_sequencer.h new file mode 100644 index 0000000..d3b542e --- /dev/null +++ b/src/midi_sequencer.h @@ -0,0 +1,91 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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 +#ifndef BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHH +#define BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHH + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +struct BW_MidiRtInterface +{ + //! 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); + RawEventHook onEvent; + void *onEvent_userData; + + //! Library internal debug messages + typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); + DebugMessageHook onDebugMessage; + void *onDebugMessage_userData; + + void *rtUserData; + + /* Standard MIDI events. All of them are required! */ + typedef void (*RtNoteOn)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); + RtNoteOn rt_noteOn; + + typedef void (*RtNoteOff)(void *userdata, uint8_t channel, uint8_t note); + RtNoteOff rt_noteOff; + + typedef void (*RtNoteAfterTouch)(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal); + RtNoteAfterTouch rt_noteAfterTouch; + + typedef void (*RtChannelAfterTouch)(void *userdata, uint8_t channel, uint8_t atVal); + RtChannelAfterTouch rt_channelAfterTouch; + + typedef void (*RtControlerChange)(void *userdata, uint8_t channel, uint8_t type, uint8_t value); + RtControlerChange rt_controllerChange; + + typedef void (*RtPatchChange)(void *userdata, uint8_t channel, uint8_t patch); + RtPatchChange rt_patchChange; + + typedef void (*RtPitchBend)(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb); + RtPitchBend rt_pitchBend; + + typedef void (*RtSysEx)(void *userdata, const uint8_t *msg, size_t size); + RtSysEx rt_systemExclusive; + + + /* NonStandard events. There are optional */ + typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value); + RtRawOPL rt_rawOPL; + + typedef void (*RtDeviceSwitch)(void *userdata, size_t track, const char *data, size_t length); + RtDeviceSwitch rt_deviceSwitch; + + typedef uint64_t (*RtCurrentDevice)(void *userdata, size_t track); + RtCurrentDevice rt_currentDevice; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHH */ diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp new file mode 100644 index 0000000..370068b --- /dev/null +++ b/src/midi_sequencer.hpp @@ -0,0 +1,496 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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 +#ifndef BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP +#define BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP + +#include +#include + +#include "fraction.hpp" +#include "file_reader.hpp" +#include "midi_sequencer.h" + +//! Helper for unused values +#define BW_MidiSequencer_UNUSED(x) (void)x; + +class BW_MidiSequencer +{ + /** + * @brief MIDI Event utility container + */ + class MidiEvent + { + public: + MidiEvent(); + /** + * @brief Main MIDI event types + */ + enum Types + { + //! Unknown event + T_UNKNOWN = 0x00, + //! Note-Off event + T_NOTEOFF = 0x08,//size == 2 + //! Note-On event + T_NOTEON = 0x09,//size == 2 + //! Note After-Touch event + T_NOTETOUCH = 0x0A,//size == 2 + //! Controller change event + T_CTRLCHANGE = 0x0B,//size == 2 + //! Patch change event + T_PATCHCHANGE = 0x0C,//size == 1 + //! Channel After-Touch event + T_CHANAFTTOUCH = 0x0D,//size == 1 + //! Pitch-bend change event + T_WHEEL = 0x0E,//size == 2 + + //! System Exclusive message, type 1 + T_SYSEX = 0xF0,//size == len + //! Sys Com Song Position Pntr [LSB, MSB] + T_SYSCOMSPOSPTR = 0xF2,//size == 2 + //! Sys Com Song Select(Song #) [0-127] + T_SYSCOMSNGSEL = 0xF3,//size == 1 + //! System Exclusive message, type 2 + T_SYSEX2 = 0xF7,//size == len + //! Special event + T_SPECIAL = 0xFF + }; + /** + * @brief Special MIDI event sub-types + */ + enum SubTypes + { + //! Sequension number + ST_SEQNUMBER = 0x00,//size == 2 + //! Text label + ST_TEXT = 0x01,//size == len + //! Copyright notice + ST_COPYRIGHT = 0x02,//size == len + //! Sequence track title + ST_SQTRKTITLE = 0x03,//size == len + //! Instrument title + ST_INSTRTITLE = 0x04,//size == len + //! Lyrics text fragment + ST_LYRICS = 0x05,//size == len + //! MIDI Marker + ST_MARKER = 0x06,//size == len + //! Cue Point + ST_CUEPOINT = 0x07,//size == len + //! [Non-Standard] Device Switch + ST_DEVICESWITCH = 0x09,//size == len + //! MIDI Channel prefix + ST_MIDICHPREFIX = 0x20,//size == 1 + + //! End of Track event + ST_ENDTRACK = 0x2F,//size == 0 + //! Tempo change event + ST_TEMPOCHANGE = 0x51,//size == 3 + //! SMPTE offset + ST_SMPTEOFFSET = 0x54,//size == 5 + //! Time signature + ST_TIMESIGNATURE = 0x55, //size == 4 + //! Key signature + ST_KEYSIGNATURE = 0x59,//size == 2 + //! Sequencer specs + ST_SEQUENCERSPEC = 0x7F, //size == len + + /* Non-standard, internal ADLMIDI usage only */ + //! [Non-Standard] Loop Start point + ST_LOOPSTART = 0xE1,//size == 0 + //! [Non-Standard] Loop End point + ST_LOOPEND = 0xE2,//size == 0 + //! [Non-Standard] Raw OPL data + ST_RAWOPL = 0xE3//size == 0 + }; + //! Main type of event + uint8_t type; + //! Sub-type of the event + uint8_t subtype; + //! Targeted MIDI channel + uint8_t channel; + //! Is valid event + uint8_t isValid; + //! Reserved 5 bytes padding + uint8_t __padding[4]; + //! Absolute tick position (Used for the tempo calculation only) + uint64_t absPosition; + //! 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 absPos; + //! Delay to next event in seconds + double timeDelay; + //! List of MIDI events in the current row + std::vector events; + /** + * @brief Sort events in this position + * @param noteStates Buffer of currently pressed/released note keys in the track + */ + void sortEvents(bool *noteStates = NULL); + }; + + /** + * @brief Tempo change point entry. Used in the MIDI data building function only. + */ + struct TempoChangePoint + { + uint64_t absPos; + fraction tempo; + }; + //P.S. I declared it here instead of local in-function because C++98 can't process templates with locally-declared structures + + typedef std::list MidiTrackQueue; + + /** + * @brief Song position context + */ + struct Position + { + //! Was track began playing + bool began; + char padding[7]; + //! Waiting time before next event in seconds + double wait; + //! Absolute time position on the track in seconds + double absTimePosition; + //! Track information + struct TrackInfo + { + size_t ptr; + //! Delay to next event in a track + uint64_t delay; + //! Last handled event type + int lastHandledEvent; + char padding2[4]; + //! MIDI Events queue position iterator + MidiTrackQueue::iterator pos; + TrackInfo(): ptr(0), delay(0), lastHandledEvent(0) {} + }; + std::vector track; + Position(): began(false), wait(0.0), absTimePosition(0.0), track() + {} + }; + + //! MIDI Output interface context + const BW_MidiRtInterface *m_interface; + + /** + * @brief Build MIDI track data from the raw track data storage + * @return true if everything successfully processed, or false on any error + */ + bool buildTrackData(const std::vector > &trackData); + + /** + * @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 isSeek is a seeking process + * @return returns false on reaching end of the song + */ + bool processEvents(bool isSeek = 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, int &status); + +public: + /** + * @brief MIDI marker entry + */ + struct MIDI_MarkerEntry + { + //! Label + std::string label; + //! Position time in seconds + double pos_time; + //! Position time in MIDI ticks + uint64_t pos_ticks; + }; + + /** + * @brief Container of one raw CMF instrument + */ + struct CmfInstrument + { + //! Raw CMF instrument data + uint8_t data[16]; + }; + + /** + * @brief The FileFormat enum + */ + enum FileFormat + { + //! MIDI format + Format_MIDI, + //! CMF format + Format_CMF, + //! Id-Software Music File + Format_IMF, + //! EA-MUS format + Format_RSXX + }; + +private: + //! Music file format type. MIDI is default. + FileFormat m_format; + + //! Current position + Position m_currentPosition; + //! Track begin position + Position m_trackBeginPosition; + //! Loop start point + Position m_loopBeginPosition; + + //! Is looping enabled or not + bool m_loopEnabled; + + //! Full song length in seconds + double m_fullSongTimeLength; + //! Delay after song playd before rejecting the output stream requests + double m_postSongWaitDelay; + + //! Loop start time + double m_loopStartTime; + //! Loop end time + double m_loopEndTime; + + //! Pre-processed track data storage + std::vector m_trackData; + + //! CMF instruments + std::vector m_cmfInstruments; + + //! Title of music + std::string m_musTitle; + //! Copyright notice of music + std::string m_musCopyright; + //! List of track titles + std::vector m_musTrackTitles; + //! List of MIDI markers + std::vector m_musMarkers; + + //! Time of one tick + fraction m_invDeltaTicks; + //! Current tempo + fraction m_tempo; + + //! Tempo multiplier factor + double m_tempoMultiplier; + //! Is song at end + bool m_atEnd; + //! Loop start has reached + bool m_loopStart; + //! Loop end has reached, reset on handling + bool m_loopEnd; + //! Are loop points invalid? + bool m_invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/ + + //! File parsing errors string (adding into m_errorString on aborting of the process) + std::string m_parsingErrorsString; + //! Common error string + std::string m_errorString; + +public: + BW_MidiSequencer(); + virtual ~BW_MidiSequencer(); + + /** + * @brief Sets the RT interface + * @param interface Pre-Initialized interface structure (pointer will be taken) + */ + void setInterface(const BW_MidiRtInterface *interface); + + /** + * @brief Returns file format type of currently loaded file + * @return File format type enumeration + */ + FileFormat getFormat(); + + /** + * @brief Get the list of CMF instruments (CMF only) + * @return Array of raw CMF instruments entries + */ + const std::vector getRawCmfInstruments(); + + /** + * @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 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 path + * @param filename Path to file to open + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(const std::string &filename); + + /** + * @brief Load MIDI file from a memory block + * @param data Pointer to memory block with MIDI data + * @param size Size of source memory block + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(const void *data, size_t size); + + /** + * @brief Load MIDI file by using FileAndMemReader interface + * @param fr FileAndMemReader context with opened source file + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(FileAndMemReader &fr); + + /** + * @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); +}; + +#endif /* BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP */ diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp new file mode 100644 index 0000000..1e4e643 --- /dev/null +++ b/src/midi_sequencer_impl.hpp @@ -0,0 +1,1780 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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. + */ + +#include "midi_sequencer.hpp" +#include +#include +#include + +#include + +#ifndef BWMIDI_DISABLE_MUS_SUPPORT +#include "cvt_mus2mid.hpp" +#endif//MUS + +#ifndef BWMIDI_DISABLE_XMI_SUPPORT +#include "cvt_xmi2mid.hpp" +#endif//XMI + +/** + * @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 ReadBEint(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const unsigned char *data = reinterpret_cast(buffer); + + for(unsigned 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 ReadLEint(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const unsigned char *data = reinterpret_cast(buffer); + + for(unsigned n = 0; n < nbytes; ++n) + result = result + static_cast(data[n] << (n * 8)); + + return result; +} + +/** + * @brief Standard MIDI Variable-Length numeric value parser without of validation + * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value + * @return Unsigned integer that conains parsed variable-length value + */ +static inline uint64_t ReadVarLen(uint8_t **ptr) +{ + uint64_t result = 0; + for(;;) + { + uint8_t byte = *((*ptr)++); + result = (result << 7) + (byte & 0x7F); + if(!(byte & 0x80)) + break; + } + 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 ReadVarLenEx(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; +} + +BW_MidiSequencer::MidiEvent::MidiEvent() : + type(T_UNKNOWN), + subtype(T_UNKNOWN), + channel(0), + isValid(1), + absPosition(0) +{} + +BW_MidiSequencer::MidiTrackRow::MidiTrackRow() : + time(0.0), + delay(0), + absPos(0), + timeDelay(0.0) +{} + +void BW_MidiSequencer::MidiTrackRow::clear() +{ + time = 0.0; + delay = 0; + absPos = 0; + timeDelay = 0.0; + events.clear(); +} + +void BW_MidiSequencer::MidiTrackRow::sortEvents(bool *noteStates) +{ + typedef std::vector EvtArr; + EvtArr metas; + EvtArr noteOffs; + EvtArr controllers; + EvtArr anyOther; + + metas.reserve(events.size()); + noteOffs.reserve(events.size()); + controllers.reserve(events.size()); + anyOther.reserve(events.size()); + + for(size_t i = 0; i < events.size(); i++) + { + if(events[i].type == MidiEvent::T_NOTEOFF) + noteOffs.push_back(events[i]); + else if((events[i].type == MidiEvent::T_CTRLCHANGE) + || (events[i].type == MidiEvent::T_PATCHCHANGE) + || (events[i].type == MidiEvent::T_WHEEL) + || (events[i].type == MidiEvent::T_CHANAFTTOUCH)) + { + controllers.push_back(events[i]); + } + else if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_MARKER)) + metas.push_back(events[i]); + else + 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(noteStates) + { + std::set markAsOn; + for(size_t i = 0; i < anyOther.size(); i++) + { + const MidiEvent e = anyOther[i]; + if(e.type == MidiEvent::T_NOTEON) + { + const size_t note_i = (e.channel * 255) + (e.data[0] & 0x7F); + //Check, was previously note is on or off + bool wasOn = noteStates[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 = (j->channel * 255) + (j->data[0] & 0x7F); + noteStates[note_i] = false; + } + + for(std::set::iterator j = markAsOn.begin(); j != markAsOn.end(); j++) + noteStates[*j] = true; + } + /***********************************************************************************/ + + events.clear(); + events.insert(events.end(), noteOffs.begin(), noteOffs.end()); + events.insert(events.end(), metas.begin(), metas.end()); + events.insert(events.end(), controllers.begin(), controllers.end()); + events.insert(events.end(), anyOther.begin(), anyOther.end()); +} + +BW_MidiSequencer::BW_MidiSequencer() : + m_interface(NULL), + m_format(Format_MIDI), + m_loopEnabled(false), + m_fullSongTimeLength(0.0), + m_postSongWaitDelay(1.0), + m_loopStartTime(-1.0), + m_loopEndTime(-1.0), + m_tempoMultiplier(1.0), + m_atEnd(false), + m_loopStart(false), + m_loopEnd(false), + m_invalidLoop(false) +{} + +BW_MidiSequencer::~BW_MidiSequencer() +{} + +void BW_MidiSequencer::setInterface(const BW_MidiRtInterface *interface) +{ + // Interface must NOT be NULL + assert(interface); + + //Note ON hook is REQUIRED + assert(interface->rt_noteOn); + //Note OFF hook is REQUIRED + assert(interface->rt_noteOff); + //Note Aftertouch hook is REQUIRED + assert(interface->rt_noteAfterTouch); + //Channel Aftertouch hook is REQUIRED + assert(interface->rt_channelAfterTouch); + //Controller change hook is REQUIRED + assert(interface->rt_controllerChange); + //Patch change hook is REQUIRED + assert(interface->rt_patchChange); + //Pitch bend hook is REQUIRED + assert(interface->rt_pitchBend); + //System Exclusive hook is REQUIRED + assert(interface->rt_systemExclusive); + + m_interface = interface; +} + +BW_MidiSequencer::FileFormat BW_MidiSequencer::getFormat() +{ + return m_format; +} + +const std::vector BW_MidiSequencer::getRawCmfInstruments() +{ + return m_cmfInstruments; +} + +const std::string &BW_MidiSequencer::getErrorString() +{ + return m_errorString; +} + +bool BW_MidiSequencer::getLoopEnabled() +{ + return m_loopEnabled; +} + +void BW_MidiSequencer::setLoopEnabled(bool enabled) +{ + m_loopEnabled = enabled; +} + +const std::string &BW_MidiSequencer::getMusicTitle() +{ + return m_musTitle; +} + +const std::string &BW_MidiSequencer::getMusicCopyright() +{ + return m_musCopyright; +} + +const std::vector &BW_MidiSequencer::getTrackTitles() +{ + return m_musTrackTitles; +} + +const std::vector &BW_MidiSequencer::getMarkers() +{ + return m_musMarkers; +} + +bool BW_MidiSequencer::positionAtEnd() +{ + return m_atEnd; +} + +double BW_MidiSequencer::getTempoMultiplier() +{ + return m_tempoMultiplier; +} + +bool BW_MidiSequencer::buildTrackData(const std::vector > &trackData) +{ + m_fullSongTimeLength = 0.0; + m_loopStartTime = -1.0; + m_loopEndTime = -1.0; + m_musTitle.clear(); + m_musCopyright.clear(); + m_musTrackTitles.clear(); + m_musMarkers.clear(); + m_trackData.clear(); + const size_t trackCount = trackData.size(); + m_trackData.resize(trackCount, MidiTrackQueue()); + + m_invalidLoop = false; + bool gotLoopStart = false, gotLoopEnd = false, gotLoopEventInThisRow = false; + //! Tick position of loop start tag + uint64_t loopStartTicks = 0; + //! Tick position of loop end tag + uint64_t loopEndTicks = 0; + //! Full length of song in ticks + uint64_t ticksSongLength = 0; + //! Cache for error message strign + char error[150]; + + m_currentPosition.track.clear(); + m_currentPosition.track.resize(trackCount); + + //! Caches note on/off states. + bool noteStates[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 + std::vector tempos; + + /* + * 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 < trackCount; ++tk) + { + uint64_t abs_position = 0; + int status = 0; + MidiEvent event; + bool ok = false; + const uint8_t *end = trackData[tk].data() + trackData[tk].size(); + const uint8_t *trackPtr = trackData[tk].data(); + std::memset(noteStates, 0, sizeof(noteStates)); + + //Time delay that follows the first event in the track + { + MidiTrackRow evtPos; + if(m_format == Format_RSXX) + ok = true; + else + evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); + if(!ok) + { + int len = snprintf(error, 150, "buildTrackData: Can't read variable-length value at begin of track %d.\n", (int)tk); + if((len > 0) && (len < 150)) + m_parsingErrorsString += 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 + for(uint8_t chan = 0; chan < 16; chan++) + { + MidiEvent event; + event.type = MidiEvent::T_CTRLCHANGE; + event.channel = chan; + event.data.push_back(121); + event.data.push_back(0); + evtPos.events.push_back(event); + } + + evtPos.absPos = abs_position; + abs_position += evtPos.delay; + m_trackData[tk].push_back(evtPos); + } + + MidiTrackRow evtPos; + do + { + event = parseEvent(&trackPtr, end, status); + if(!event.isValid) + { + int len = snprintf(error, 150, "buildTrackData: Fail to parse event in the track %d.\n", (int)tk); + if((len > 0) && (len < 150)) + m_parsingErrorsString += std::string(error, (size_t)len); + return false; + } + + evtPos.events.push_back(event); + if(event.type == MidiEvent::T_SPECIAL) + { + if(event.subtype == MidiEvent::ST_TEMPOCHANGE) + { + event.absPosition = abs_position; + tempos.push_back(event); + } + else if(!m_invalidLoop && (event.subtype == MidiEvent::ST_LOOPSTART)) + { + /* + * loopStart is invalid when: + * - starts together with loopEnd + * - appears more than one time in same MIDI file + */ + if(gotLoopStart || gotLoopEventInThisRow) + m_invalidLoop = true; + else + { + gotLoopStart = true; + loopStartTicks = abs_position; + } + //In this row we got loop event, register this! + gotLoopEventInThisRow = true; + } + else if(!m_invalidLoop && (event.subtype == MidiEvent::ST_LOOPEND)) + { + /* + * loopEnd is invalid when: + * - starts before loopStart + * - starts together with loopStart + * - appars more than one time in same MIDI file + */ + if(gotLoopEnd || gotLoopEventInThisRow) + m_invalidLoop = true; + else + { + gotLoopEnd = true; + loopEndTicks = abs_position; + } + //In this row we got loop event, register this! + gotLoopEventInThisRow = true; + } + } + + if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event! + { + evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); + if(!ok) + { + /* End of track has been reached! However, there is no EOT event presented */ + event.type = MidiEvent::T_SPECIAL; + event.subtype = MidiEvent::ST_ENDTRACK; + } + } + + if((evtPos.delay > 0) || (event.subtype == MidiEvent::ST_ENDTRACK)) + { + evtPos.absPos = abs_position; + abs_position += evtPos.delay; + evtPos.sortEvents(noteStates); + m_trackData[tk].push_back(evtPos); + evtPos.clear(); + gotLoopEventInThisRow = false; + } + } + while((trackPtr <= end) && (event.subtype != MidiEvent::ST_ENDTRACK)); + + if(ticksSongLength < abs_position) + ticksSongLength = abs_position; + //Set the chain of events begin + if(m_trackData[tk].size() > 0) + m_currentPosition.track[tk].pos = m_trackData[tk].begin(); + } + + if(gotLoopStart && !gotLoopEnd) + { + gotLoopEnd = true; + loopEndTicks = ticksSongLength; + } + + //loopStart must be located before loopEnd! + if(loopStartTicks >= loopEndTicks) + m_invalidLoop = true; + + /********************************************************************************/ + //Calculate time basing on collected tempo events + /********************************************************************************/ + for(size_t tk = 0; tk < trackCount; ++tk) + { + fraction currentTempo = m_tempo; + double time = 0.0; + uint64_t abs_position = 0; + size_t tempo_change_index = 0; + MidiTrackQueue &track = m_trackData[tk]; + if(track.empty()) + continue;//Empty track is useless! + +#ifdef DEBUG_TIME_CALCULATION + std::fprintf(stdout, "\n============Track %" PRIuPTR "=============\n", tk); + std::fflush(stdout); +#endif + + MidiTrackRow *posPrev = &(*(track.begin()));//First element + for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) + { +#ifdef DEBUG_TIME_CALCULATION + bool tempoChanged = false; +#endif + 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].absPosition <= pos.absPos) + { + //Stop points: begin point and tempo change points are before end point + std::vector points; + fraction t; + TempoChangePoint firstPoint = {posPrev->absPos, currentTempo}; + points.push_back(firstPoint); + + //Collect tempo change points between previous and current events + do + { + TempoChangePoint tempoMarker; + MidiEvent &tempoPoint = tempos[tempo_change_index]; + tempoMarker.absPos = tempoPoint.absPosition; + tempoMarker.tempo = m_invDeltaTicks * fraction(ReadBEint(tempoPoint.data.data(), tempoPoint.data.size())); + points.push_back(tempoMarker); + tempo_change_index++; + } + while((tempo_change_index < tempos.size()) && + (tempos[tempo_change_index].absPosition <= pos.absPos)); + + // Re-calculate time delay of previous event + time -= posPrev->timeDelay; + posPrev->timeDelay = 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].absPos - points[i].absPos; + //Time delay between points + t = midDelay * currentTempo; + posPrev->timeDelay += t.value(); + + //Apply next tempo + currentTempo = points[j].tempo; +#ifdef DEBUG_TIME_CALCULATION + tempoChanged = true; +#endif + } + //Then calculate time between last tempo change point and end point + TempoChangePoint tailTempo = points.back(); + uint64_t postDelay = pos.absPos - tailTempo.absPos; + t = postDelay * currentTempo; + posPrev->timeDelay += t.value(); + + //Store Common time delay + posPrev->time = time; + time += posPrev->timeDelay; + } + } + + fraction t = pos.delay * currentTempo; + pos.timeDelay = t.value(); + pos.time = time; + time += pos.timeDelay; + + //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::T_SPECIAL) && (e.subtype == MidiEvent::ST_MARKER)) + { + MIDI_MarkerEntry marker; + marker.label = std::string((char *)e.data.data(), e.data.size()); + marker.pos_ticks = pos.absPos; + marker.pos_time = pos.time; + m_musMarkers.push_back(marker); + } + } + + //Capture loop points time positions + if(!m_invalidLoop) + { + // Set loop points times + if(loopStartTicks == pos.absPos) + m_loopStartTime = pos.time; + else if(loopEndTicks == pos.absPos) + m_loopEndTime = pos.time; + } + +#ifdef DEBUG_TIME_CALCULATION + std::fprintf(stdout, "= %10" PRId64 " = %10f%s\n", pos.absPos, pos.time, tempoChanged ? " <----TEMPO CHANGED" : ""); + std::fflush(stdout); +#endif + + abs_position += pos.delay; + posPrev = &pos; + } + + if(time > m_fullSongTimeLength) + m_fullSongTimeLength = time; + } + + m_fullSongTimeLength += m_postSongWaitDelay; + //Set begin of the music + m_trackBeginPosition = m_currentPosition; + //Initial loop position will begin at begin of track until passing of the loop point + m_loopBeginPosition = m_currentPosition; + + /********************************************************************************/ + //Resolve "hell of all times" of too short drum notes: + //move too short percussion note-offs far far away as possible + /********************************************************************************/ +#if 1 //Use this to record WAVEs for comparison before/after implementing of this + if(m_format == Format_MIDI)//Percussion fix is needed for MIDI only, not for IMF/RSXX or CMF + { + //! Minimal real time in seconds +#define DRUM_NOTE_MIN_TIME 0.03 + //! Minimal ticks count +#define DRUM_NOTE_MIN_TICKS 15 + struct NoteState + { + double delay; + uint64_t delayTicks; + bool isOn; + char ___pad[7]; + } drNotes[255]; + uint16_t banks[16]; + + for(size_t tk = 0; tk < trackCount; ++tk) + { + std::memset(drNotes, 0, sizeof(drNotes)); + std::memset(banks, 0, sizeof(banks)); + MidiTrackQueue &track = m_trackData[tk]; + if(track.empty()) + continue;//Empty track is useless! + + for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) + { + MidiTrackRow &pos = *it; + + for(ssize_t e = 0; e < (ssize_t)pos.events.size(); e++) + { + MidiEvent *et = &pos.events[(size_t)e]; + + /* Set MSB/LSB bank */ + if(et->type == MidiEvent::T_CTRLCHANGE) + { + uint8_t ctrlno = et->data[0]; + uint8_t value = et->data[1]; + switch(ctrlno) + { + case 0: // Set bank msb (GM bank) + banks[et->channel] = uint16_t(uint16_t(value) << 8) | uint16_t(banks[et->channel] & 0x00FF); + break; + case 32: // Set bank lsb (XG bank) + banks[et->channel] = (banks[et->channel] & 0xFF00) | (uint16_t(value) & 0x00FF); + break; + } + continue; + } + + bool percussion = (et->channel == 9) || + banks[et->channel] == 0x7E00 || //XG SFX1/SFX2 channel (16128 signed decimal) + banks[et->channel] == 0x7F00; //XG Percussion channel (16256 signed decimal) + if(!percussion) + continue; + + if(et->type == MidiEvent::T_NOTEON) + { + uint8_t note = et->data[0] & 0x7F; + NoteState &ns = drNotes[note]; + ns.isOn = true; + ns.delay = 0.0; + ns.delayTicks = 0; + } + else if(et->type == MidiEvent::T_NOTEOFF) + { + uint8_t note = et->data[0] & 0x7F; + NoteState &ns = drNotes[note]; + if(ns.isOn) + { + ns.isOn = false; + if(ns.delayTicks < DRUM_NOTE_MIN_TICKS || ns.delay < DRUM_NOTE_MIN_TIME)//If note is too short + { + //Move it into next event position if that possible + for(MidiTrackQueue::iterator itNext = it; + itNext != track.end(); + itNext++) + { + MidiTrackRow &posN = *itNext; + if(ns.delayTicks > DRUM_NOTE_MIN_TICKS && ns.delay > DRUM_NOTE_MIN_TIME) + { + //Put note-off into begin of next event list + posN.events.insert(posN.events.begin(), pos.events[(size_t)e]); + //Renive this event from a current row + pos.events.erase(pos.events.begin() + (int)e); + e--; + break; + } + ns.delay += posN.timeDelay; + ns.delayTicks += posN.delay; + } + } + ns.delay = 0.0; + ns.delayTicks = 0; + } + } + } + + //Append time delays to sustaining notes + for(size_t no = 0; no < 128; no++) + { + NoteState &ns = drNotes[no]; + if(ns.isOn) + { + ns.delay += pos.timeDelay; + ns.delayTicks += pos.delay; + } + } + } + } +#undef DRUM_NOTE_MIN_TIME +#undef DRUM_NOTE_MIN_TICKS + } +#endif + + return true; +} + +bool BW_MidiSequencer::processEvents(bool isSeek) +{ + if(m_currentPosition.track.size() == 0) + m_atEnd = true;//No MIDI track data to play + if(m_atEnd) + return false;//No more events in the queue + + m_loopEnd = false; + const size_t TrackCount = m_currentPosition.track.size(); + const Position RowBeginPosition(m_currentPosition); + +#ifdef DEBUG_TIME_CALCULATION + double maxTime = 0.0; +#endif + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + Position::TrackInfo &track = m_currentPosition.track[tk]; + if((track.lastHandledEvent >= 0) && (track.delay <= 0)) + { + //Check is an end of track has been reached + if(track.pos == m_trackData[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]; +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON)) + CurrentPositionNew.began = true; +#endif + if(isSeek && (evt.type == MidiEvent::T_NOTEON)) + continue; + handleEvent(tk, evt, track.lastHandledEvent); + if(m_loopEnd) + break;//Stop event handling on catching loopEnd event! + } + +#ifdef DEBUG_TIME_CALCULATION + if(maxTime < track.pos->time) + maxTime = track.pos->time; +#endif + // Read next event time (unless the track just ended) + if(track.lastHandledEvent >= 0) + { + track.delay += track.pos->delay; + track.pos++; + } + } + } + +#ifdef DEBUG_TIME_CALCULATION + std::fprintf(stdout, " \r"); + std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); + std::fflush(stdout); +#endif + + // Find shortest delay from all track + uint64_t shortest = 0; + bool shortest_no = true; + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + Position::TrackInfo &track = m_currentPosition.track[tk]; + if((track.lastHandledEvent >= 0) && (shortest_no || track.delay < shortest)) + { + shortest = track.delay; + shortest_no = false; + } + } + + //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest); + + // Schedule the next playevent to be processed after that delay + for(size_t tk = 0; tk < TrackCount; ++tk) + m_currentPosition.track[tk].delay -= shortest; + + fraction t = shortest * m_tempo; + +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(CurrentPositionNew.began) +#endif + m_currentPosition.wait += t.value(); + + //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); + if(m_loopStart) + { + m_loopBeginPosition = RowBeginPosition; + m_loopStart = false; + } + + if(shortest_no || m_loopEnd) + { + //Loop if song end or loop end point has reached + m_loopEnd = false; + shortest = 0; + if(!m_loopEnabled) + { + m_atEnd = true; //Don't handle events anymore + m_currentPosition.wait += m_postSongWaitDelay;//One second delay until stop playing + return true;//We have caugh end here! + } + m_currentPosition = m_loopBeginPosition; + } + + return true;//Has events in queue +} + +BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, const uint8_t *end, int &status) +{ + const uint8_t *&ptr = *pptr; + BW_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::T_SPECIAL; + evt.subtype = MidiEvent::ST_ENDTRACK; + return evt; + } + + unsigned char byte = *(ptr++); + bool ok = false; + + if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx + { + uint64_t length = ReadVarLenEx(pptr, end, ok); + if(!ok || (ptr + length > end)) + { + m_parsingErrorsString += "parseEvent: Can't read SysEx event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + ptr += (size_t)length; + return evt; + } + + if(byte == MidiEvent::T_SPECIAL) + { + // Special event FF + uint8_t evtype = *(ptr++); + uint64_t length = ReadVarLenEx(pptr, end, ok); + if(!ok || (ptr + length > end)) + { + m_parsingErrorsString += "parseEvent: Can't read Special event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + std::string data(length ? (const char *)ptr : 0, (size_t)length); + ptr += (size_t)length; + + evt.type = byte; + evt.subtype = evtype; + evt.data.insert(evt.data.begin(), data.begin(), data.end()); + +#if 0 /* Print all tempo events */ + if(evt.subtype == MidiEvent::ST_TEMPOCHANGE) + { + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "Temp Change: %02X%02X%02X", evt.data[0], evt.data[1], evt.data[2]); + } +#endif + + /* 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.subtype == MidiEvent::ST_COPYRIGHT) + { + if(m_musCopyright.empty()) + { + m_musCopyright = std::string((const char *)evt.data.data(), evt.data.size()); + if(m_interface->onDebugMessage) + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Music copyright: %s", m_musCopyright.c_str()); + } + else if(m_interface->onDebugMessage) + { + std::string str((const char *)evt.data.data(), evt.data.size()); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Extra copyright event: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_SQTRKTITLE) + { + if(m_musTitle.empty()) + { + m_musTitle = std::string((const char *)evt.data.data(), evt.data.size()); + if(m_interface->onDebugMessage) + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Music title: %s", m_musTitle.c_str()); + } + else if(m_interface->onDebugMessage) + { + //TODO: Store track titles and associate them with each track and make API to retreive them + std::string str((const char *)evt.data.data(), evt.data.size()); + m_musTrackTitles.push_back(str); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Track title: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_INSTRTITLE) + { + if(m_interface->onDebugMessage) + { + std::string str((const char *)evt.data.data(), evt.data.size()); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Instrument: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_MARKER) + { + //To lower + for(size_t i = 0; i < data.size(); i++) + { + if(data[i] <= 'Z' && data[i] >= 'A') + data[i] = data[i] - ('Z' - 'z'); + } + + if(data == "loopstart") + { + //Return a custom Loop Start event instead of Marker + evt.subtype = MidiEvent::ST_LOOPSTART; + evt.data.clear();//Data is not needed + return evt; + } + + if(data == "loopend") + { + //Return a custom Loop End event instead of Marker + evt.subtype = MidiEvent::ST_LOOPEND; + evt.data.clear();//Data is not needed + return evt; + } + } + + if(evtype == MidiEvent::ST_ENDTRACK) + status = -1;//Finalize track + + return evt; + } + + // Any normal event (80..EF) + if(byte < 0x80) + { + byte = static_cast(status | 0x80); + ptr--; + } + + //Sys Com Song Select(Song #) [0-127] + if(byte == MidiEvent::T_SYSCOMSNGSEL) + { + if(ptr + 1 > end) + { + m_parsingErrorsString += "parseEvent: Can't read System Command Song Select event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + evt.type = byte; + evt.data.push_back(*(ptr++)); + return evt; + } + + //Sys Com Song Position Pntr [LSB, MSB] + if(byte == MidiEvent::T_SYSCOMSPOSPTR) + { + if(ptr + 2 > end) + { + m_parsingErrorsString += "parseEvent: Can't read System Command Position Pointer event - Unexpected end of track data.\n"; + evt.isValid = 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::T_NOTEOFF://2 byte length + case MidiEvent::T_NOTEON: + case MidiEvent::T_NOTETOUCH: + case MidiEvent::T_CTRLCHANGE: + case MidiEvent::T_WHEEL: + if(ptr + 2 > end) + { + m_parsingErrorsString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + + evt.data.push_back(*(ptr++)); + evt.data.push_back(*(ptr++)); + + if((evType == MidiEvent::T_NOTEON) && (evt.data[1] == 0)) + { + evt.type = MidiEvent::T_NOTEOFF; // Note ON with zero velocity is Note OFF! + } //111'th loopStart controller (RPG Maker and others) + else if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111)) + { + //Change event type to custom Loop Start event and clear data + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_LOOPSTART; + evt.data.clear(); + } + + return evt; + case MidiEvent::T_PATCHCHANGE://1 byte length + case MidiEvent::T_CHANAFTTOUCH: + if(ptr + 1 > end) + { + m_parsingErrorsString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + evt.data.push_back(*(ptr++)); + return evt; + } + + return evt; +} + +void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent &evt, int &status) +{ + if(m_interface->onEvent) + { + m_interface->onEvent(m_interface->onEvent_userData, + evt.type, evt.subtype, evt.channel, + evt.data.data(), evt.data.size()); + } + + if(evt.type == MidiEvent::T_SYSEX || evt.type == MidiEvent::T_SYSEX2) // Ignore SysEx + { + //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length ); + //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/); + m_interface->rt_systemExclusive(m_interface->rtUserData, evt.data.data(), evt.data.size()); + return; + } + + if(evt.type == MidiEvent::T_SPECIAL) + { + // Special event FF + uint8_t evtype = evt.subtype; + uint64_t length = (uint64_t)evt.data.size(); + std::string data(length ? (const char *)evt.data.data() : 0, (size_t)length); + + if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track + { + status = -1; + return; + } + + if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change + { + m_tempo = m_invDeltaTicks * fraction(ReadBEint(evt.data.data(), evt.data.size())); + return; + } + + if(evtype == MidiEvent::ST_MARKER)//Meta event + { + //Do nothing! :-P + return; + } + + if(evtype == MidiEvent::ST_DEVICESWITCH) + { + if(m_interface->rt_deviceSwitch) + m_interface->rt_deviceSwitch(m_interface->rtUserData, tk, data.c_str(), data.size()); + return; + } + + //if(evtype >= 1 && evtype <= 6) + // UI.PrintLn("Meta %d: %s", evtype, data.c_str()); + + //Turn on Loop handling when loop is enabled + if(m_loopEnabled && !m_invalidLoop) + { + if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + m_loopStart = true; + return; + } + + if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + m_loopEnd = true; + return; + } + } + + if(evtype == MidiEvent::ST_RAWOPL) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + if(m_interface->rt_rawOPL) + m_interface->rt_rawOPL(m_interface->rtUserData, static_cast(data[0]), static_cast(data[1])); + return; + } + + return; + } + + // Any normal event (80..EF) + // if(evt.type < 0x80) + // { + // byte = static_cast(CurrentPosition.track[tk].status | 0x80); + // CurrentPosition.track[tk].ptr--; + // } + + if(evt.type == MidiEvent::T_SYSCOMSNGSEL || + evt.type == MidiEvent::T_SYSCOMSPOSPTR) + return; + + /*UI.PrintLn("@%X Track %u: %02X %02X", + CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte, + TrackData[tk][CurrentPosition.track[tk].ptr]);*/ + uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4; + if(m_interface->rt_currentDevice) + midCh += (uint8_t)m_interface->rt_currentDevice(m_interface->rtUserData, tk); + status = evt.type; + + switch(evt.type) + { + case MidiEvent::T_NOTEOFF: // Note off + { + uint8_t note = evt.data[0]; + m_interface->rt_noteOff(m_interface->rtUserData, midCh, note); + break; + } + + case MidiEvent::T_NOTEON: // Note on + { + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + m_interface->rt_noteOn(m_interface->rtUserData, midCh, note, vol); + break; + } + + case MidiEvent::T_NOTETOUCH: // Note touch + { + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + m_interface->rt_noteAfterTouch(m_interface->rtUserData, midCh, note, vol); + break; + } + + case MidiEvent::T_CTRLCHANGE: // Controller change + { + uint8_t ctrlno = evt.data[0]; + uint8_t value = evt.data[1]; + m_interface->rt_controllerChange(m_interface->rtUserData, midCh, ctrlno, value); + break; + } + + case MidiEvent::T_PATCHCHANGE: // Patch change + { + m_interface->rt_patchChange(m_interface->rtUserData, midCh, evt.data[0]); + break; + } + + case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch + { + uint8_t chanat = evt.data[0]; + m_interface->rt_channelAfterTouch(m_interface->rtUserData, midCh, chanat); + break; + } + + case MidiEvent::T_WHEEL: // Wheel/pitch bend + { + uint8_t a = evt.data[0]; + uint8_t b = evt.data[1]; + m_interface->rt_pitchBend(m_interface->rtUserData, midCh, b, a); + break; + } + }//switch +} + +double BW_MidiSequencer::Tick(double s, double granularity) +{ + assert(m_interface);// MIDI output interface must be defined! + + s *= m_tempoMultiplier; +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(CurrentPositionNew.began) +#endif + m_currentPosition.wait -= s; + m_currentPosition.absTimePosition += s; + + int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing + while((m_currentPosition.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) + { + //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); + if(!processEvents()) + break; + if(m_currentPosition.wait <= 0.0) + antiFreezeCounter--; + } + + if(antiFreezeCounter <= 0) + m_currentPosition.wait += 1.0;/* Add extra 1 second when over 10000 events + with zero delay are been detected */ + + if(m_currentPosition.wait < 0.0)//Avoid negative delay value! + return 0.0; + + return m_currentPosition.wait; +} + + +double BW_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 > m_fullSongTimeLength) + { + rewind(); + return 0.0; + } + + bool loopFlagState = m_loopEnabled; + // Turn loop pooints off because it causes wrong position rememberin on a quick seek + m_loopEnabled = 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 + */ + 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 + */ + m_loopStart = false; + + while((m_currentPosition.absTimePosition < seconds) && + (m_currentPosition.absTimePosition < m_fullSongTimeLength)) + { + m_currentPosition.wait -= s; + m_currentPosition.absTimePosition += s; + int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing + double dstWait = m_currentPosition.wait + granualityHalf; + while((m_currentPosition.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(m_currentPosition.wait <= dstWait) + antiFreezeCounter--; + else + { + dstWait = m_currentPosition.wait + granualityHalf; + antiFreezeCounter = 10000; + } + } + if(antiFreezeCounter <= 0) + m_currentPosition.wait += 1.0;/* Add extra 1 second when over 10000 events + with zero delay are been detected */ + } + + if(m_currentPosition.wait < 0.0) + m_currentPosition.wait = 0.0; + + m_loopEnabled = loopFlagState; + return m_currentPosition.wait; +} + +double BW_MidiSequencer::tell() +{ + return m_currentPosition.absTimePosition; +} + +double BW_MidiSequencer::timeLength() +{ + return m_fullSongTimeLength; +} + +double BW_MidiSequencer::getLoopStart() +{ + return m_loopStartTime; +} + +double BW_MidiSequencer::getLoopEnd() +{ + return m_loopEndTime; +} + +void BW_MidiSequencer::rewind() +{ + m_currentPosition = m_trackBeginPosition; + m_atEnd = false; + m_loopStart = true; + m_loopEnd = false; + //invalidLoop = false;//No more needed here as this flag is set on load time +} + +void BW_MidiSequencer::setTempo(double tempo) +{ + m_tempoMultiplier = tempo; +} + +bool BW_MidiSequencer::loadMIDI(const std::string &filename) +{ + FileAndMemReader file; + file.openFile(filename.c_str()); + if(!loadMIDI(file)) + return false; + return true; +} + +bool BW_MidiSequencer::loadMIDI(const void *data, size_t size) +{ + FileAndMemReader file; + file.openData(data, size); + return loadMIDI(file); +} + +template +class BufferGuard +{ + T *m_ptr; +public: + BufferGuard() : m_ptr(NULL) + {} + + ~BufferGuard() + { + set(); + } + + void set(T *p = NULL) + { + if(m_ptr) + free(m_ptr); + m_ptr = p; + } +}; + +bool BW_MidiSequencer::loadMIDI(FileAndMemReader &fr) +{ + size_t fsize; + BW_MidiSequencer_UNUSED(fsize); + std::vector > rawTrackData; + //! Temp buffer for conversion + BufferGuard cvt_buf; + m_parsingErrorsString.clear(); + + assert(m_interface);// MIDI output interface must be defined! + + if(!fr.isValid()) + { + m_errorString = "Invalid data stream!\n"; +#ifndef _WIN32 + m_errorString += std::strerror(errno); +#endif + return false; + } + + m_atEnd = false; + m_loopStart = true; + m_invalidLoop = false; + + m_format = Format_MIDI; + + bool is_GMF = false; // GMD/MUS files (ScummVM) + //bool is_MUS = false; // MUS/DMX files (Doom) + bool is_IMF = false; // IMF + bool is_CMF = false; // Creative Music format (CMF/CTMF) + bool is_RSXX = false; // RSXX, such as Cartooners + + const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[HeaderSize] = ""; + size_t DeltaTicks = 192, TrackCount = 1; + +riffskip: + fsize = fr.read(headerBuf, 1, HeaderSize); + + if(std::memcmp(headerBuf, "RIFF", 4) == 0) + { + fr.seek(6l, FileAndMemReader::CUR); + goto riffskip; + } + + if(std::memcmp(headerBuf, "GMF\x1", 4) == 0) + { + // GMD/MUS files (ScummVM) + fr.seek(7 - static_cast(HeaderSize), FileAndMemReader::CUR); + is_GMF = true; + } +#ifndef BWMIDI_DISABLE_MUS_SUPPORT + else if(std::memcmp(headerBuf, "MUS\x1A", 4) == 0) + { + // MUS/DMX files (Doom) + size_t mus_len = fr.fileSize(); + fr.seek(0, FileAndMemReader::SET); + uint8_t *mus = (uint8_t *)malloc(mus_len); + if(!mus) + { + m_errorString = "Out of memory!"; + return false; + } + fr.read(mus, 1, mus_len); + //Close source stream + fr.close(); + + uint8_t *mid = NULL; + uint32_t mid_len = 0; + int m2mret = Convert_mus2midi(mus, static_cast(mus_len), + &mid, &mid_len, 0); + if(mus) + free(mus); + if(m2mret < 0) + { + m_errorString = "Invalid MUS/DMX data format!"; + return false; + } + cvt_buf.set(mid); + //Open converted MIDI file + fr.openData(mid, static_cast(mid_len)); + //Re-Read header again! + goto riffskip; + } +#endif //BWMIDI_DISABLE_MUS_SUPPORT + +#ifndef BWMIDI_DISABLE_XMI_SUPPORT + else if(std::memcmp(headerBuf, "FORM", 4) == 0) + { + if(std::memcmp(headerBuf + 8, "XDIR", 4) != 0) + { + fr.close(); + m_errorString = fr.fileName() + ": Invalid format\n"; + return false; + } + + size_t mus_len = fr.fileSize(); + fr.seek(0, FileAndMemReader::SET); + + uint8_t *mus = (uint8_t*)malloc(mus_len); + if(!mus) + { + m_errorString = "Out of memory!"; + return false; + } + fr.read(mus, 1, mus_len); + //Close source stream + fr.close(); + + uint8_t *mid = NULL; + uint32_t mid_len = 0; + int m2mret = Convert_xmi2midi(mus, static_cast(mus_len), + &mid, &mid_len, XMIDI_CONVERT_NOCONVERSION); + if(mus) free(mus); + if(m2mret < 0) + { + m_errorString = "Invalid XMI data format!"; + return false; + } + cvt_buf.set(mid); + //Open converted MIDI file + fr.openData(mid, static_cast(mid_len)); + //Re-Read header again! + goto riffskip; + } +#endif //BWMIDI_DISABLE_XMI_SUPPORT + + else if(std::memcmp(headerBuf, "CTMF", 4) == 0) + { + // Creative Music Format (CMF). + // When playing CTMF files, use the following commandline: + // adlmidi song8.ctmf -p -v 1 1 0 + // i.e. enable percussion mode, deeper vibrato, and use only 1 card. + is_CMF = true; + m_format = Format_CMF; + //unsigned version = ReadLEint(HeaderBuf+4, 2); + uint64_t ins_start = ReadLEint(headerBuf + 6, 2); + uint64_t mus_start = ReadLEint(headerBuf + 8, 2); + //unsigned deltas = ReadLEint(HeaderBuf+10, 2); + uint64_t ticks = ReadLEint(headerBuf + 12, 2); + // Read title, author, remarks start offsets in file + fr.read(headerBuf, 1, 6); + //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; + fr.seek(16, FileAndMemReader::CUR); // Skip the channels-in-use table + fr.read(headerBuf, 1, 4); + uint64_t ins_count = ReadLEint(headerBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); + fr.seek(static_cast(ins_start), FileAndMemReader::SET); + + m_cmfInstruments.reserve(static_cast(ins_count)); + for(uint64_t i = 0; i < ins_count; ++i) + { + CmfInstrument inst; + fr.read(inst.data, 1, 16); + m_cmfInstruments.push_back(inst); + } + + fr.seeku(mus_start, FileAndMemReader::SET); + TrackCount = 1; + DeltaTicks = (size_t)ticks; + } + else + { + // Try to identify RSXX format + if(headerBuf[0] == 0x7D) + { + fr.seek(0x6D, FileAndMemReader::SET); + fr.read(headerBuf, 6, 1); + if(std::memcmp(headerBuf, "rsxx}u", 6) == 0) + { + is_RSXX = true; + m_format = Format_RSXX; + fr.seek(0x7D, FileAndMemReader::SET); + TrackCount = 1; + DeltaTicks = 60; + } + } + + // Try parsing as an IMF file + if(!is_RSXX) + { + do + { + uint8_t raw[4]; + size_t end = static_cast(headerBuf[0]) + 256 * static_cast(headerBuf[1]); + + if(!end || (end & 3)) + break; + + size_t backup_pos = fr.tell(); + int64_t sum1 = 0, sum2 = 0; + fr.seek(2, FileAndMemReader::SET); + + for(unsigned n = 0; n < 42; ++n) + { + if(fr.read(raw, 1, 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; + } + + fr.seek(static_cast(backup_pos), FileAndMemReader::SET); + + if(sum1 > sum2) + { + is_IMF = true; + m_format = Format_IMF; + DeltaTicks = 1; + } + } while(false); + } + + if(!is_IMF && !is_RSXX) + { + if(std::memcmp(headerBuf, "MThd\0\0\0\6", 8) != 0) + { + fr.close(); + m_errorString = fr.fileName() + ": Invalid format, Header signature is unknown!\n"; + return false; + } + + /*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/ + TrackCount = (size_t)ReadBEint(headerBuf + 10, 2); + DeltaTicks = (size_t)ReadBEint(headerBuf + 12, 2); + } + } + + rawTrackData.clear(); + rawTrackData.resize(TrackCount, std::vector()); + m_invDeltaTicks = fraction(1, 1000000l * static_cast(DeltaTicks)); + if(is_CMF || is_RSXX) + m_tempo = fraction(1, static_cast(DeltaTicks)); + else + m_tempo = fraction(1, static_cast(DeltaTicks) * 2); + static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; + size_t totalGotten = 0; + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + // Read track header + size_t TrackLength; + + if(is_IMF) + { + //std::fprintf(stderr, "Reading IMF file...\n"); + size_t end = static_cast(headerBuf[0]) + 256 * static_cast(headerBuf[1]); + unsigned IMF_tempo = 1428; + static const unsigned char imf_tempo[] = {0x0,//Zero delay! + MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4, + static_cast(IMF_tempo >> 24), + static_cast(IMF_tempo >> 16), + static_cast(IMF_tempo >> 8), + static_cast(IMF_tempo) + }; + rawTrackData[tk].insert(rawTrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo)); + rawTrackData[tk].push_back(0x00); + fr.seek(2, FileAndMemReader::SET); + + while(fr.tell() < end && !fr.eof()) + { + uint8_t special_event_buf[5]; + uint8_t raw[4]; + special_event_buf[0] = MidiEvent::T_SPECIAL; + special_event_buf[1] = MidiEvent::ST_RAWOPL; + special_event_buf[2] = 0x02; + if(fr.read(raw, 1, 4) != 4) + break; + special_event_buf[3] = raw[0]; // port index + special_event_buf[4] = raw[1]; // port value + uint32_t delay = static_cast(raw[2]); + delay += 256 * static_cast(raw[3]); + totalGotten += 4; + //if(special_event_buf[3] <= 8) continue; + //fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay); + rawTrackData[tk].insert(rawTrackData[tk].end(), special_event_buf, special_event_buf + 5); + //if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) ); + if(delay >> 14) + rawTrackData[tk].push_back(0x80 | ((delay >> 14) & 0x7F)); + if(delay >> 7) + rawTrackData[tk].push_back(0x80 | ((delay >> 7) & 0x7F)); + rawTrackData[tk].push_back(((delay >> 0) & 0x7F)); + } + + rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); + //CurrentPosition.track[tk].delay = 0; + //CurrentPosition.began = true; + } + else + { + // Take the rest of the file + if(is_GMF || is_CMF || is_RSXX) + { + size_t pos = fr.tell(); + fr.seek(0, FileAndMemReader::END); + TrackLength = fr.tell() - pos; + fr.seek(static_cast(pos), FileAndMemReader::SET); + } + //else if(is_MUS) // Read TrackLength from file position 4 + //{ + // size_t pos = fr.tell(); + // fr.seek(4, FileAndMemReader::SET); + // TrackLength = static_cast(fr.getc()); + // TrackLength += static_cast(fr.getc() << 8); + // fr.seek(static_cast(pos), FileAndMemReader::SET); + //} + else + { + fsize = fr.read(headerBuf, 1, 8); + if(std::memcmp(headerBuf, "MTrk", 4) != 0) + { + fr.close(); + m_errorString = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; + return false; + } + TrackLength = (size_t)ReadBEint(headerBuf + 4, 4); + } + + // Read track data + rawTrackData[tk].resize(TrackLength); + fsize = fr.read(&rawTrackData[tk][0], 1, TrackLength); + totalGotten += fsize; + + if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag. + rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); + if(is_RSXX)//Finalize raw track data with a zero + rawTrackData[tk].push_back(0); + + //bool ok = false; + //// Read next event time + //uint64_t tkDelay = ReadVarLenEx(tk, ok); + //if(ok) + // CurrentPosition.track[tk].delay = tkDelay; + //else + //{ + // std::stringstream msg; + // msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")"; + // ADLMIDI_ErrorString = msg.str(); + // return false; + //} + } + } + + for(size_t tk = 0; tk < TrackCount; ++tk) + totalGotten += rawTrackData[tk].size(); + + if(totalGotten == 0) + { + m_errorString = fr.fileName() + ": Empty track data"; + return false; + } + + //Build new MIDI events table + if(!buildTrackData(rawTrackData)) + { + m_errorString = fr.fileName() + ": MIDI data parsing error has occouped!\n" + m_parsingErrorsString; + return false; + } + + return true; +} -- cgit v1.2.3 From 2f23710e928876c785f19689ac13f81511c0c7ae Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:24:31 +0300 Subject: Remove second Bend coefficient I have tested 14'th bank on DosBox emulator in current state, and seems everything is fine, no any hi-hats distortions. --- src/adlmidi_midiplay.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 5b7f0d9..f539b16 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -953,11 +953,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, if(vibrato && (!d || d->vibdelay >= Ch[MidCh].vibdelay)) bend += static_cast(vibrato) * Ch[MidCh].vibdepth * std::sin(Ch[MidCh].vibpos); -#ifdef ADLMIDI_USE_DOSBOX_OPL -# define BEND_COEFFICIENT 172.00093 -#else -# define BEND_COEFFICIENT 172.4387 -#endif +#define BEND_COEFFICIENT 172.4387 opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (currentTone + bend + phase))); #undef BEND_COEFFICIENT if(hooks.onNote) -- cgit v1.2.3 From 0e6debe44d6e03c495d670762c1a7d6e8acb12c8 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:38:06 +0300 Subject: Fixed C++98 bulid lround is C99 and C++11, not C++98! --- src/adlmidi_load.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 3e51b77..d7b1e52 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -104,7 +104,7 @@ static void cvt_FMIns_to_generic(WOPLI &ins, const adlinsdata2 &in) ins.second_voice_detune = -1; else { - long value = lround(voice2_fine_tune * (1000.0 / 15.625)); + long value = static_cast(round(voice2_fine_tune * (1000.0 / 15.625))); value = (value < -128) ? -128 : value; value = (value > +127) ? +127 : value; ins.second_voice_detune = static_cast(value); -- cgit v1.2.3 From 51ffa56a7f30679ecb40d76725a94b9c954546a1 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:41:54 +0300 Subject: Attempt to fix a Windows build --- src/midi_sequencer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/midi_sequencer.h b/src/midi_sequencer.h index d3b542e..2a2b543 100644 --- a/src/midi_sequencer.h +++ b/src/midi_sequencer.h @@ -33,7 +33,7 @@ extern "C" { #include #include -struct BW_MidiRtInterface +typedef struct { //! 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); @@ -82,7 +82,7 @@ struct BW_MidiRtInterface typedef uint64_t (*RtCurrentDevice)(void *userdata, size_t track); RtCurrentDevice rt_currentDevice; -}; +} BW_MidiRtInterface; #ifdef __cplusplus } -- cgit v1.2.3 From b9304bc1342ccfa645ffb422fbaad1965e64178f Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:43:55 +0300 Subject: Second attempt to fix Windows build --- src/midi_sequencer.hpp | 2 +- src/midi_sequencer_impl.hpp | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp index 370068b..a8e04d0 100644 --- a/src/midi_sequencer.hpp +++ b/src/midi_sequencer.hpp @@ -351,7 +351,7 @@ public: * @brief Sets the RT interface * @param interface Pre-Initialized interface structure (pointer will be taken) */ - void setInterface(const BW_MidiRtInterface *interface); + void setInterface(const BW_MidiRtInterface *intrf); /** * @brief Returns file format type of currently loaded file diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 1e4e643..1d627fe 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -252,29 +252,29 @@ BW_MidiSequencer::BW_MidiSequencer() : BW_MidiSequencer::~BW_MidiSequencer() {} -void BW_MidiSequencer::setInterface(const BW_MidiRtInterface *interface) +void BW_MidiSequencer::setInterface(const BW_MidiRtInterface *intrf) { // Interface must NOT be NULL - assert(interface); + assert(intrf); //Note ON hook is REQUIRED - assert(interface->rt_noteOn); + assert(intrf->rt_noteOn); //Note OFF hook is REQUIRED - assert(interface->rt_noteOff); + assert(intrf->rt_noteOff); //Note Aftertouch hook is REQUIRED - assert(interface->rt_noteAfterTouch); + assert(intrf->rt_noteAfterTouch); //Channel Aftertouch hook is REQUIRED - assert(interface->rt_channelAfterTouch); + assert(intrf->rt_channelAfterTouch); //Controller change hook is REQUIRED - assert(interface->rt_controllerChange); + assert(intrf->rt_controllerChange); //Patch change hook is REQUIRED - assert(interface->rt_patchChange); + assert(intrf->rt_patchChange); //Pitch bend hook is REQUIRED - assert(interface->rt_pitchBend); + assert(intrf->rt_pitchBend); //System Exclusive hook is REQUIRED - assert(interface->rt_systemExclusive); + assert(intrf->rt_systemExclusive); - m_interface = interface; + m_interface = intrf; } BW_MidiSequencer::FileFormat BW_MidiSequencer::getFormat() -- cgit v1.2.3 From 96fef8f4aa43bc60b54e765da20d465a6830e0c7 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:53:22 +0300 Subject: Fix MSVC's warning C4267 in the file reader --- src/file_reader.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/file_reader.hpp b/src/file_reader.hpp index c2cfe7b..0f630df 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -285,10 +285,10 @@ public: return 0; if(!m_fp) return m_mp_size; //Size of memory block is well known - size_t old_pos = tell(); - seek(0, FileAndMemReader::END); - size_t file_size = tell(); - seek(old_pos, FileAndMemReader::SET); + size_t old_pos = this->tell(); + seek(0l, FileAndMemReader::END); + size_t file_size = this->tell(); + seek(static_cast(old_pos), FileAndMemReader::SET); return file_size; } }; -- cgit v1.2.3 From f20f618327deba5091a384eb749fd4b4f7c10a2f Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 05:01:50 +0300 Subject: Fixed build of MIDI sequencer out of pre-included private ADLMIDI's header --- src/adlmidi_private.hpp | 2 +- src/adlmidi_sequencer.cpp | 5 +++-- src/midi_sequencer_impl.hpp | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 446bae0..06d21ab 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -134,7 +134,7 @@ typedef int32_t ssize_t; #include "file_reader.hpp" #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -// Rename class to avoid ABI conflicts +// Rename class to avoid ABI collisions #define BW_MidiSequencer AdlMidiSequencer #include "midi_sequencer.hpp" typedef BW_MidiSequencer MidiSequencer; diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp index 8bc3569..fcc1380 100644 --- a/src/adlmidi_sequencer.cpp +++ b/src/adlmidi_sequencer.cpp @@ -23,11 +23,12 @@ #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -#include "adlmidi_private.hpp" - +// Rename class to avoid ABI collisions +#define BW_MidiSequencer AdlMidiSequencer // Inlucde MIDI sequencer class implementation #include "midi_sequencer_impl.hpp" +#include "adlmidi_private.hpp" /**************************************************** * Real-Time MIDI calls proxies * diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 1d627fe..8c0e3dd 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include -- cgit v1.2.3 From 86038a9ccafd7e848e67535f7995d86601cb1e0f Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 05:07:47 +0300 Subject: Remove useless "_parent" pointer from OPL3 class --- src/adlmidi_private.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 06d21ab..b80d2f2 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -207,7 +207,6 @@ inline int32_t adl_cvtU32(int32_t x) return (uint32_t)adl_cvtS32(x) - (uint32_t)INT32_MIN; } -class MIDIplay; struct ADL_MIDIPlayer; class OPL3 { @@ -216,7 +215,6 @@ public: friend class AdlInstrumentTester; uint32_t NumChannels; char ____padding[4]; - ADL_MIDIPlayer *_parent; #ifndef ADLMIDI_HW_OPL std::vector > cardsOP2; #endif -- cgit v1.2.3 From 6b0ca0b306b23a313491742f0c78debb49a037e9 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 05:47:20 +0300 Subject: Attempt to fix build on Windows --- src/file_reader.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/file_reader.hpp b/src/file_reader.hpp index 0f630df..f601cee 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -26,9 +26,10 @@ #ifndef FILE_AND_MEM_READER_HHHH #define FILE_AND_MEM_READER_HHHH -#include -#include +#include // std::string +#include // std::fopen, std::fread, std::fseek, std::ftell, std::fclose, std::feof #ifdef _WIN32 +#include // std::strlen #include // MultiByteToWideChar #endif @@ -93,7 +94,7 @@ public: m_fp = std::fopen(path, "rb"); #else wchar_t widePath[MAX_PATH]; - int size = MultiByteToWideChar(CP_UTF8, 0, path, (int)std::strlen(path), widePath, MAX_PATH); + int size = MultiByteToWideChar(CP_UTF8, 0, path, static_cast(std::strlen(path)), widePath, MAX_PATH); widePath[size] = '\0'; m_fp = _wfopen(widePath, L"rb"); #endif -- cgit v1.2.3 From cca01359c3f7acc5cfd016d59561fae41b72793e Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 05:49:47 +0300 Subject: Fix for "NOMINMAX" redefinition warning on Windows --- src/adlmidi_private.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index b80d2f2..058a431 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -39,7 +39,7 @@ #define ADLMIDI_UNSTABLE_API #ifdef _WIN32 -#define NOMINMAX +#define NOMINMAX 1 #endif #if defined(_WIN32) && !defined(__WATCOMC__) @@ -51,7 +51,7 @@ typedef __int64 ssize_t; # else typedef __int32 ssize_t; # endif -# define NOMINMAX //Don't override std::min and std::max +# define NOMINMAX 1 //Don't override std::min and std::max # else # ifdef _WIN64 typedef int64_t ssize_t; -- cgit v1.2.3 From 34f55ea7bad2f177d1a50708f7c269d90d9aa2f0 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 06:00:23 +0300 Subject: Fix MSVC build because of missing `ssize_t` typedef --- src/midi_sequencer_impl.hpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 8c0e3dd..6f62e0b 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -27,9 +27,24 @@ #include #include #include - #include +#if defined(_WIN32) && !defined(__WATCOMC__) +# ifdef _MSC_VER +# ifdef _WIN64 +typedef __int64 ssize_t; +# else +typedef __int32 ssize_t; +# endif +# else +# ifdef _WIN64 +typedef int64_t ssize_t; +# else +typedef int32_t ssize_t; +# endif +# endif +#endif + #ifndef BWMIDI_DISABLE_MUS_SUPPORT #include "cvt_mus2mid.hpp" #endif//MUS -- cgit v1.2.3 From 1b3d6d1a95337e7c87af5eac139842f0e454f768 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 07:20:08 +0300 Subject: MidiPlay: print loop points in pretty format --- utils/midiplay/adlmidiplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp index be4c7ee..ded25ea 100644 --- a/utils/midiplay/adlmidiplay.cpp +++ b/utils/midiplay/adlmidiplay.cpp @@ -603,7 +603,7 @@ int main(int argc, char **argv) { std::fprintf(stdout, " - Loop is turned %s\n", loopEnabled ? "ON" : "OFF"); if(loopStart >= 0.0 && loopEnd >= 0.0) - std::fprintf(stdout, " - Has loop points: %10f ... %10f\n", loopStart, loopEnd); + std::fprintf(stdout, " - Has loop points: %s ... %s\n", loopStartHMS, loopEndHMS); std::fprintf(stdout, "\n==========================================\n"); flushout(stdout); -- cgit v1.2.3 From 2fcf34cdc43a7ef546887448f49c4cfa084ba935 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 08:04:31 +0300 Subject: MIDI Sequencer: Small clean-up --- src/adlmidi.cpp | 5 +- src/midi_sequencer.hpp | 17 ++++--- src/midi_sequencer_impl.hpp | 109 +++++++++++++++++++++++++++++--------------- 3 files changed, 88 insertions(+), 43 deletions(-) diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 69755d3..efd716c 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -517,7 +517,10 @@ ADLMIDI_EXPORT int adl_switchEmulator(struct ADL_MIDIPlayer *device, int emulato if(device) { MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - if(play && (emulator >= 0) && (emulator < ADLMIDI_EMU_end)) + assert(play); + if(!play) + return -1; + if((emulator >= 0) && (emulator < ADLMIDI_EMU_end)) { play->m_setup.emulator = emulator; adl_reset(device); diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp index a8e04d0..89b4619 100644 --- a/src/midi_sequencer.hpp +++ b/src/midi_sequencer.hpp @@ -189,7 +189,8 @@ class BW_MidiSequencer { //! Was track began playing bool began; - char padding[7]; + //! Reserved + char __padding[7]; //! Waiting time before next event in seconds double wait; //! Absolute time position on the track in seconds @@ -197,15 +198,19 @@ class BW_MidiSequencer //! Track information struct TrackInfo { - size_t ptr; //! Delay to next event in a track uint64_t delay; //! Last handled event type - int lastHandledEvent; - char padding2[4]; + int32_t lastHandledEvent; + //! Reserved + char __padding2[4]; //! MIDI Events queue position iterator MidiTrackQueue::iterator pos; - TrackInfo(): ptr(0), delay(0), lastHandledEvent(0) {} + + TrackInfo() : + delay(0), + lastHandledEvent(0) + {} }; std::vector track; Position(): began(false), wait(0.0), absTimePosition(0.0), track() @@ -243,7 +248,7 @@ class BW_MidiSequencer * @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, int &status); + void handleEvent(size_t tk, const MidiEvent &evt, int32_t &status); public: /** diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 6f62e0b..b3bfefa 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -59,7 +59,7 @@ typedef int32_t ssize_t; * @param nbytes Count of bytes to parse integer * @return Extracted unsigned integer */ -static inline uint64_t ReadBEint(const void *buffer, size_t nbytes) +static inline uint64_t readBEint(const void *buffer, size_t nbytes) { uint64_t result = 0; const unsigned char *data = reinterpret_cast(buffer); @@ -76,7 +76,7 @@ static inline uint64_t ReadBEint(const void *buffer, size_t nbytes) * @param nbytes Count of bytes to parse integer * @return Extracted unsigned integer */ -static inline uint64_t ReadLEint(const void *buffer, size_t nbytes) +static inline uint64_t readLEint(const void *buffer, size_t nbytes) { uint64_t result = 0; const unsigned char *data = reinterpret_cast(buffer); @@ -573,7 +573,7 @@ bool BW_MidiSequencer::buildTrackData(const std::vector > & TempoChangePoint tempoMarker; MidiEvent &tempoPoint = tempos[tempo_change_index]; tempoMarker.absPos = tempoPoint.absPosition; - tempoMarker.tempo = m_invDeltaTicks * fraction(ReadBEint(tempoPoint.data.data(), tempoPoint.data.size())); + tempoMarker.tempo = m_invDeltaTicks * fraction(readBEint(tempoPoint.data.data(), tempoPoint.data.size())); points.push_back(tempoMarker); tempo_change_index++; } @@ -839,7 +839,7 @@ bool BW_MidiSequencer::processEvents(bool isSeek) #ifdef DEBUG_TIME_CALCULATION std::fprintf(stdout, " \r"); - std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); + std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, m_currentPosition.absTimePosition); std::fflush(stdout); #endif @@ -1107,7 +1107,7 @@ BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, c return evt; } -void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent &evt, int &status) +void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent &evt, int32_t &status) { if(m_interface->onEvent) { @@ -1139,7 +1139,7 @@ void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change { - m_tempo = m_invDeltaTicks * fraction(ReadBEint(evt.data.data(), evt.data.size())); + m_tempo = m_invDeltaTicks * fraction(readBEint(evt.data.data(), evt.data.size())); return; } @@ -1463,12 +1463,17 @@ bool BW_MidiSequencer::loadMIDI(FileAndMemReader &fr) bool is_CMF = false; // Creative Music format (CMF/CTMF) bool is_RSXX = false; // RSXX, such as Cartooners - const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 - char headerBuf[HeaderSize] = ""; + const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[headerSize] = ""; size_t DeltaTicks = 192, TrackCount = 1; riffskip: - fsize = fr.read(headerBuf, 1, HeaderSize); + fsize = fr.read(headerBuf, 1, headerSize); + if(fsize < headerSize) + { + m_errorString = "Unexpected end of file at header!\n"; + return false; + } if(std::memcmp(headerBuf, "RIFF", 4) == 0) { @@ -1479,7 +1484,7 @@ riffskip: if(std::memcmp(headerBuf, "GMF\x1", 4) == 0) { // GMD/MUS files (ScummVM) - fr.seek(7 - static_cast(HeaderSize), FileAndMemReader::CUR); + fr.seek(7 - static_cast(headerSize), FileAndMemReader::CUR); is_GMF = true; } #ifndef BWMIDI_DISABLE_MUS_SUPPORT @@ -1494,7 +1499,14 @@ riffskip: m_errorString = "Out of memory!"; return false; } - fr.read(mus, 1, mus_len); + fsize = fr.read(mus, 1, mus_len); + if(fsize < mus_len) + { + fr.close(); + m_errorString = "Failed to read MUS file data!\n"; + return false; + } + //Close source stream fr.close(); @@ -1536,7 +1548,14 @@ riffskip: m_errorString = "Out of memory!"; return false; } - fr.read(mus, 1, mus_len); + fsize = fr.read(mus, 1, mus_len); + if(fsize < mus_len) + { + fr.close(); + m_errorString = "Failed to read XMI file data!\n"; + return false; + } + //Close source stream fr.close(); @@ -1567,23 +1586,43 @@ riffskip: is_CMF = true; m_format = Format_CMF; //unsigned version = ReadLEint(HeaderBuf+4, 2); - uint64_t ins_start = ReadLEint(headerBuf + 6, 2); - uint64_t mus_start = ReadLEint(headerBuf + 8, 2); + uint64_t ins_start = readLEint(headerBuf + 6, 2); + uint64_t mus_start = readLEint(headerBuf + 8, 2); //unsigned deltas = ReadLEint(HeaderBuf+10, 2); - uint64_t ticks = ReadLEint(headerBuf + 12, 2); + uint64_t ticks = readLEint(headerBuf + 12, 2); // Read title, author, remarks start offsets in file - fr.read(headerBuf, 1, 6); + fsize = fr.read(headerBuf, 1, 6); + if(fsize < 6) + { + fr.close(); + m_errorString = "Unexpected file ending on attempt to read CTMF header!"; + return false; + } + //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; fr.seek(16, FileAndMemReader::CUR); // Skip the channels-in-use table - fr.read(headerBuf, 1, 4); - uint64_t ins_count = ReadLEint(headerBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); + fsize = fr.read(headerBuf, 1, 4); + if(fsize < 4) + { + fr.close(); + m_errorString = "Unexpected file ending on attempt to read CMF instruments block header!"; + return false; + } + + uint64_t ins_count = readLEint(headerBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); fr.seek(static_cast(ins_start), FileAndMemReader::SET); m_cmfInstruments.reserve(static_cast(ins_count)); for(uint64_t i = 0; i < ins_count; ++i) { CmfInstrument inst; - fr.read(inst.data, 1, 16); + fsize = fr.read(inst.data, 1, 16); + if(fsize < 16) + { + fr.close(); + m_errorString = "Unexpected file ending on attempt to read CMF instruments raw data!"; + return false; + } m_cmfInstruments.push_back(inst); } @@ -1597,7 +1636,7 @@ riffskip: if(headerBuf[0] == 0x7D) { fr.seek(0x6D, FileAndMemReader::SET); - fr.read(headerBuf, 6, 1); + fr.read(headerBuf, 1, 6); if(std::memcmp(headerBuf, "rsxx}u", 6) == 0) { is_RSXX = true; @@ -1656,8 +1695,8 @@ riffskip: } /*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/ - TrackCount = (size_t)ReadBEint(headerBuf + 10, 2); - DeltaTicks = (size_t)ReadBEint(headerBuf + 12, 2); + TrackCount = (size_t)readBEint(headerBuf + 10, 2); + DeltaTicks = (size_t)readBEint(headerBuf + 12, 2); } } @@ -1674,7 +1713,7 @@ riffskip: for(size_t tk = 0; tk < TrackCount; ++tk) { // Read track header - size_t TrackLength; + size_t trackLength; if(is_IMF) { @@ -1728,32 +1767,30 @@ riffskip: { size_t pos = fr.tell(); fr.seek(0, FileAndMemReader::END); - TrackLength = fr.tell() - pos; + trackLength = fr.tell() - pos; fr.seek(static_cast(pos), FileAndMemReader::SET); } - //else if(is_MUS) // Read TrackLength from file position 4 - //{ - // size_t pos = fr.tell(); - // fr.seek(4, FileAndMemReader::SET); - // TrackLength = static_cast(fr.getc()); - // TrackLength += static_cast(fr.getc() << 8); - // fr.seek(static_cast(pos), FileAndMemReader::SET); - //} else { fsize = fr.read(headerBuf, 1, 8); - if(std::memcmp(headerBuf, "MTrk", 4) != 0) + if((fsize < 8) || (std::memcmp(headerBuf, "MTrk", 4) != 0)) { fr.close(); m_errorString = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; return false; } - TrackLength = (size_t)ReadBEint(headerBuf + 4, 4); + trackLength = (size_t)readBEint(headerBuf + 4, 4); } // Read track data - rawTrackData[tk].resize(TrackLength); - fsize = fr.read(&rawTrackData[tk][0], 1, TrackLength); + rawTrackData[tk].resize(trackLength); + fsize = fr.read(&rawTrackData[tk][0], 1, trackLength); + if(fsize < trackLength) + { + fr.close(); + m_errorString = fr.fileName() + ": Unexpected file ending while getting raw track data!\n"; + return false; + } totalGotten += fsize; if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag. -- cgit v1.2.3 From 85b4a6621ec960314b568cc767e77a20e178c5de Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 08:08:52 +0300 Subject: FileReader: directly assign SET/CUR/END enum into SEEK_* macros to match --- src/file_reader.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/file_reader.hpp b/src/file_reader.hpp index f601cee..e12e591 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -57,11 +57,11 @@ public: enum relTo { //! At begin position - SET = 0, + SET = SEEK_SET, //! At current position - CUR = 1, + CUR = SEEK_CUR, //! At end position - END = 2 + END = SEEK_END }; /** -- cgit v1.2.3 From 8917fd60694ef2a0757d6e250faea37482b34956 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 08:58:49 +0300 Subject: Small clean-up in a custom bank loading function --- src/adlmidi_load.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index d7b1e52..41f1837 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -173,9 +173,8 @@ bool MIDIplay::LoadBank(FileAndMemReader &fr) } // Read complete bank file into the memory - fr.seek(0, SEEK_END); - fsize = fr.tell(); - fr.seek(0, SEEK_SET); + fsize = fr.fileSize(); + fr.seek(0, FileAndMemReader::SET); // Allocate necessary memory block raw_file_data = (char*)malloc(fsize); if(!raw_file_data) -- cgit v1.2.3 From 6e38a3b9b6a408683aa42d68281b35ad9b9b39b2 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 03:35:06 +0300 Subject: Portamento must use previously played note in a channel like S-YXG50 does --- src/adlmidi_midiplay.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index f539b16..56078fb 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -477,7 +477,8 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) midiChan.portamentoEnable && currentPortamentoRate != HUGE_VAL && !isPercussion && !isXgPercussion; // Record the last note on MIDI channel as source of portamento - midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1; + midiChan.portamentoSource = static_cast(note); + // midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1; // Enable gliding on portamento note if (portamentoEnable && currentPortamentoSource >= 0) -- cgit v1.2.3 From 015d86e06359f0bc7aff9812ec6da5a599a5aa2f Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 03:36:15 +0300 Subject: VLC Plugin: Add missing Note-AfterTouch event support --- utils/vlc_codec/libadlmidi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/vlc_codec/libadlmidi.c b/utils/vlc_codec/libadlmidi.c index aedd718..efc2bb8 100644 --- a/utils/vlc_codec/libadlmidi.c +++ b/utils/vlc_codec/libadlmidi.c @@ -276,7 +276,9 @@ static block_t *DecodeBlock (decoder_t *p_dec, block_t **pp_block) case 0x90: adl_rt_noteOn(p_sys->synth, channel, p1, p2); break; - /*case 0xA0: note aftertouch not implemented */ + case 0xA0: + adl_rt_noteAfterTouch(p_sys->synth, channel, p1, p2); + break; case 0xB0: adl_rt_controllerChange(p_sys->synth, channel, p1, p2); break; -- cgit v1.2.3