From 7edc18c97791e259ba3706f720d53743bdd3135d Mon Sep 17 00:00:00 2001 From: drelbszoomer <173870110+drelbszoomer@users.noreply.github.com> Date: Tue, 28 Jan 2025 00:55:02 -0500 Subject: [PATCH] Feature: Add ALSA midi output support --- CMakeLists.txt | 2 + cmake/FindALSA.cmake | 76 + src/3rdparty/CMakeLists.txt | 1 + src/3rdparty/midifile/Binasc.cpp | 2012 +++++++++++++ src/3rdparty/midifile/Binasc.h | 161 ++ src/3rdparty/midifile/CMakeLists.txt | 12 + src/3rdparty/midifile/LICENSE.txt | 22 + src/3rdparty/midifile/MidiEvent.cpp | 298 ++ src/3rdparty/midifile/MidiEvent.h | 78 + src/3rdparty/midifile/MidiEventList.cpp | 623 ++++ src/3rdparty/midifile/MidiEventList.h | 82 + src/3rdparty/midifile/MidiFile.cpp | 3465 +++++++++++++++++++++++ src/3rdparty/midifile/MidiFile.h | 332 +++ src/3rdparty/midifile/MidiMessage.cpp | 2381 ++++++++++++++++ src/3rdparty/midifile/MidiMessage.h | 219 ++ src/music/CMakeLists.txt | 6 + src/music/alsamidi.cpp | 604 ++++ src/music/alsamidi.h | 93 + 18 files changed, 10467 insertions(+) create mode 100644 cmake/FindALSA.cmake create mode 100644 src/3rdparty/midifile/Binasc.cpp create mode 100644 src/3rdparty/midifile/Binasc.h create mode 100644 src/3rdparty/midifile/CMakeLists.txt create mode 100644 src/3rdparty/midifile/LICENSE.txt create mode 100644 src/3rdparty/midifile/MidiEvent.cpp create mode 100644 src/3rdparty/midifile/MidiEvent.h create mode 100644 src/3rdparty/midifile/MidiEventList.cpp create mode 100644 src/3rdparty/midifile/MidiEventList.h create mode 100644 src/3rdparty/midifile/MidiFile.cpp create mode 100644 src/3rdparty/midifile/MidiFile.h create mode 100644 src/3rdparty/midifile/MidiMessage.cpp create mode 100644 src/3rdparty/midifile/MidiMessage.h create mode 100644 src/music/alsamidi.cpp create mode 100644 src/music/alsamidi.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 60f4bc43ff..aa68bb85a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,7 @@ if(NOT OPTION_DEDICATED) find_package(Freetype) find_package(SDL2) find_package(Fluidsynth) + find_package(ALSA) if(Freetype_FOUND) find_package(Fontconfig) endif() @@ -326,6 +327,7 @@ if(NOT OPTION_DEDICATED) link_package(ICU_i18n) link_package(ICU_uc) link_package(OpusFile TARGET OpusFile::opusfile) + link_package(ALSA) if(SDL2_FOUND AND OPENGL_FOUND AND UNIX) # SDL2 dynamically loads OpenGL if needed, so do not link to OpenGL when diff --git a/cmake/FindALSA.cmake b/cmake/FindALSA.cmake new file mode 100644 index 0000000000..75e2448f7a --- /dev/null +++ b/cmake/FindALSA.cmake @@ -0,0 +1,76 @@ +#[=======================================================================[.rst: +FindALSA +-------- + +Find Advanced Linux Sound Architecture (ALSA) + +Find the alsa libraries (``asound``) + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.12 + +This module defines :prop_tgt:`IMPORTED` target ``ALSA::ALSA``, if +ALSA has been found. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module defines the following variables: + +``ALSA_FOUND`` + True if ALSA_INCLUDE_DIR & ALSA_LIBRARY are found + +``ALSA_LIBRARIES`` + List of libraries when using ALSA. + +``ALSA_INCLUDE_DIRS`` + Where to find the ALSA headers. + +Cache variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``ALSA_INCLUDE_DIR`` + the ALSA include directory + +``ALSA_LIBRARY`` + the absolute path of the asound library +#]=======================================================================] + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_ALSA QUIET alsa) + +find_path(ALSA_INCLUDE_DIR NAMES alsa/asoundlib.h + DOC "The ALSA (asound) include directory" +) + +find_library(ALSA_LIBRARY NAMES asound + DOC "The ALSA (asound) library" +) + +if(ALSA_INCLUDE_DIR AND EXISTS "${ALSA_INCLUDE_DIR}/alsa/version.h") + file(STRINGS "${ALSA_INCLUDE_DIR}/alsa/version.h" alsa_version_str REGEX "^#define[\t ]+SND_LIB_VERSION_STR[\t ]+\".*\"") + + string(REGEX REPLACE "^.*SND_LIB_VERSION_STR[\t ]+\"([^\"]*)\".*$" "\\1" ALSA_VERSION_STRING "${alsa_version_str}") + unset(alsa_version_str) +endif() + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(ALSA + REQUIRED_VARS ALSA_LIBRARY ALSA_INCLUDE_DIR + VERSION_VAR ALSA_VERSION_STRING) + +if(ALSA_FOUND) + set( ALSA_LIBRARIES ${ALSA_LIBRARY} ) + set( ALSA_INCLUDE_DIRS ${ALSA_INCLUDE_DIR} ) + if(NOT TARGET ALSA::ALSA) + add_library(ALSA::ALSA UNKNOWN IMPORTED) + set_target_properties(ALSA::ALSA PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ALSA_INCLUDE_DIRS}") + set_property(TARGET ALSA::ALSA APPEND PROPERTY IMPORTED_LOCATION "${ALSA_LIBRARY}") + endif() +endif() + +mark_as_advanced(ALSA_INCLUDE_DIR ALSA_LIBRARY) diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index 4d17f023a5..0017a7f303 100644 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -7,3 +7,4 @@ add_subdirectory(squirrel) add_subdirectory(nlohmann) add_subdirectory(opengl) add_subdirectory(openttd_social_integration_api) +add_subdirectory(midifile) diff --git a/src/3rdparty/midifile/Binasc.cpp b/src/3rdparty/midifile/Binasc.cpp new file mode 100644 index 0000000000..446dc03b9b --- /dev/null +++ b/src/3rdparty/midifile/Binasc.cpp @@ -0,0 +1,2012 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Mon Feb 16 12:26:32 PST 2015 Adapted from binasc program. +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/src/Binasc.cpp +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// description: Interface to convert bytes between binary and ASCII forms. +// + +#include "Binasc.h" + +#include +#include + + +namespace smf { + +const char* Binasc::GMinstrument[128] = { + "acoustic grand piano", "bright acoustic piano", "electric grand piano", "honky-tonk piano", "rhodes piano", "chorused piano", + "harpsichord", "clavinet", "celeste", "glockenspiel", "music box", "vibraphone", + "marimba", "xylophone", "tubular bells", "dulcimer", "hammond organ", "percussive organ", + "rock organ", "church organ", "reed organ", "accordion", "harmonica", "tango accordion", + "nylon guitar", "steel guitar", "jazz guitar", "clean guitar", "muted guitar", "overdriven guitar", + "distortion guitar", "guitar harmonics", "acoustic bass", "fingered electric bass", "picked electric bass", "fretless bass", + "slap bass 1", "slap bass 2", "synth bass 1", "synth bass 2", "violin", "viola", + "cello", "contrabass", "tremolo strings", "pizzcato strings", "orchestral harp", "timpani", + "string ensemble 1", "string ensemble 2", "synth strings 1", "synth strings 1", "choir aahs", "voice oohs", + "synth voices", "orchestra hit", "trumpet", "trombone", "tuba", "muted trumpet", + "frenc horn", "brass section", "syn brass 1", "synth brass 2", "soprano sax", "alto sax", + "tenor sax", "baritone sax", "oboe", "english horn", "bassoon", "clarinet", + "piccolo", "flute", "recorder", "pan flute", "bottle blow", "shakuhachi", + "whistle", "ocarina", "square wave", "saw wave", "calliope lead", "chiffer lead", + "charang lead", "voice lead", "fifths lead", "brass lead", "newage pad", "warm pad", + "polysyn pad", "choir pad", "bowed pad", "metallic pad", "halo pad", "sweep pad", + "rain", "soundtrack", "crystal", "atmosphere", "brightness", "goblins", + "echoes", "sci-fi", "sitar", "banjo", "shamisen", "koto", + "kalimba", "bagpipes", "fiddle", "shanai", "tinkle bell", "agogo", + "steel drums", "woodblock", "taiko drum", "melodoc tom", "synth drum", "reverse cymbal", + "guitar fret noise", "breath noise", "seashore", "bird tweet", "telephone ring", "helicopter", + "applause", "gunshot" +}; + +////////////////////////////// +// +// Binasc::Binasc -- Constructor: set the default option values. +// + +Binasc::Binasc(void) { + m_bytesQ = 1; // for printing HEX bytes when converting to ASCII + m_commentsQ = 0; // for printing text comments when converting to ASCII + m_midiQ = 0; // for printing ASCII as parsed MIDI file. + m_maxLineLength = 75; + m_maxLineBytes = 25; +} + + + +////////////////////////////// +// +// Binasc::~Binasc -- Destructor. +// + +Binasc::~Binasc() { + // do nothing +} + + + +////////////////////////////// +// +// Binasc::setLineLength -- Set the maximum length of a line when converting +// binary content into ASCII bytes. If the input size is less than one, +// set to the default value of 75 characters per line. +// + +int Binasc::setLineLength(int length) { + if (length < 1) { + m_maxLineLength = 75; + } else { + m_maxLineLength = length; + } + return m_maxLineLength; +} + + + +////////////////////////////// +// +// Binasc::getLineLength -- Set the maximum length of a line when converting +// binary content into ASCII bytes. +// + +int Binasc::getLineLength(void) { + return m_maxLineLength; +} + + + +////////////////////////////// +// +// Binasc::setLineBytes -- Set the maximum number of hex bytes in ASCII output. +// If the input size is less than one, set to the default value of 25 +// hex bytes per line. +// + +int Binasc::setLineBytes(int length) { + if (length < 1) { + m_maxLineBytes = 25; + } else { + m_maxLineBytes = length; + } + return m_maxLineBytes; +} + + + +////////////////////////////// +// +// Binasc::getLineBytes -- Get the maximum number of hex bytes in ASCII output. +// + +int Binasc::getLineBytes(void) { + return m_maxLineLength; +} + + + +////////////////////////////// +// +// Binasc::setComments -- Display or not display printable characters +// as comments when converting binary files to ASCII byte codes. +// + +void Binasc::setComments(int state) { + m_commentsQ = state ? 1 : 0; +} + + +void Binasc::setCommentsOn(void) { + setComments(true); +} + + +void Binasc::setCommentsOff(void) { + setComments(false); +} + + + +////////////////////////////// +// +// Binasc::getComments -- Get the comment display style for +// showing comments in ASCII output; +// + +int Binasc::getComments(void) { + return m_commentsQ; +} + + + +////////////////////////////// +// +// Binasc::setBytes -- Display or not display hex codes (only +// print ASCII printable characters). +// + +void Binasc::setBytes(int state) { + m_bytesQ = state ? 1 : 0; +} + + +void Binasc::setBytesOn(void) { + setBytes(true); +} + + +void Binasc::setBytesOff(void) { + setBytes(false); +} + + +////////////////////////////// +// +// Binasc::getBytes -- Get hex byte display status. +// + +int Binasc::getBytes(void) { + return m_bytesQ; +} + + +////////////////////////////// +// +// Binasc::setMidi -- Display or not display parsed MIDI data. +// + +void Binasc::setMidi(int state) { + m_midiQ = state ? 1 : 0; +} + + +void Binasc::setMidiOn(void) { + setMidi(true); +} + + +void Binasc::setMidiOff(void) { + setMidi(false); +} + + + +////////////////////////////// +// +// Binasc::getMidi -- Get the MIDI file printing style option state. +// + +int Binasc::getMidi(void) { + return m_midiQ; +} + + + +////////////////////////////// +// +// Binasc::writeToBinary -- Convert an ASCII representation of bytes into +// the binary file that it describes. Returns 0 if there was a problem +// otherwise returns 1. +// + +int Binasc::writeToBinary(const std::string& outfile, + const std::string& infile) { + std::ifstream input; + input.open(infile.c_str()); + if (!input.is_open()) { + std::cerr << "Cannot open " << infile + << " for reading in binasc." << std::endl; + return 0; + } + + std::ofstream output; + output.open(outfile.c_str()); + if (!output.is_open()) { + std::cerr << "Cannot open " << outfile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = writeToBinary(output, input); + input.close(); + output.close(); + return status; +} + + +int Binasc::writeToBinary(const std::string& outfile, std::istream& input) { + std::ofstream output; + output.open(outfile.c_str()); + if (!output.is_open()) { + std::cerr << "Cannot open " << outfile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = writeToBinary(output, input); + output.close(); + return status; +} + + +int Binasc::writeToBinary(std::ostream& out, const std::string& infile) { + std::ifstream input; + input.open(infile.c_str()); + if (!input.is_open()) { + std::cerr << "Cannot open " << infile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = writeToBinary(out, input); + input.close(); + return status; +} + + +int Binasc::writeToBinary(std::ostream& out, std::istream& input) { + std::string inputLine; + inputLine.reserve(8196); + int lineNum = 0; // current line number + getline(input, inputLine, '\n'); + lineNum++; + while (!input.eof()) { + int status = processLine(out, inputLine, lineNum); + if (!status) { + return 0; + } + getline(input, inputLine, '\n'); + lineNum++; + } + return 1; +} + + + +////////////////////////////// +// +// Binasc::readFromBinary -- convert an ASCII representation of bytes into +// the binary file that it describes. +// + +int Binasc::readFromBinary(const std::string& outfile, const std::string& infile) { + std::ifstream input; + input.open(infile.c_str()); + if (!input.is_open()) { + std::cerr << "Cannot open " << infile + << " for reading in binasc." << std::endl; + return 0; + } + + std::ofstream output; + output.open(outfile.c_str()); + if (!output.is_open()) { + std::cerr << "Cannot open " << outfile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = readFromBinary(output, input); + input.close(); + output.close(); + return status; +} + + +int Binasc::readFromBinary(const std::string& outfile, std::istream& input) { + std::ofstream output; + output.open(outfile.c_str()); + if (!output.is_open()) { + std::cerr << "Cannot open " << outfile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = readFromBinary(output, input); + output.close(); + return status; +} + + +int Binasc::readFromBinary(std::ostream& out, const std::string& infile) { + std::ifstream input; + input.open(infile.c_str()); + if (!input.is_open()) { + std::cerr << "Cannot open " << infile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = readFromBinary(out, input); + input.close(); + return status; +} + + +int Binasc::readFromBinary(std::ostream& out, std::istream& input) { + int status; + if (m_midiQ) { + status = outputStyleMidi(out, input); + } else if (!m_bytesQ) { + status = outputStyleAscii(out, input); + } else if (m_bytesQ && m_commentsQ) { + status = outputStyleBoth(out, input); + } else { + status = outputStyleBinary(out, input); + } + return status; +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// protected functions -- +// + +////////////////////////////// +// +// Binasc::outputStyleAscii -- read an input file and output bytes in ascii +// form, not displaying any blank lines. Output words are not +// broken unless they are longer than 75 characters. +// + +int Binasc::outputStyleAscii(std::ostream& out, std::istream& input) { + uchar outputWord[256] = {0}; // storage for current word + int index = 0; // current length of word + int lineCount = 0; // current length of line + int type = 0; // 0=space, 1=printable + uchar ch; // current input byte + + ch = static_cast(input.get()); + while (!input.eof()) { + int lastType = type; + type = (isprint(ch) && !isspace(ch)) ? 1 : 0; + + if ((type == 1) && (lastType == 0)) { + // start of a new word. check where to put old word + if (index + lineCount >= m_maxLineLength) { // put on next line + outputWord[index] = '\0'; + out << '\n' << outputWord; + lineCount = index; + index = 0; + } else { // put on current line + outputWord[index] = '\0'; + if (lineCount != 0) { + out << ' '; + lineCount++; + } + out << outputWord; + lineCount += index; + index = 0; + } + } + if (type == 1) { + outputWord[index++] = ch; + } + ch = static_cast(input.get()); + } + + if (index != 0) { + out << std::endl; + } + + return 1; +} + + + +////////////////////////////// +// +// Binasc::outputStyleBinary -- read an input binary file and output bytes +// in ascii form, hexadecimal numbers only. +// + +int Binasc::outputStyleBinary(std::ostream& out, std::istream& input) { + int currentByte = 0; // current byte output in line + uchar ch; // current input byte + + ch = static_cast(input.get()); + if (input.eof()) { + std::cerr << "End of the file right away!" << std::endl; + return 0; + } + + while (!input.eof()) { + if (ch < 0x10) { + out << '0'; + } + out << std::hex << (int)ch << ' '; + currentByte++; + if (currentByte >= m_maxLineBytes) { + out << '\n'; + currentByte = 0; + } + ch = static_cast(input.get()); + } + + if (currentByte != 0) { + out << std::endl; + } + + return 1; +} + + + +////////////////////////////// +// +// Binasc::outputStyleBoth -- read an input file and output bytes in ASCII +// form with both hexadecimal numbers and ascii representation +// + +int Binasc::outputStyleBoth(std::ostream& out, std::istream& input) { + uchar asciiLine[256] = {0}; // storage for output line + int currentByte = 0; // current byte output in line + int index = 0; // current character in asciiLine + uchar ch; // current input byte + + ch = static_cast(input.get()); + while (!input.eof()) { + if (index == 0) { + asciiLine[index++] = ';'; + out << ' '; + } + if (ch < 0x10) { + out << '0'; + } + out << std::hex << (int)ch << ' '; + currentByte++; + + asciiLine[index++] = ' '; + if (isprint(ch)) { + asciiLine[index++] = ch; + } else { + asciiLine[index++] = ' '; + } + asciiLine[index++] = ' '; + + if (currentByte >= m_maxLineBytes) { + out << '\n'; + asciiLine[index] = '\0'; + out << asciiLine << "\n\n"; + currentByte = 0; + index = 0; + } + ch = static_cast(input.get()); + } + + if (currentByte != 0) { + out << '\n'; + asciiLine[index] = '\0'; + out << asciiLine << '\n' << std::endl; + } + + return 1; +} + + + +/////////////////////////////// +// +// Binasc::processLine -- Read a line of input and output any specified bytes. +// + +int Binasc::processLine(std::ostream& out, const std::string& input, + int lineCount) { + int status = 1; + int i = 0; + int length = (int)input.size(); + std::string word; + while (i 2)) { + status = processBinaryWord(out, word, lineCount); + } else { + status = processHexWord(out, word, lineCount); + } + } + + if (status == 0) { + return 0; + } + + } + + return 1; +} + + + +////////////////////////////// +// +// Binasc::getWord -- extract a sub string, stopping at any of the given +// terminator characters. +// + +int Binasc::getWord(std::string& word, const std::string& input, + const std::string& terminators, int index) { + word.resize(0); + int i = index; + int escape = 0; + int ecount = 0; + if (terminators.find('"') != std::string::npos) { + escape = 1; + } + while (i < (int)input.size()) { + if (escape && input[i] == '\"') { + ecount++; + i++; + if (ecount >= 2) { + break; + } + } + if (escape && (i<(int)input.size()-1) && (input[i] == '\\') + && (input[i+1] == '"')) { + word.push_back(input[i+1]); + i += 2; + } else if (terminators.find(input[i]) == std::string::npos) { + word.push_back(input[i]); + i++; + } else { + i++; + return i; + } + } + return i; +} + + + +/////////////////////////////// +// +// Binasc::getVLV -- read a Variable-Length Value from the file +// + +int Binasc::getVLV(std::istream& infile, int& trackbytes) { + int output = 0; + uchar ch = 0; + infile.read((char*)&ch, 1); + trackbytes++; + output = (output << 7) | (0x7f & ch); + while (ch >= 0x80) { + infile.read((char*)&ch, 1); + trackbytes++; + output = (output << 7) | (0x7f & ch); + } + return output; +} + + + +////////////////////////////// +// +// Binasc::readMidiEvent -- Read a delta time and then a MIDI message +// (or meta message). Returns 1 if not end-of-track meta message; +// 0 otherwise. +// + +int Binasc::readMidiEvent(std::ostream& out, std::istream& infile, + int& trackbytes, int& command) { + + // Read and print Variable Length Value for delta ticks + int vlv = getVLV(infile, trackbytes); + + std::stringstream output; + + output << "v" << std::dec << vlv << "\t"; + + std::string comment; + + int status = 1; + uchar ch = 0; + char byte1, byte2; + infile.read((char*)&ch, 1); + trackbytes++; + if (ch < 0x80) { + // running status: command byte is previous one in data stream + output << " "; + } else { + // midi command byte + output << std::hex << (int)ch; + command = ch; + infile.read((char*)&ch, 1); + trackbytes++; + } + byte1 = ch; + switch (command & 0xf0) { + case 0x80: // note-off: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + comment += "note-off " + keyToPitchName(byte1); + } + break; + case 0x90: // note-on: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + if (byte2 == 0) { + comment += "note-off " + keyToPitchName(byte1); + } else { + comment += "note-on " + keyToPitchName(byte1); + } + } + break; + case 0xA0: // aftertouch: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + comment += "after-touch"; + } + break; + case 0xB0: // continuous controller: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + comment += "controller"; + } + break; + case 0xE0: // pitch-bend: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + comment += "pitch-bend"; + } + break; + case 0xC0: // patch change: 1 bytes + output << " '" << std::dec << (int)byte1; + if (m_commentsQ) { + output << "\t"; + comment += "patch-change ("; + comment += GMinstrument[byte1 & 0x7f]; + comment += ")"; + } + break; + case 0xD0: // channel pressure: 1 bytes + output << " '" << std::dec << (int)byte1; + if (m_commentsQ) { + comment += "channel pressure"; + } + break; + case 0xF0: // various system bytes: variable bytes + switch (command) { + case 0xf0: + { + // A system exclusive message. The first byte + // is 0xf0, then a VLV of the length of the message + // and then the message itself (which must end with 0xf7). + int length = getVLV(infile, trackbytes); + output << " v" << std::dec << length; + for (int b=0; b 0) { + tempout << "\t\t\t; unknown header bytes"; + tempout << std::endl; + } + + for (i=0; i 127 || tempLong < -128) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "Decimal number out of range from -128 to 127" << std::endl; + return 0; + } + char charOutput = (char)tempLong; + out << charOutput; + return 1; + } else { + ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]); + uchar ucharOutput = (uchar)tempLong; + if (tempLong > 255) { // || (tempLong < 0)) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "Decimal number out of range from 0 to 255" << std::endl; + return 0; + } + out << ucharOutput; + return 1; + } + } + + // left with an integer number with a specified number of bytes + switch (byteCount) { + case 1: + if (signIndex != -1) { + long tempLong = atoi(&word[quoteIndex + 1]); + char charOutput = (char)tempLong; + out << charOutput; + return 1; + } else { + ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]); + uchar ucharOutput = (uchar)tempLong; + out << ucharOutput; + return 1; + } + break; + case 2: + if (signIndex != -1) { + long tempLong = atoi(&word[quoteIndex + 1]); + short shortOutput = (short)tempLong; + if (endianIndex == -1) { + writeBigEndianShort(out, shortOutput); + } else { + writeLittleEndianShort(out, shortOutput); + } + return 1; + } else { + ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]); + ushort ushortOutput = (ushort)tempLong; + if (endianIndex == -1) { + writeBigEndianUShort(out, ushortOutput); + } else { + writeLittleEndianUShort(out, ushortOutput); + } + return 1; + } + break; + case 3: + { + if (signIndex != -1) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "negative decimal numbers cannot be stored in 3 bytes" + << std::endl; + return 0; + } + ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]); + uchar byte1 = (uchar)((tempLong & 0x00ff0000) >> 16); + uchar byte2 = (uchar)((tempLong & 0x0000ff00) >> 8); + uchar byte3 = (uchar)((tempLong & 0x000000ff)); + if (endianIndex == -1) { + out << byte1; + out << byte2; + out << byte3; + } else { + out << byte3; + out << byte2; + out << byte1; + } + return 1; + } + break; + case 4: + if (signIndex != -1) { + long tempLong = atoi(&word[quoteIndex + 1]); + if (endianIndex == -1) { + writeBigEndianLong(out, tempLong); + } else { + writeLittleEndianLong(out, tempLong); + } + return 1; + } else { + ulong tempuLong = (ulong)atoi(&word[quoteIndex + 1]); + if (endianIndex == -1) { + writeBigEndianULong(out, tempuLong); + } else { + writeLittleEndianULong(out, tempuLong); + } + return 1; + } + break; + default: + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "invalid byte count specification for decimal number" << std::endl; + return 0; + } +} + + + +////////////////////////////// +// +// Binasc::processHexWord -- interprets a hexadecimal word and converts into +// its binary byte form. +// + +int Binasc::processHexWord(std::ostream& out, const std::string& word, + int lineNum) { + int length = (int)word.size(); + uchar outputByte; + + if (length > 2) { + std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl; + std::cerr << "Size of hexadecimal number is too large. Max is ff." << std::endl; + return 0; + } + + if (!isxdigit(word[0]) || (length == 2 && !isxdigit(word[1]))) { + std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl; + std::cerr << "Invalid character in hexadecimal number." << std::endl; + return 0; + } + + outputByte = (uchar)strtol(word.c_str(), (char**)NULL, 16); + out << outputByte; + return 1; +} + + + +////////////////////////////// +// +// Binasc::processStringWord -- interprets a binary word into +// its constituent byte +// + +int Binasc::processStringWord(std::ostream& out, const std::string& word, + int /* lineNum */) { + out << word; + return 1; +} + + + +////////////////////////////// +// +// Binasc::processAsciiWord -- interprets a binary word into +// its constituent byte +// + +int Binasc::processAsciiWord(std::ostream& out, const std::string& word, + int lineNum) { + int length = (int)word.size(); + uchar outputByte; + + if (word[0] != '+') { + std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl; + std::cerr << "character byte must start with \'+\' sign: " << std::endl; + return 0; + } + + if (length > 2) { + std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl; + std::cerr << "character byte word is too long -- specify only one character" + << std::endl; + return 0; + } + + if (length == 2) { + outputByte = (uchar)word[1]; + } else { + outputByte = ' '; + } + out << outputByte; + return 1; +} + + + +////////////////////////////// +// +// Binasc::processBinaryWord -- interprets a binary word into +// its constituent byte +// + +int Binasc::processBinaryWord(std::ostream& out, const std::string& word, + int lineNum) { + int length = (int)word.size(); // length of ascii binary number + int commaIndex = -1; // index location of comma in number + int leftDigits = -1; // number of digits to left of comma + int rightDigits = -1; // number of digits to right of comma + int i = 0; + + // make sure that all characters are valid + for (i=0; i 8) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "too many digits in binary number" << std::endl; + return 0; + } + // if there is a comma, then there cannot be more than 4 digits on a side + if (leftDigits > 4) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "too many digits to left of comma" << std::endl; + return 0; + } + if (rightDigits > 4) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "too many digits to right of comma" << std::endl; + return 0; + } + + // OK, we have a valid binary number, so calculate the byte + + uchar output = 0; + + // if no comma in binary number + if (commaIndex == -1) { + for (i=0; i> 28) & 0x7f; + byte[1] = (value >> 21) & 0x7f; + byte[2] = (value >> 14) & 0x7f; + byte[3] = (value >> 7) & 0x7f; + byte[4] = (value >> 0) & 0x7f; + + int i; + int flag = 0; + for (i=0; i<4; i++) { + if (byte[i] != 0) { + flag = 1; + } + if (flag) { + byte[i] |= 0x80; + } + } + + for (i=0; i<5; i++) { + if (byte[i] >= 0x80 || i == 4) { + out << byte[i]; + } + } + + return 1; +} + + + +//////////////////////////// +// +// Binasc::processMidiTempoWord -- convert a floating point tempo into +// a three-byte number of microseconds per beat per minute value. +// + +int Binasc::processMidiTempoWord(std::ostream& out, const std::string& word, + int lineNum) { + if (word.size() < 2) { + std::cerr << "Error on line: " << lineNum + << ": 't' needs to be followed immediately by " + << "a floating-point number" << std::endl; + return 0; + } + if (!(isdigit(word[1]) || word[1] == '.' || word[1] == '-' + || word[1] == '+')) { + std::cerr << "Error on line: " << lineNum + << ": 't' needs to be followed immediately by " + << "a floating-point number" << std::endl; + return 0; + } + double value = strtod(&word[1], NULL); + + if (value < 0.0) { + value = -value; + } + + int intval = int(60.0 * 1000000.0 / value + 0.5); + + uchar byte0 = intval & 0xff; + uchar byte1 = (intval >> 8) & 0xff; + uchar byte2 = (intval >> 16) & 0xff; + out << byte2 << byte1 << byte0; + return 1; +} + + + +//////////////////////////// +// +// Binasc::processMidiPitchBendWord -- convert a floating point number in +// the range from +1.0 to -1.0 into a 14-point integer with -1.0 mapping +// to 0 and +1.0 mapping to 2^15-1. This integer will be packed into +// two bytes, with the LSB coming first and containing the bottom +// 7-bits of the 14-bit value, then the MSB coming second and containing +// the top 7-bits of the 14-bit value. + +int Binasc::processMidiPitchBendWord(std::ostream& out, const std::string& word, + int lineNum) { + if (word.size() < 2) { + std::cerr << "Error on line: " << lineNum + << ": 'p' needs to be followed immediately by " + << "a floating-point number" << std::endl; + return 0; + } + if (!(isdigit(word[1]) || word[1] == '.' || word[1] == '-' + || word[1] == '+')) { + std::cerr << "Error on line: " << lineNum + << ": 'p' needs to be followed immediately by " + << "a floating-point number" << std::endl; + return 0; + } + double value = strtod(&word[1], NULL); + + if (value > 1.0) { + value = 1.0; + } + if (value < -1.0) { + value = -1.0; + } + + int intval = (int)(((1 << 13)-0.5) * (value + 1.0) + 0.5); + uchar LSB = intval & 0x7f; + uchar MSB = (intval >> 7) & 0x7f; + out << LSB << MSB; + return 1; +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// Ordered byte writing functions -- +// + +////////////////////////////// +// +// Binasc::writeLittleEndianUShort -- +// + +std::ostream& Binasc::writeLittleEndianUShort(std::ostream& out, ushort value) { + union { char bytes[2]; ushort us; } data; + data.us = value; + out << data.bytes[0]; + out << data.bytes[1]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianUShort -- +// + +std::ostream& Binasc::writeBigEndianUShort(std::ostream& out, ushort value) { + union { char bytes[2]; ushort us; } data; + data.us = value; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianShort -- +// + +std::ostream& Binasc::writeLittleEndianShort(std::ostream& out, short value) { + union { char bytes[2]; short s; } data; + data.s = value; + out << data.bytes[0]; + out << data.bytes[1]; + return out; +} + + + +////////////////////////////// +// +// writeBigEndianShort -- +// + +std::ostream& Binasc::writeBigEndianShort(std::ostream& out, short value) { + union { char bytes[2]; short s; } data; + data.s = value; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianULong -- +// + +std::ostream& Binasc::writeLittleEndianULong(std::ostream& out, ulong value) { + union { char bytes[4]; ulong ul; } data; + data.ul = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianULong -- +// + +std::ostream& Binasc::writeBigEndianULong(std::ostream& out, ulong value) { + union { char bytes[4]; long ul; } data; + data.ul = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianLong -- +// + +std::ostream& Binasc::writeLittleEndianLong(std::ostream& out, long value) { + union { char bytes[4]; long l; } data; + data.l = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianLong -- +// + +std::ostream& Binasc::writeBigEndianLong(std::ostream& out, long value) { + union { char bytes[4]; long l; } data; + data.l = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; + +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianFloat -- +// + +std::ostream& Binasc::writeBigEndianFloat(std::ostream& out, float value) { + union { char bytes[4]; float f; } data; + data.f = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianFloat -- +// + +std::ostream& Binasc::writeLittleEndianFloat(std::ostream& out, float value) { + union { char bytes[4]; float f; } data; + data.f = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianDouble -- +// + +std::ostream& Binasc::writeBigEndianDouble(std::ostream& out, double value) { + union { char bytes[8]; double d; } data; + data.d = value; + out << data.bytes[7]; + out << data.bytes[6]; + out << data.bytes[5]; + out << data.bytes[4]; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianDouble -- +// + +std::ostream& Binasc::writeLittleEndianDouble(std::ostream& out, double value) { + union { char bytes[8]; double d; } data; + data.d = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + out << data.bytes[4]; + out << data.bytes[5]; + out << data.bytes[6]; + out << data.bytes[7]; + return out; +} + + +} // end namespace smf + + + diff --git a/src/3rdparty/midifile/Binasc.h b/src/3rdparty/midifile/Binasc.h new file mode 100644 index 0000000000..686cc407c7 --- /dev/null +++ b/src/3rdparty/midifile/Binasc.h @@ -0,0 +1,161 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Mon Feb 16 12:26:32 PST 2015 Adapted from binasc program. +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/Binasc.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// description: Interface to convert bytes between binary and ASCII forms. +// + +#ifndef _BINASC_H_INCLUDED +#define _BINASC_H_INCLUDED + +#include +#include +#include +#include + + +namespace smf { + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned long ulong; + +class Binasc { + + public: + Binasc (void); + + ~Binasc (); + + // functions for setting options: + int setLineLength (int length); + int getLineLength (void); + int setLineBytes (int length); + int getLineBytes (void); + void setComments (int state); + void setCommentsOn (void); + void setCommentsOff (void); + int getComments (void); + void setBytes (int state); + void setBytesOn (void); + void setBytesOff (void); + int getBytes (void); + void setMidi (int state); + void setMidiOn (void); + void setMidiOff (void); + int getMidi (void); + + // functions for converting into a binary file: + int writeToBinary (const std::string& outfile, + const std::string& infile); + int writeToBinary (const std::string& outfile, + std::istream& input); + int writeToBinary (std::ostream& out, + const std::string& infile); + int writeToBinary (std::ostream& out, + std::istream& input); + + // functions for converting into an ASCII file with hex bytes: + int readFromBinary (const std::string& + outfile, + const std::string& infile); + int readFromBinary (const std::string& outfile, + std::istream& input); + int readFromBinary (std::ostream& out, + const std::string& infile); + int readFromBinary (std::ostream& out, + std::istream& input); + + // static functions for writing ordered bytes: + static std::ostream& writeLittleEndianUShort (std::ostream& out, + ushort value); + static std::ostream& writeBigEndianUShort (std::ostream& out, + ushort value); + static std::ostream& writeLittleEndianShort (std::ostream& out, + short value); + static std::ostream& writeBigEndianShort (std::ostream& out, + short value); + static std::ostream& writeLittleEndianULong (std::ostream& out, + ulong value); + static std::ostream& writeBigEndianULong (std::ostream& out, + ulong value); + static std::ostream& writeLittleEndianLong (std::ostream& out, + long value); + static std::ostream& writeBigEndianLong (std::ostream& out, + long value); + static std::ostream& writeLittleEndianFloat (std::ostream& out, + float value); + static std::ostream& writeBigEndianFloat (std::ostream& out, + float value); + static std::ostream& writeLittleEndianDouble (std::ostream& out, + double value); + static std::ostream& writeBigEndianDouble (std::ostream& out, + double value); + + static std::string keyToPitchName (int key); + + protected: + int m_bytesQ; // option for printing hex bytes in ASCII output. + int m_commentsQ; // option for printing comments in ASCII output. + int m_midiQ; // output ASCII data as parsed MIDI file. + int m_maxLineLength;// number of character in ASCII output on a line. + int m_maxLineBytes; // number of hex bytes in ASCII output on a line. + + private: + // helper functions for reading ASCII content to conver to binary: + int processLine (std::ostream& out, + const std::string& input, + int lineNum); + int processAsciiWord (std::ostream& out, + const std::string& input, + int lineNum); + int processStringWord (std::ostream& out, + const std::string& input, + int lineNum); + int processBinaryWord (std::ostream& out, + const std::string& input, + int lineNum); + int processDecimalWord (std::ostream& out, + const std::string& input, + int lineNum); + int processHexWord (std::ostream& out, + const std::string& input, + int lineNum); + int processVlvWord (std::ostream& out, + const std::string& input, + int lineNum); + int processMidiPitchBendWord(std::ostream& out, + const std::string& input, + int lineNum); + int processMidiTempoWord (std::ostream& out, + const std::string& input, + int lineNum); + + // helper functions for reading binary content to convert to ASCII: + int outputStyleAscii (std::ostream& out, std::istream& input); + int outputStyleBinary (std::ostream& out, std::istream& input); + int outputStyleBoth (std::ostream& out, std::istream& input); + int outputStyleMidi (std::ostream& out, std::istream& input); + + // MIDI parsing helper functions: + int readMidiEvent (std::ostream& out, std::istream& infile, + int& trackbytes, int& command); + int getVLV (std::istream& infile, int& trackbytes); + int getWord (std::string& word, const std::string& input, + const std::string& terminators, int index); + + static const char *GMinstrument[128]; + +}; + +} // end of namespace smf + +#endif /* _BINASC_H_INCLUDED */ + + + diff --git a/src/3rdparty/midifile/CMakeLists.txt b/src/3rdparty/midifile/CMakeLists.txt new file mode 100644 index 0000000000..749444d561 --- /dev/null +++ b/src/3rdparty/midifile/CMakeLists.txt @@ -0,0 +1,12 @@ +add_files( + Binasc.cpp + Binasc.h + MidiEvent.cpp + MidiEvent.h + MidiEventList.cpp + MidiEventList.h + MidiFile.cpp + MidiFile.h + MidiMessage.cpp + MidiMessage.h +) diff --git a/src/3rdparty/midifile/LICENSE.txt b/src/3rdparty/midifile/LICENSE.txt new file mode 100644 index 0000000000..9d256595b7 --- /dev/null +++ b/src/3rdparty/midifile/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 1999-2018, Craig Stuart Sapp +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/3rdparty/midifile/MidiEvent.cpp b/src/3rdparty/midifile/MidiEvent.cpp new file mode 100644 index 0000000000..0cf590d8c9 --- /dev/null +++ b/src/3rdparty/midifile/MidiEvent.cpp @@ -0,0 +1,298 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 21:40:14 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/src/MidiEvent.cpp +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class which stores a MidiMessage and a timestamp +// for the MidiFile class. +// + +#include "MidiEvent.h" + +#include + + +namespace smf { + +////////////////////////////// +// +// MidiEvent::MidiEvent -- Constructor classes +// + +MidiEvent::MidiEvent(void) : MidiMessage() { + clearVariables(); +} + + +MidiEvent::MidiEvent(int command) : MidiMessage(command) { + clearVariables(); +} + + +MidiEvent::MidiEvent(int command, int p1) : MidiMessage(command, p1) { + clearVariables(); +} + + +MidiEvent::MidiEvent(int command, int p1, int p2) + : MidiMessage(command, p1, p2) { + clearVariables(); +} + + +MidiEvent::MidiEvent(int aTime, int aTrack, vector& message) + : MidiMessage(message) { + track = aTrack; + tick = aTime; + seconds = 0.0; + seq = 0; + m_eventlink = NULL; +} + + +MidiEvent::MidiEvent(const MidiEvent& mfevent) : MidiMessage() { + track = mfevent.track; + tick = mfevent.tick; + seconds = mfevent.seconds; + seq = mfevent.seq; + m_eventlink = NULL; + + this->resize(mfevent.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = mfevent[i]; + } +} + + + +////////////////////////////// +// +// MidiEvent::~MidiEvent -- MidiFile Event destructor +// + +MidiEvent::~MidiEvent() { + track = -1; + tick = -1; + seconds = -1.0; + seq = -1; + this->resize(0); + m_eventlink = NULL; +} + + +////////////////////////////// +// +// MidiEvent::clearVariables -- Clear everything except MidiMessage data. +// + +void MidiEvent::clearVariables(void) { + track = 0; + tick = 0; + seconds = 0.0; + seq = 0; + m_eventlink = NULL; +} + + +////////////////////////////// +// +// MidiEvent::operator= -- Copy the contents of another MidiEvent. +// + +MidiEvent& MidiEvent::operator=(const MidiEvent& mfevent) { + if (this == &mfevent) { + return *this; + } + tick = mfevent.tick; + track = mfevent.track; + seconds = mfevent.seconds; + seq = mfevent.seq; + m_eventlink = NULL; + this->resize(mfevent.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = mfevent[i]; + } + return *this; +} + + +MidiEvent& MidiEvent::operator=(const MidiMessage& message) { + if (this == &message) { + return *this; + } + clearVariables(); + this->resize(message.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = message[i]; + } + return *this; +} + + +MidiEvent& MidiEvent::operator=(const vector& bytes) { + clearVariables(); + this->resize(bytes.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = bytes[i]; + } + return *this; +} + + +MidiEvent& MidiEvent::operator=(const vector& bytes) { + clearVariables(); + setMessage(bytes); + return *this; +} + + +MidiEvent& MidiEvent::operator=(const vector& bytes) { + clearVariables(); + setMessage(bytes); + return *this; +} + + + +////////////////////////////// +// +// MidiEvent::unlinkEvent -- Disassociate this event with another. +// Also tell the other event to disassociate from this event. +// + +void MidiEvent::unlinkEvent(void) { + if (m_eventlink == NULL) { + return; + } + MidiEvent* mev = m_eventlink; + m_eventlink = NULL; + mev->unlinkEvent(); +} + + + +////////////////////////////// +// +// MidiEvent::linkEvent -- Make a link between two messages. +// Unlinking +// + +void MidiEvent::linkEvent(MidiEvent* mev) { + if (mev->m_eventlink != NULL) { + // unlink other event if it is linked to something else; + mev->unlinkEvent(); + } + // if this is already linked to something else, then unlink: + if (m_eventlink != NULL) { + m_eventlink->unlinkEvent(); + } + unlinkEvent(); + + mev->m_eventlink = this; + m_eventlink = mev; +} + + +void MidiEvent::linkEvent(MidiEvent& mev) { + linkEvent(&mev); +} + + + +////////////////////////////// +// +// MidiEvent::getLinkedEvent -- Returns a linked event. Usually +// this is the note-off message for a note-on message and vice-versa. +// Returns null if there are no links. +// + +MidiEvent* MidiEvent::getLinkedEvent(void) { + return m_eventlink; +} + + +const MidiEvent* MidiEvent::getLinkedEvent(void) const { + return m_eventlink; +} + + + +////////////////////////////// +// +// MidiEvent::isLinked -- Returns true if there is an event which is not +// NULL. This function is similar to getLinkedEvent(). +// + +int MidiEvent::isLinked(void) const { + return m_eventlink == NULL ? 0 : 1; +} + + + +////////////////////////////// +// +// MidiEvent::getTickDuration -- For linked events (note-ons and note-offs), +// return the absolute tick time difference between the two events. +// The tick values are presumed to be in absolute tick mode rather than +// delta tick mode. Returns 0 if not linked. +// + +int MidiEvent::getTickDuration(void) const { + const MidiEvent* mev = getLinkedEvent(); + if (mev == NULL) { + return 0; + } + int tick2 = mev->tick; + if (tick2 > tick) { + return tick2 - tick; + } else { + return tick - tick2; + } +} + + + +////////////////////////////// +// +// MidiEvent::getDurationInSeconds -- For linked events (note-ons and +// note-offs), return the duration of the note in seconds. The +// seconds analysis must be done first; otherwise the duration will be +// reported as zero. +// + +double MidiEvent::getDurationInSeconds(void) const { + const MidiEvent* mev = getLinkedEvent(); + if (mev == NULL) { + return 0; + } + double seconds2 = mev->seconds; + if (seconds2 > seconds) { + return seconds2 - seconds; + } else { + return seconds - seconds2; + } +} + + + +////////////////////////////// +// +// operator<<(MidiMessage) -- Print tick value followed by MIDI bytes for event. +// The tick value will be either relative or absolute depending on the state +// of the MidiFile object containing it. +// + +std::ostream& operator<<(std::ostream& out, MidiEvent& event) { + out << event.tick << '(' << static_cast(event) << ')'; + return out; +} + + +} // end namespace smf + + + diff --git a/src/3rdparty/midifile/MidiEvent.h b/src/3rdparty/midifile/MidiEvent.h new file mode 100644 index 0000000000..f8141d17bb --- /dev/null +++ b/src/3rdparty/midifile/MidiEvent.h @@ -0,0 +1,78 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 21:47:39 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/MidiEvent.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class which stores a MidiMessage and a timestamp +// for the MidiFile class. +// + +#ifndef _MIDIEVENT_H_INCLUDED +#define _MIDIEVENT_H_INCLUDED + +#include "MidiMessage.h" + +#include +#include + + +namespace smf { + +class MidiEvent : public MidiMessage { + public: + MidiEvent (void); + MidiEvent (int command); + MidiEvent (int command, int param1); + MidiEvent (int command, int param1, int param2); + MidiEvent (const MidiMessage& message); + MidiEvent (const MidiEvent& mfevent); + MidiEvent (int aTime, int aTrack, + std::vector& message); + + ~MidiEvent (); + + MidiEvent& operator= (const MidiEvent& mfevent); + MidiEvent& operator= (const MidiMessage& message); + MidiEvent& operator= (const std::vector& bytes); + MidiEvent& operator= (const std::vector& bytes); + MidiEvent& operator= (const std::vector& bytes); + + void clearVariables (void); + + // functions related to event linking (note-ons to note-offs). + void unlinkEvent (void); + void unlinkEvents (void); + void linkEvent (MidiEvent* mev); + void linkEvents (MidiEvent* mev); + void linkEvent (MidiEvent& mev); + void linkEvents (MidiEvent& mev); + int isLinked (void) const; + MidiEvent* getLinkedEvent (void); + const MidiEvent* getLinkedEvent (void) const; + int getTickDuration (void) const; + double getDurationInSeconds (void) const; + + int tick; // delta or absolute MIDI ticks + int track; // [original] track number of event in MIDI file + double seconds; // calculated time in sec. (after doTimeAnalysis()) + int seq; // sorting sequence number of event + + private: + MidiEvent* m_eventlink; // used to match note-ons and note-offs + +}; + + +std::ostream& operator<<(std::ostream& out, MidiEvent& event); + + +} // end of namespace smf + +#endif /* _MIDIEVENT_H_INCLUDED */ + + + diff --git a/src/3rdparty/midifile/MidiEventList.cpp b/src/3rdparty/midifile/MidiEventList.cpp new file mode 100644 index 0000000000..f38fefbc2c --- /dev/null +++ b/src/3rdparty/midifile/MidiEventList.cpp @@ -0,0 +1,623 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 21:55:38 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/src/MidiEventList.cpp +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class which stores a MidiEvents for a MidiFile track. +// + +#include "MidiEventList.h" + +#include +#include +#include +#include +#include + + +namespace smf { + +////////////////////////////// +// +// MidiEventList::MidiEventList -- Constructor. +// + +MidiEventList::MidiEventList(void) { + reserve(1000); +} + + + +////////////////////////////// +// +// MidiEventList::MidiEventList(MidiEventList&) -- Copy constructor. +// + +MidiEventList::MidiEventList(const MidiEventList& other) { + list.reserve(other.list.size()); + auto it = other.list.begin(); + std::generate_n(std::back_inserter(list), other.list.size(), [&]() -> MidiEvent* { + return new MidiEvent(**it++); + }); +} + + + +////////////////////////////// +// +// MidiEventList::MidiEventList(MidiEventList&&) -- Move constructor. +// + +MidiEventList::MidiEventList(MidiEventList&& other) { + list = std::move(other.list); + other.list.clear(); +} + + + +////////////////////////////// +// +// MidiEventList::~MidiEventList -- Deconstructor. Deallocate all stored +// data. +// + +MidiEventList::~MidiEventList() { + clear(); +} + + + +////////////////////////////// +// +// MidiEventList::operator[] -- +// + +MidiEvent& MidiEventList::operator[](int index) { + return *list[index]; +} + + +const MidiEvent& MidiEventList::operator[](int index) const { + return *list[index]; +} + + + +////////////////////////////// +// +// MidiEventList::back -- Return the last element in the list. +// + +MidiEvent& MidiEventList::back(void) { + return *list.back(); +} + + +const MidiEvent& MidiEventList::back(void) const { + return *list.back(); +} + +// +// MidiEventList::last -- Alias for MidiEventList::back(). +// + +MidiEvent& MidiEventList::last(void) { + return back(); +} + + +const MidiEvent& MidiEventList::last(void) const { + return back(); +} + + + +////////////////////////////// +// +// MidiEventList::getEvent -- The same thing as operator[], for +// internal use when operator[] would look more messy. +// + +MidiEvent& MidiEventList::getEvent(int index) { + return *list[index]; +} + + +const MidiEvent& MidiEventList::getEvent(int index) const { + return *list[index]; +} + + + +////////////////////////////// +// +// MidiEventList::clear -- De-allocate any MidiEvents present in the list +// and set the size of the list to 0. +// + +void MidiEventList::clear(void) { + for (auto& item : list) { + if (item != NULL) { + delete item; + item = NULL; + } + } + list.resize(0); +} + + + +////////////////////////////// +// +// MidiEventList::data -- Return the low-level array of MidiMessage +// pointers. This is useful for applying your own sorting +// function to the list. +// + +MidiEvent** MidiEventList::data(void) { + return list.data(); +} + + + +////////////////////////////// +// +// MidiEventList::reserve -- Pre-allocate space in the list for storing +// elements. +// + +void MidiEventList::reserve(int rsize) { + if (rsize > (int)list.size()) { + list.reserve(rsize); + } +} + + +////////////////////////////// +// +// MidiEventList::getSize -- Return the number of MidiEvents stored +// in the list. +// + +int MidiEventList::getSize(void) const { + return (int)list.size(); +} + +// +// MidiEventList::size -- Alias for MidiEventList::getSize(). +// + +int MidiEventList::size(void) const { + return getSize(); +} + +// +// MidiEventList::getEventCount -- Alias for MidiEventList::getSize(). +// + +int MidiEventList::getEventCount(void) const { + return getSize(); +} + + + +////////////////////////////// +// +// MidiEventList::append -- add a MidiEvent at the end of the list. Returns +// the index of the appended event. +// + +int MidiEventList::append(MidiEvent& event) { + MidiEvent* ptr = new MidiEvent(event); + list.push_back(ptr); + return (int)list.size()-1; +} + +// +// MidiEventList::push -- Alias for MidiEventList::append(). +// + +int MidiEventList::push(MidiEvent& event) { + return append(event); +} + +// +// MidiEventList::push_back -- Alias for MidiEventList::append(). +// + +int MidiEventList::push_back(MidiEvent& event) { + return append(event); +} + + + +////////////////////////////// +// +// MidiEventList::removeEmpties -- Remove any MIDI message which contain no +// bytes. This function first deallocates any empty MIDI events, and then +// removes them from the list of events. +// + +void MidiEventList::removeEmpties(void) { + int count = 0; + for (auto& item : list) { + if (item->empty()) { + delete item; + item = NULL; + count++; + } + } + if (count == 0) { + return; + } + std::vector newlist; + newlist.reserve(list.size() - count); + for (auto& item : list) { + if (item) { + newlist.push_back(item); + } + } + list.swap(newlist); +} + + + +////////////////////////////// +// +// MidiEventList::linkNotePairs -- Match note-ones and note-offs together +// There are two models that can be done if two notes are overlapping +// on the same pitch: the first note-off affects the last note-on, +// or the first note-off affects the first note-on. Currently the +// first note-off affects the last note-on, but both methods could +// be implemented with user selectability. The current state of the +// track is assumed to be in time-sorted order. Returns the number +// of linked notes (note-on/note-off pairs). +// + +int MidiEventList::linkEventPairs(void) { + return linkNotePairs(); +} + + +int MidiEventList::linkNotePairs(void) { + + // Note-on states: + // dimension 1: MIDI channel (0-15) + // dimension 2: MIDI key (0-127) (but 0 not used for note-ons) + // dimension 3: List of active note-ons or note-offs. + std::vector>> noteons; + noteons.resize(16); + for (auto& noteon : noteons) { + noteon.resize(128); + } + + // Controller linking: The following General MIDI controller numbers are + // also monitored for linking within the track (but not between tracks). + // hex dec name range + // 40 64 Hold pedal (Sustain) on/off 0..63=off 64..127=on + // 41 65 Portamento on/off 0..63=off 64..127=on + // 42 66 Sustenuto Pedal on/off 0..63=off 64..127=on + // 43 67 Soft Pedal on/off 0..63=off 64..127=on + // 44 68 Legato Pedal on/off 0..63=off 64..127=on + // 45 69 Hold Pedal 2 on/off 0..63=off 64..127=on + // 50 80 General Purpose Button 0..63=off 64..127=on + // 51 81 General Purpose Button 0..63=off 64..127=on + // 52 82 General Purpose Button 0..63=off 64..127=on + // 53 83 General Purpose Button 0..63=off 64..127=on + // 54 84 Undefined on/off 0..63=off 64..127=on + // 55 85 Undefined on/off 0..63=off 64..127=on + // 56 86 Undefined on/off 0..63=off 64..127=on + // 57 87 Undefined on/off 0..63=off 64..127=on + // 58 88 Undefined on/off 0..63=off 64..127=on + // 59 89 Undefined on/off 0..63=off 64..127=on + // 5A 90 Undefined on/off 0..63=off 64..127=on + // 7A 122 Local Keyboard On/Off 0..63=off 64..127=on + + // first keep track of whether the controller is an on/off switch: + std::vector> contmap; + contmap.resize(128); + std::pair zero(0, 0); + std::fill(contmap.begin(), contmap.end(), zero); + contmap[64].first = 1; contmap[64].second = 0; + contmap[65].first = 1; contmap[65].second = 1; + contmap[66].first = 1; contmap[66].second = 2; + contmap[67].first = 1; contmap[67].second = 3; + contmap[68].first = 1; contmap[68].second = 4; + contmap[69].first = 1; contmap[69].second = 5; + contmap[80].first = 1; contmap[80].second = 6; + contmap[81].first = 1; contmap[81].second = 7; + contmap[82].first = 1; contmap[82].second = 8; + contmap[83].first = 1; contmap[83].second = 9; + contmap[84].first = 1; contmap[84].second = 10; + contmap[85].first = 1; contmap[85].second = 11; + contmap[86].first = 1; contmap[86].second = 12; + contmap[87].first = 1; contmap[87].second = 13; + contmap[88].first = 1; contmap[88].second = 14; + contmap[89].first = 1; contmap[89].second = 15; + contmap[90].first = 1; contmap[90].second = 16; + contmap[122].first = 1; contmap[122].second = 17; + + // dimensions: + // 1: mapped controller (0 to 17) + // 2: channel (0 to 15) + std::vector> contevents; + contevents.resize(18); + std::vector> oldstates; + oldstates.resize(18); + for (int i=0; i<18; i++) { + contevents[i].resize(16); + std::fill(contevents[i].begin(), contevents[i].end(), nullptr); + oldstates[i].resize(16); + std::fill(oldstates[i].begin(), oldstates[i].end(), -1); + } + + // Now iterate through the MidiEventList keeping track of note and + // select controller states and linking notes/controllers as needed. + int channel; + int key; + int contnum; + int contval; + int conti; + int contstate; + int counter = 0; + MidiEvent* mev; + MidiEvent* noteon; + for (int i=0; iunlinkEvent(); + if (mev->isNoteOn()) { + // store the note-on to pair later with a note-off message. + key = mev->getKeyNumber(); + channel = mev->getChannel(); + noteons[channel][key].push_back(mev); + } else if (mev->isNoteOff()) { + key = mev->getKeyNumber(); + channel = mev->getChannel(); + if (noteons[channel][key].size() > 0) { + noteon = noteons[channel][key].back(); + noteons[channel][key].pop_back(); + noteon->linkEvent(mev); + counter++; + } + } else if (mev->isController()) { + contnum = mev->getP1(); + if (contmap[contnum].first) { + conti = contmap[contnum].second; + channel = mev->getChannel(); + contval = mev->getP2(); + contstate = contval < 64 ? 0 : 1; + if ((oldstates[conti][channel] == -1) && contstate) { + // a newly initialized onstate was detected, so store for + // later linking to an off state. + contevents[conti][channel] = mev; + oldstates[conti][channel] = contstate; + } else if (oldstates[conti][channel] == contstate) { + // the controller state is redundant and will be ignored. + } else if ((oldstates[conti][channel] == 0) && contstate) { + // controller is currently off, so store on-state for next link + contevents[conti][channel] = mev; + oldstates[conti][channel] = contstate; + } else if ((oldstates[conti][channel] == 1) && (contstate == 0)) { + // controller has just been turned off, so link to + // stored on-message. + contevents[conti][channel]->linkEvent(mev); + oldstates[conti][channel] = contstate; + // not necessary, but maybe use for something later: + contevents[conti][channel] = mev; + } + } + } + } + return counter; +} + + + +////////////////////////////// +// +// MidiEventList::clearLinks -- remove all note-on/note-off links. +// + +void MidiEventList::clearLinks(void) { + for (int i=0; i<(int)getSize(); i++) { + getEvent(i).unlinkEvent(); + } +} + + + +////////////////////////////// +// +// MidiEventList::clearSequence -- Remove any sequence serial numbers from +// MidiEvents in the list. This will cause the default ordering by +// sortTracks() to be used, in which case the ordering of MidiEvents +// occurring at the same tick may switch their ordering. +// + +void MidiEventList::clearSequence(void) { + for (int i=0; i bevent.tick) { + // aevent occurs after bevent + return +1; + } else if (aevent.tick < bevent.tick) { + // aevent occurs before bevent + return -1; + } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq > bevent.seq)) { + // aevent sequencing state occurs after bevent + // see MidiEventList::markSequence() + return +1; + } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq < bevent.seq)) { + // aevent sequencing state occurs before bevent + // see MidiEventList::markSequence() + return -1; + } else if (aevent.getP0() == 0xff && aevent.getP1() == 0x2f) { + // end-of-track meta-message should always be last (but won't really + // matter since the writing function ignores all end-of-track messages + // and writes its own. + return +1; + } else if (bevent.getP0() == 0xff && bevent.getP1() == 0x2f) { + // end-of-track meta-message should always be last (but won't really + // matter since the writing function ignores all end-of-track messages + // and writes its own. + return -1; + } else if (aevent.getP0() == 0xff && bevent.getP0() != 0xff) { + // other meta-messages are placed before real MIDI messages + return -1; + } else if (aevent.getP0() != 0xff && bevent.getP0() == 0xff) { + // other meta-messages are placed before real MIDI messages + return +1; + } else if (((aevent.getP0() & 0xf0) == 0x90) && (aevent.getP2() != 0)) { + // note-ons come after all other types of MIDI messages + return +1; + } else if (((bevent.getP0() & 0xf0) == 0x90) && (bevent.getP2() != 0)) { + // note-ons come after all other types of MIDI messages + return -1; + } else if (((aevent.getP0() & 0xf0) == 0x90) || ((aevent.getP0() & 0xf0) == 0x80)) { + // note-offs come after all other MIDI messages (except note-ons) + return +1; + } else if (((bevent.getP0() & 0xf0) == 0x90) || ((bevent.getP0() & 0xf0) == 0x80)) { + // note-offs come after all other MIDI messages (except note-ons) + return -1; + } else if (((aevent.getP0() & 0xf0) == 0xb0) && ((bevent.getP0() & 0xf0) == 0xb0)) { + // both events are continuous controllers. Sort them by controller number + if (aevent.getP1() > bevent.getP1()) { + return +1; + } if (aevent.getP1() < bevent.getP1()) { + return -1; + } else { + // same controller number, so sort by data value + if (aevent.getP2() > bevent.getP2()) { + return +1; + } if (aevent.getP2() < bevent.getP2()) { + return -1; + } else { + return 0; + } + } + } else { + return 0; + } +} + + +} // end namespace smf + + + diff --git a/src/3rdparty/midifile/MidiEventList.h b/src/3rdparty/midifile/MidiEventList.h new file mode 100644 index 0000000000..f9b1e8538e --- /dev/null +++ b/src/3rdparty/midifile/MidiEventList.h @@ -0,0 +1,82 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 21:55:38 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/MidiEventList.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class that stores a MidiEvents for a MidiFile track. +// + +#ifndef _MIDIEVENTLIST_H_INCLUDED +#define _MIDIEVENTLIST_H_INCLUDED + +#include "MidiEvent.h" + +#include + + +namespace smf { + +class MidiEventList { + public: + MidiEventList (void); + MidiEventList (const MidiEventList& other); + MidiEventList (MidiEventList&& other); + + ~MidiEventList (); + + MidiEventList& operator= (MidiEventList& other); + MidiEvent& operator[] (int index); + const MidiEvent& operator[] (int index) const; + + MidiEvent& back (void); + const MidiEvent& back (void) const; + MidiEvent& last (void); + const MidiEvent& last (void) const; + MidiEvent& getEvent (int index); + const MidiEvent& getEvent (int index) const; + void clear (void); + void reserve (int rsize); + int getEventCount (void) const; + int getSize (void) const; + int size (void) const; + void removeEmpties (void); + int linkNotePairs (void); + int linkEventPairs (void); + void clearLinks (void); + void clearSequence (void); + int markSequence (int sequence = 1); + + int push (MidiEvent& event); + int push_back (MidiEvent& event); + int append (MidiEvent& event); + + // careful when using these, intended for internal use in MidiFile class: + void detach (void); + int push_back_no_copy (MidiEvent* event); + + // access to the list of MidiEvents for sorting with an external function: + MidiEvent** data (void); + + protected: + std::vector list; + + private: + void sort (void); + + // MidiFile class calls sort() + friend class MidiFile; +}; + + +int eventcompare(const void* a, const void* b); + +} // end of namespace smf + +#endif /* _MIDIEVENTLIST_H_INCLUDED */ + + + diff --git a/src/3rdparty/midifile/MidiFile.cpp b/src/3rdparty/midifile/MidiFile.cpp new file mode 100644 index 0000000000..55956c8ce7 --- /dev/null +++ b/src/3rdparty/midifile/MidiFile.cpp @@ -0,0 +1,3465 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Fri Nov 26 14:12:01 PST 1999 +// Last Modified: Thu Jun 24 18:35:30 PDT 2021 Added base64 encoding read/write +// Filename: midifile/src/MidiFile.cpp +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class which can read/write Standard MIDI files. +// MIDI data is stored by track in an array. This +// class is used for example in the MidiPerform class. +// + +#include "MidiFile.h" +#include "Binasc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace smf { + + +const std::string MidiFile::encodeLookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +const std::vector MidiFile::decodeLookup { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +}; + + +const char* MidiFile::GMinstrument[128] = { + "acoustic grand piano", "bright acoustic piano", "electric grand piano", "honky-tonk piano", "rhodes piano", "chorused piano", + "harpsichord", "clavinet", "celeste", "glockenspiel", "music box", "vibraphone", + "marimba", "xylophone", "tubular bells", "dulcimer", "hammond organ", "percussive organ", + "rock organ", "church organ", "reed organ", "accordion", "harmonica", "tango accordion", + "nylon guitar", "steel guitar", "jazz guitar", "clean guitar", "muted guitar", "overdriven guitar", + "distortion guitar", "guitar harmonics", "acoustic bass", "fingered electric bass", "picked electric bass", "fretless bass", + "slap bass 1", "slap bass 2", "synth bass 1", "synth bass 2", "violin", "viola", + "cello", "contrabass", "tremolo strings", "pizzcato strings", "orchestral harp", "timpani", + "string ensemble 1", "string ensemble 2", "synth strings 1", "synth strings 1", "choir aahs", "voice oohs", + "synth voices", "orchestra hit", "trumpet", "trombone", "tuba", "muted trumpet", + "frenc horn", "brass section", "syn brass 1", "synth brass 2", "soprano sax", "alto sax", + "tenor sax", "baritone sax", "oboe", "english horn", "bassoon", "clarinet", + "piccolo", "flute", "recorder", "pan flute", "bottle blow", "shakuhachi", + "whistle", "ocarina", "square wave", "saw wave", "calliope lead", "chiffer lead", + "charang lead", "voice lead", "fifths lead", "brass lead", "newage pad", "warm pad", + "polysyn pad", "choir pad", "bowed pad", "metallic pad", "halo pad", "sweep pad", + "rain", "soundtrack", "crystal", "atmosphere", "brightness", "goblins", + "echoes", "sci-fi", "sitar", "banjo", "shamisen", "koto", + "kalimba", "bagpipes", "fiddle", "shanai", "tinkle bell", "agogo", + "steel drums", "woodblock", "taiko drum", "melodoc tom", "synth drum", "reverse cymbal", + "guitar fret noise", "breath noise", "seashore", "bird tweet", "telephone ring", "helicopter", + "applause", "gunshot" +}; + + + +////////////////////////////// +// +// MidiFile::MidiFile -- Constructor. +// + +MidiFile::MidiFile(void) { + m_events.resize(1); + for (auto &event : m_events) { + event = new MidiEventList; + } +} + + +MidiFile::MidiFile(const std::string& filename) { + m_events.resize(1); + for (auto &event : m_events) { + event = new MidiEventList; + } + read(filename); +} + + +MidiFile::MidiFile(std::istream& input) { + m_events.resize(1); + for (auto &event : m_events) { + event = new MidiEventList; + } + read(input); +} + + + +MidiFile::MidiFile(const MidiFile& other) { + *this = other; +} + + + +MidiFile::MidiFile(MidiFile&& other) { + *this = std::move(other); +} + + + +////////////////////////////// +// +// MidiFile::~MidiFile -- Deconstructor. +// + +MidiFile::~MidiFile() { + m_readFileName.clear(); + clear(); + if (m_events[0] != NULL) { + delete m_events[0]; + m_events[0] = NULL; + } + m_events.resize(0); + m_rwstatus = false; + m_timemap.clear(); + m_timemapvalid = 0; +} + + + +////////////////////////////// +// +// MidiFile::operator= -- Copying another +// + +MidiFile& MidiFile::operator=(const MidiFile& other) { + if (this == &other) { + return *this; + } + m_events.reserve(other.m_events.size()); + auto it = other.m_events.begin(); + std::generate_n(std::back_inserter(m_events), other.m_events.size(), + [&]()->MidiEventList* { + return new MidiEventList(**it++); + } + ); + m_ticksPerQuarterNote = other.m_ticksPerQuarterNote; + m_theTrackState = other.m_theTrackState; + m_theTimeState = other.m_theTimeState; + m_readFileName = other.m_readFileName; + m_timemapvalid = other.m_timemapvalid; + m_timemap = other.m_timemap; + m_rwstatus = other.m_rwstatus; + if (other.m_linkedEventsQ) { + linkEventPairs(); + } + return *this; +} + + +MidiFile& MidiFile::operator=(MidiFile&& other) { + m_events = std::move(other.m_events); + m_linkedEventsQ = other.m_linkedEventsQ; + other.m_linkedEventsQ = false; + other.m_events.clear(); + other.m_events.emplace_back(new MidiEventList); + m_ticksPerQuarterNote = other.m_ticksPerQuarterNote; + m_theTrackState = other.m_theTrackState; + m_theTimeState = other.m_theTimeState; + m_readFileName = other.m_readFileName; + m_timemapvalid = other.m_timemapvalid; + m_timemap = other.m_timemap; + m_rwstatus = other.m_rwstatus; + return *this; +} + + +/////////////////////////////////////////////////////////////////////////// +// +// reading/writing functions -- +// + + +////////////////////////////// +// +// MidiFile::read -- Parse a Standard MIDI File or ASCII-encoded Standard MIDI +// File and store its contents in the object. +// + +bool MidiFile::read(const std::string& filename) { + m_timemapvalid = 0; + setFilename(filename); + m_rwstatus = true; + + std::fstream input; + input.open(filename.c_str(), std::ios::binary | std::ios::in); + + if (!input.is_open()) { + m_rwstatus = false; + return m_rwstatus; + } + + m_rwstatus = read(input); + return m_rwstatus; +} + +// +// istream version of read(). +// + +bool MidiFile::read(std::istream& input) { + m_rwstatus = true; + if (input.peek() != 'M') { + // If the first byte in the input stream is not 'M', then presume that + // the MIDI file is in the binasc format which is an ASCII representation + // of the MIDI file. Convert the binasc content into binary content and + // then continue reading with this function. + std::stringstream binarydata; + Binasc binasc; + binasc.writeToBinary(binarydata, input); + binarydata.seekg(0, std::ios_base::beg); + if (binarydata.peek() != 'M') { + std::cerr << "Bad MIDI data input" << std::endl; + m_rwstatus = false; + return m_rwstatus; + } else { + m_rwstatus = readSmf(binarydata); + return m_rwstatus; + } + } else { + m_rwstatus = readSmf(input); + return m_rwstatus; + } +} + + + +////////////////////////////// +// +// MidiFile::readBase64 -- First decode base64 string and then parse as either a +// Standard MIDI File or binasc-encoded Standard MIDI File. +// + +bool MidiFile::readBase64(const std::string& base64data) { + std::stringstream stream; + stream << MidiFile::base64Decode(base64data); + return MidiFile::read(stream); +} + +bool MidiFile::readBase64(std::istream& instream) { + std::string base64data((std::istreambuf_iterator(instream)), + std::istreambuf_iterator()); + std::stringstream stream; + stream << MidiFile::base64Decode(base64data); + return MidiFile::read(stream); +} + + + +////////////////////////////// +// +// MidiFile::readSmf -- Parse a Standard MIDI File and store its contents +// in the object. +// + +bool MidiFile::readSmf(const std::string& filename) { + m_timemapvalid = 0; + setFilename(filename); + m_rwstatus = true; + + std::fstream input; + input.open(filename.c_str(), std::ios::binary | std::ios::in); + + if (!input.is_open()) { + m_rwstatus = false; + return m_rwstatus; + } + + m_rwstatus = readSmf(input); + return m_rwstatus; +} + + + +////////////////////////////// +// +// MidiFile::readSmf -- Parse a Standard MIDI File and store its contents in the object. +// + +bool MidiFile::readSmf(std::istream& input) { + m_rwstatus = true; + + std::string filename = getFilename(); + + int character; + // uchar buffer[123456] = {0}; + ulong longdata; + ushort shortdata; + + // Read the MIDI header (4 bytes of ID, 4 byte data size, + // anticipated 6 bytes of data. + + character = input.get(); + if (character == EOF) { + std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; + std::cerr << "Expecting 'M' at first byte, but found nothing." << std::endl; + m_rwstatus = false; return m_rwstatus; + } else if (character != 'M') { + std::cerr << "File " << filename << " is not a MIDI file" << std::endl; + std::cerr << "Expecting 'M' at first byte but got '" + << (char)character << "'" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + character = input.get(); + if (character == EOF) { + std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; + std::cerr << "Expecting 'T' at second byte, but found nothing." << std::endl; + m_rwstatus = false; return m_rwstatus; + } else if (character != 'T') { + std::cerr << "File " << filename << " is not a MIDI file" << std::endl; + std::cerr << "Expecting 'T' at second byte but got '" + << (char)character << "'" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + character = input.get(); + if (character == EOF) { + std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; + std::cerr << "Expecting 'h' at third byte, but found nothing." << std::endl; + m_rwstatus = false; return m_rwstatus; + } else if (character != 'h') { + std::cerr << "File " << filename << " is not a MIDI file" << std::endl; + std::cerr << "Expecting 'h' at third byte but got '" + << (char)character << "'" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + character = input.get(); + if (character == EOF) { + std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; + std::cerr << "Expecting 'd' at fourth byte, but found nothing." << std::endl; + m_rwstatus = false; return m_rwstatus; + } else if (character != 'd') { + std::cerr << "File " << filename << " is not a MIDI file" << std::endl; + std::cerr << "Expecting 'd' at fourth byte but got '" + << (char)character << "'" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + // read header size (allow larger header size?) + longdata = readLittleEndian4Bytes(input); + if (longdata != 6) { + std::cerr << "File " << filename + << " is not a MIDI 1.0 Standard MIDI file." << std::endl; + std::cerr << "The header size is " << longdata << " bytes." << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + // Header parameter #1: format type + int type; + shortdata = readLittleEndian2Bytes(input); + switch (shortdata) { + case 0: + type = 0; + break; + case 1: + type = 1; + break; + case 2: + // Type-2 MIDI files should probably be allowed as well, + // but I have never seen one in the wild to test with. + default: + std::cerr << "Error: cannot handle a type-" << shortdata + << " MIDI file" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + // Header parameter #2: track count + int tracks; + shortdata = readLittleEndian2Bytes(input); + if (type == 0 && shortdata != 1) { + std::cerr << "Error: Type 0 MIDI file can only contain one track" << std::endl; + std::cerr << "Instead track count is: " << shortdata << std::endl; + m_rwstatus = false; return m_rwstatus; + } else { + tracks = shortdata; + } + clear(); + if (m_events[0] != NULL) { + delete m_events[0]; + } + m_events.resize(tracks); + for (int z=0; zreserve(10000); // Initialize with 10,000 event storage. + m_events[z]->clear(); + } + + // Header parameter #3: Ticks per quarter note + shortdata = readLittleEndian2Bytes(input); + if (shortdata >= 0x8000) { + int framespersecond = 255 - ((shortdata >> 8) & 0x00ff) + 1; + int subframes = shortdata & 0x00ff; + switch (framespersecond) { + case 25: framespersecond = 25; break; + case 24: framespersecond = 24; break; + case 29: framespersecond = 29; break; // really 29.97 for color television + case 30: framespersecond = 30; break; + default: + std::cerr << "Warning: unknown FPS: " << framespersecond << std::endl; + std::cerr << "Using non-standard FPS: " << framespersecond << std::endl; + } + m_ticksPerQuarterNote = framespersecond * subframes; + + // std::cerr << "SMPTE ticks: " << m_ticksPerQuarterNote << " ticks/sec" << std::endl; + // std::cerr << "SMPTE frames per second: " << framespersecond << std::endl; + // std::cerr << "SMPTE subframes per frame: " << subframes << std::endl; + } else { + m_ticksPerQuarterNote = shortdata; + } + + + ////////////////////////////////////////////////// + // + // now read individual tracks: + // + + uchar runningCommand; + MidiEvent event; + std::vector bytes; + int xstatus; + + for (int i=0; ireserve((int)longdata/2); + m_events[i]->clear(); + + // Read MIDI events in the track, which are pairs of VLV values + // and then the bytes for the MIDI message. Running status messages + // will be filled in with their implicit command byte. + // The timestamps are converted from delta ticks to absolute ticks, + // with the absticks variable accumulating the VLV tick values. + int absticks = 0; + while (!input.eof()) { + longdata = readVLValue(input); + absticks += longdata; + xstatus = extractMidiData(input, bytes, runningCommand); + if (xstatus == 0) { + m_rwstatus = false; return m_rwstatus; + } + event.setMessage(bytes); + event.tick = absticks; + event.track = i; + + if (bytes[0] == 0xff && bytes[1] == 0x2f) { + // end-of-track message + // comment out the following line if you don't want to see the + // end of track message (which is always required, and will added + // automatically when a MIDI is written, so it is not necessary. + m_events[i]->push_back(event); + break; + } + m_events[i]->push_back(event); + } + } + + m_theTimeState = TIME_STATE_ABSOLUTE; + + // The original order of the MIDI events is marked with an enumeration which + // allows for reconstruction of the order when merging/splitting tracks to/from + // a type-0 configuration. + markSequence(); + + return m_rwstatus; +} + + + +////////////////////////////// +// +// MidiFile::write -- write a standard MIDI file to a file or an output +// stream. +// + +bool MidiFile::write(const std::string& filename) { + std::fstream output(filename.c_str(), std::ios::binary | std::ios::out); + + if (!output.is_open()) { + std::cerr << "Error: could not write: " << filename << std::endl; + return false; + } + m_rwstatus = write(output); + output.close(); + return m_rwstatus; +} + +// +// ostream version of MidiFile::write(). +// + +bool MidiFile::write(std::ostream& out) { + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_ABSOLUTE) { + makeDeltaTicks(); + } + + // write the header of the Standard MIDI File + char ch; + // 1. The characters "MThd" + ch = 'M'; out << ch; + ch = 'T'; out << ch; + ch = 'h'; out << ch; + ch = 'd'; out << ch; + + // 2. write the size of the header (always a "6" stored in unsigned long + // (4 bytes). + ulong longdata = 6; + writeBigEndianULong(out, longdata); + + // 3. MIDI file format, type 0, 1, or 2 + ushort shortdata; + shortdata = static_cast(getNumTracks() == 1 ? 0 : 1); + writeBigEndianUShort(out,shortdata); + + // 4. write out the number of tracks. + shortdata = static_cast(getNumTracks()); + writeBigEndianUShort(out, shortdata); + + // 5. write out the number of ticks per quarternote. (avoiding SMPTE for now) + shortdata = static_cast(getTicksPerQuarterNote()); + writeBigEndianUShort(out, shortdata); + + // now write each track. + std::vector trackdata; + uchar endoftrack[4] = {0, 0xff, 0x2f, 0x00}; + int i, j, k; + int size; + for (i=0; isize(); j++) { + if ((*m_events[i])[j].empty()) { + // Don't write empty m_events (probably a delete message). + continue; + } + if ((*m_events[i])[j].isEndOfTrack()) { + // Suppress end-of-track meta messages (one will be added + // automatically after all track data has been written). + continue; + } + writeVLValue((*m_events[i])[j].tick, trackdata); + if (((*m_events[i])[j].getCommandByte() == 0xf0) || + ((*m_events[i])[j].getCommandByte() == 0xf7)) { + // 0xf0 == Complete sysex message (0xf0 is part of the raw MIDI). + // 0xf7 == Raw byte message (0xf7 not part of the raw MIDI). + // Print the first byte of the message (0xf0 or 0xf7), then + // print a VLV length for the rest of the bytes in the message. + // In other words, when creating a 0xf0 or 0xf7 MIDI message, + // do not insert the VLV byte length yourself, as this code will + // do it for you automatically. + trackdata.push_back((*m_events[i])[j][0]); // 0xf0 or 0xf7; + writeVLValue(((int)(*m_events[i])[j].size())-1, trackdata); + for (k=1; k<(int)(*m_events[i])[j].size(); k++) { + trackdata.push_back((*m_events[i])[j][k]); + } + } else { + // non-sysex type of message, so just output the + // bytes of the message: + for (k=0; k<(int)(*m_events[i])[j].size(); k++) { + trackdata.push_back((*m_events[i])[j][k]); + } + } + } + size = (int)trackdata.size(); + if ((size < 3) || !((trackdata[size-3] == 0xff) + && (trackdata[size-2] == 0x2f))) { + trackdata.push_back(endoftrack[0]); + trackdata.push_back(endoftrack[1]); + trackdata.push_back(endoftrack[2]); + trackdata.push_back(endoftrack[3]); + } + + // now ready to write to MIDI file. + + // first write the track ID marker "MTrk": + ch = 'M'; out << ch; + ch = 'T'; out << ch; + ch = 'r'; out << ch; + ch = 'k'; out << ch; + + // A. write the size of the MIDI data to follow: + longdata = (int)trackdata.size(); + writeBigEndianULong(out, longdata); + + // B. write the actual data + out.write((char*)trackdata.data(), trackdata.size()); + } + + if (oldTimeState == TIME_STATE_ABSOLUTE) { + makeAbsoluteTicks(); + } + + return true; +} + + + +////////////////////////////// +// +// MidiFile::writeBase64 -- Write Standard MIDI file with base64 encoding. +// The width parameter can be used to add line breaks. Zero or negative +// width will prevent linebreaks from being added to the data. +// Default value: width = 0 +// + +bool MidiFile::writeBase64(const std::string& filename, int width) { + std::fstream output(filename.c_str(), std::ios::binary | std::ios::out); + + if (!output.is_open()) { + std::cerr << "Error: could not write: " << filename << std::endl; + return false; + } + m_rwstatus = writeBase64(output, width); + output.close(); + return m_rwstatus; +} + + +bool MidiFile::writeBase64(std::ostream& out, int width) { + std::stringstream raw; + bool status = MidiFile::write(raw); + if (!status) { + return status; + } + std::string encoded = MidiFile::base64Encode(raw.str()); + if (width <= 0) { + out << encoded; + return status; + } + int length = (int)encoded.size(); + for (int i=0; i= 0 ? width : 25; + for (int i=0; iremoveEmpties(); + } +} + + + +////////////////////////////// +// +// MidiFile::markSequence -- Assign a sequence serial number to +// every MidiEvent in every track in the MIDI file. This is +// useful if you want to preseve the order of MIDI messages in +// a track when they occur at the same tick time. Particularly +// for use with joinTracks() or sortTracks(). markSequence will +// be done automatically when a MIDI file is read, in case the +// ordering of m_events occurring at the same time is important. +// Use clearSequence() to use the default sorting behavior of +// sortTracks(). +// + +void MidiFile::markSequence(void) { + int sequence = 1; + for (int i=0; i= 0) && (track < getTrackCount())) { + operator[](track).markSequence(sequence); + } else { + std::cerr << "Warning: track " << track << " does not exist." << std::endl; + } +} + + + +////////////////////////////// +// +// MidiFile::clearSequence -- Remove any sequence serial numbers from +// MidiEvents in the MidiFile. This will cause the default ordering by +// sortTracks() to be used, in which case the ordering of MidiEvents +// occurring at the same tick may switch their ordering. +// + +void MidiFile::clearSequence(void) { + for (int i=0; i= 0) && (track < getTrackCount())) { + operator[](track).clearSequence(); + } else { + std::cerr << "Warning: track " << track << " does not exist." << std::endl; + } +} + + + +////////////////////////////// +// +// MidiFile::joinTracks -- Interleave the data from all tracks, +// but keeping the identity of the tracks unique so that +// the function splitTracks can be called to split the +// tracks into separate units again. The style of the +// MidiFile when read from a file is with tracks split. +// The original track index is stored in the MidiEvent::track +// variable. +// + +void MidiFile::joinTracks(void) { + if (getTrackState() == TRACK_STATE_JOINED) { + return; + } + if (getNumTracks() == 1) { + m_theTrackState = TRACK_STATE_JOINED; + return; + } + + MidiEventList* joinedTrack; + joinedTrack = new MidiEventList; + + int messagesum = 0; + int length = getNumTracks(); + int i, j; + for (i=0; ireserve((int)(messagesum + 32 + messagesum * 0.1)); + + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_DELTA) { + makeAbsoluteTicks(); + } + for (i=0; isize(); j++) { + joinedTrack->push_back_no_copy(&(*m_events[i])[j]); + } + } + + clear_no_deallocate(); + + delete m_events[0]; + m_events.resize(0); + m_events.push_back(joinedTrack); + sortTracks(); + if (oldTimeState == TIME_STATE_DELTA) { + makeDeltaTicks(); + } + + m_theTrackState = TRACK_STATE_JOINED; +} + + + +////////////////////////////// +// +// MidiFile::splitTracks -- Take the joined tracks and split them +// back into their separate track identities. +// + +void MidiFile::splitTracks(void) { + if (getTrackState() == TRACK_STATE_SPLIT) { + return; + } + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_DELTA) { + makeAbsoluteTicks(); + } + + int maxTrack = 0; + int i; + int length = m_events[0]->size(); + for (i=0; i maxTrack) { + maxTrack = (*m_events[0])[i].track; + } + } + int trackCount = maxTrack + 1; + + if (trackCount <= 1) { + return; + } + + MidiEventList* olddata = m_events[0]; + m_events[0] = NULL; + m_events.resize(trackCount); + for (i=0; ipush_back_no_copy(&(*olddata)[i]); + } + + olddata->detach(); + delete olddata; + + if (oldTimeState == TIME_STATE_DELTA) { + makeDeltaTicks(); + } + + m_theTrackState = TRACK_STATE_SPLIT; +} + + + +////////////////////////////// +// +// MidiFile::splitTracksByChannel -- Take the joined tracks and split them +// back into their separate track identities. +// + +void MidiFile::splitTracksByChannel(void) { + joinTracks(); + if (getTrackState() == TRACK_STATE_SPLIT) { + return; + } + + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_DELTA) { + makeAbsoluteTicks(); + } + + int maxTrack = 0; + int i; + MidiEventList& eventlist = *m_events[0]; + MidiEventList* olddata = &eventlist; + int length = eventlist.size(); + for (i=0; i 0) { + trackValue = (eventlist[i][0] & 0x0f) + 1; + } + m_events[trackValue]->push_back_no_copy(&eventlist[i]); + } + + olddata->detach(); + delete olddata; + + if (oldTimeState == TIME_STATE_DELTA) { + makeDeltaTicks(); + } + + m_theTrackState = TRACK_STATE_SPLIT; +} + + + +////////////////////////////// +// +// MidiFile::getTrackState -- returns what type of track method +// is being used: either TRACK_STATE_JOINED or TRACK_STATE_SPLIT. +// + +int MidiFile::getTrackState(void) const { + return m_theTrackState; +} + + + +////////////////////////////// +// +// MidiFile::hasJoinedTracks -- Returns true if the MidiFile tracks +// are in a joined state. +// + +int MidiFile::hasJoinedTracks(void) const { + return m_theTrackState == TRACK_STATE_JOINED; +} + + + +////////////////////////////// +// +// MidiFile::hasSplitTracks -- Returns true if the MidiFile tracks +// are in a split state. +// + +int MidiFile::hasSplitTracks(void) const { + return m_theTrackState == TRACK_STATE_SPLIT; +} + + + +////////////////////////////// +// +// MidiFile::getSplitTrack -- Return the track index when the MidiFile +// is in the split state. This function returns the original track +// when the MidiFile is in the joined state. The MidiEvent::track +// variable is used to store the original track index when the +// MidiFile is converted to the joined-track state. +// + +int MidiFile::getSplitTrack(int track, int index) const { + if (hasSplitTracks()) { + return track; + } else { + return getEvent(track, index).track; + } +} + +// +// When the parameter is void, assume track 0: +// + +int MidiFile::getSplitTrack(int index) const { + if (hasSplitTracks()) { + return 0; + } else { + return getEvent(0, index).track; + } +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// tick-related functions -- +// + +////////////////////////////// +// +// MidiFile::makeDeltaTicks -- convert the time data to +// delta time, which means that the time field +// in the MidiEvent struct represents the time +// since the last event was played. When a MIDI file +// is read from a file, this is the default setting. +// + +void MidiFile::makeDeltaTicks(void) { + if (getTickState() == TIME_STATE_DELTA) { + return; + } + int i, j; + int temp; + int length = getNumTracks(); + int *timedata = new int[length]; + for (i=0; isize() > 0) { + timedata[i] = (*m_events[i])[0].tick; + } else { + continue; + } + for (j=1; j<(int)m_events[i]->size(); j++) { + temp = (*m_events[i])[j].tick; + int deltatick = temp - timedata[i]; + if (deltatick < 0) { + std::cerr << "Error: negative delta tick value: " << deltatick << std::endl + << "Timestamps must be sorted first" + << " (use MidiFile::sortTracks() before writing)." << std::endl; + } + (*m_events[i])[j].tick = deltatick; + timedata[i] = temp; + } + } + m_theTimeState = TIME_STATE_DELTA; + delete [] timedata; +} + +// +// MidiFile::deltaTicks -- Alias for MidiFile::makeDeltaTicks(). +// + +void MidiFile::deltaTicks(void) { + makeDeltaTicks(); +} + + + +////////////////////////////// +// +// MidiFile::makeAbsoluteTicks -- convert the time data to +// absolute time, which means that the time field +// in the MidiEvent struct represents the exact tick +// time to play the event rather than the time since +// the last event to wait until playing the current +// event. +// + +void MidiFile::makeAbsoluteTicks(void) { + if (getTickState() == TIME_STATE_ABSOLUTE) { + return; + } + int i, j; + int length = getNumTracks(); + int* timedata = new int[length]; + for (i=0; isize() > 0) { + timedata[i] = (*m_events[i])[0].tick; + } else { + continue; + } + for (j=1; j<(int)m_events[i]->size(); j++) { + timedata[i] += (*m_events[i])[j].tick; + (*m_events[i])[j].tick = timedata[i]; + } + } + m_theTimeState = TIME_STATE_ABSOLUTE; + delete [] timedata; +} + +// +// MidiFile::absoluteTicks -- Alias for MidiFile::makeAbsoluteTicks(). +// + +void MidiFile::absoluteTicks(void) { + makeAbsoluteTicks(); +} + + + +////////////////////////////// +// +// MidiFile::getTickState -- returns what type of time method is +// being used: either TIME_STATE_ABSOLUTE or TIME_STATE_DELTA. +// + +int MidiFile::getTickState(void) const { + return m_theTimeState; +} + + + +////////////////////////////// +// +// MidiFile::isDeltaTicks -- Returns true if MidiEvent .tick +// variables are in delta time mode. +// + +bool MidiFile::isDeltaTicks(void) const { + return m_theTimeState == TIME_STATE_DELTA ? true : false; +} + + + +////////////////////////////// +// +// MidiFile::isAbsoluteTicks -- Returns true if MidiEvent .tick +// variables are in absolute time mode. +// + +bool MidiFile::isAbsoluteTicks(void) const { + return m_theTimeState == TIME_STATE_ABSOLUTE ? true : false; +} + + + +////////////////////////////// +// +// MidiFile::getFileDurationInTicks -- Returns the largest +// tick value in any track. The tracks must be sorted +// before calling this function, since this function +// assumes that the last MidiEvent in the track has the +// highest tick timestamp. The file state can be in delta +// ticks since this function will temporarily go to absolute +// tick mode for the calculation of the max tick. +// + +int MidiFile::getFileDurationInTicks(void) { + bool revertToDelta = false; + if (isDeltaTicks()) { + makeAbsoluteTicks(); + revertToDelta = true; + } + const MidiFile& mf = *this; + int output = 0; + for (int i=0; i output) { + output = mf[i].back().tick; + } + } + if (revertToDelta) { + deltaTicks(); + } + return output; +} + + + +/////////////////////////////// +// +// MidiFile::getFileDurationInQuarters -- Returns the Duration of the MidiFile +// in units of quarter notes. If the MidiFile is in delta tick mode, +// then temporarily got into absolute tick mode to do the calculations. +// Note that this is expensive, so you should normally call this function +// while in absolute tick (default) mode. +// + +double MidiFile::getFileDurationInQuarters(void) { + return (double)getFileDurationInTicks() / (double)getTicksPerQuarterNote(); +} + + + +////////////////////////////// +// +// MidiFile::getFileDurationInSeconds -- returns the duration of the +// longest track in the file. The tracks must be sorted before +// calling this function, since this function assumes that the +// last MidiEvent in the track has the highest timestamp. +// The file state can be in delta ticks since this function +// will temporarily go to absolute tick mode for the calculation +// of the max time. + +double MidiFile::getFileDurationInSeconds(void) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + bool revertToDelta = false; + if (isDeltaTicks()) { + makeAbsoluteTicks(); + revertToDelta = true; + } + const MidiFile& mf = *this; + double output = 0.0; + for (int i=0; i output) { + output = mf[i].back().seconds; + } + } + if (revertToDelta) { + deltaTicks(); + } + return output; +} + + +/////////////////////////////////////////////////////////////////////////// +// +// physical-time analysis functions -- +// + +////////////////////////////// +// +// MidiFile::doTimeAnalysis -- Identify the real-time position of +// all events by monitoring the tempo in relations to the tick +// times in the file. +// + +void MidiFile::doTimeAnalysis(void) { + buildTimeMap(); +} + + + +////////////////////////////// +// +// MidiFile::getTimeInSeconds -- return the time in seconds for +// the current message. +// + +double MidiFile::getTimeInSeconds(int aTrack, int anIndex) { + return getTimeInSeconds(getEvent(aTrack, anIndex).tick); +} + + +double MidiFile::getTimeInSeconds(int tickvalue) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + + _TickTime key; + key.tick = tickvalue; + key.seconds = -1; + + void* ptr = bsearch(&key, m_timemap.data(), m_timemap.size(), + sizeof(_TickTime), ticksearch); + + if (ptr == NULL) { + // The specific tick value was not found, so do a linear + // search for the two tick values which occur before and + // after the tick value, and do a linear interpolation of + // the time in seconds values to figure out the final + // time in seconds. + // Since the code is not yet written, kill the program at this point: + return linearSecondInterpolationAtTick(tickvalue); + } else { + return ((_TickTime*)ptr)->seconds; + } +} + + + +////////////////////////////// +// +// MidiFile::getAbsoluteTickTime -- return the tick value represented +// by the input time in seconds. If there is not tick entry at +// the given time in seconds, then interpolate between two values. +// + +double MidiFile::getAbsoluteTickTime(double starttime) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + + _TickTime key; + key.tick = -1; + key.seconds = starttime; + + void* ptr = bsearch(&key, m_timemap.data(), m_timemap.size(), + sizeof(_TickTime), secondsearch); + + if (ptr == NULL) { + // The specific seconds value was not found, so do a linear + // search for the two time values which occur before and + // after the given time value, and do a linear interpolation of + // the time in tick values to figure out the final time in ticks. + return linearTickInterpolationAtSecond(starttime); + } else { + return ((_TickTime*)ptr)->tick; + } + +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// note-analysis functions -- +// + +////////////////////////////// +// +// MidiFile::linkNotePairs -- Link note-ons to note-offs separately +// for each track. Returns the total number of note message pairs +// that were linked. +// + +int MidiFile::linkNotePairs(void) { + int i; + int sum = 0; + for (i=0; ilinkNotePairs(); + } + m_linkedEventsQ = true; + return sum; +} + +// +// MidiFile::linkEventPairs -- Alias for MidiFile::linkNotePairs(). +// + +int MidiFile::linkEventPairs(void) { + return linkNotePairs(); +} + + +/////////////////////////////////////////////////////////////////////////// +// +// filename functions -- +// + +////////////////////////////// +// +// MidiFile::setFilename -- sets the filename of the MIDI file. +// Currently removed any directory path. +// + +void MidiFile::setFilename(const std::string& aname) { + auto loc = aname.rfind('/'); + if (loc != std::string::npos) { + m_readFileName = aname.substr(loc+1); + } else { + m_readFileName = aname; + } +} + + + +////////////////////////////// +// +// MidiFile::getFilename -- returns the name of the file read into the +// structure (if the data was read from a file). +// + +const char* MidiFile::getFilename(void) const { + return m_readFileName.c_str(); +} + + + +////////////////////////////// +// +// MidiFile::addEvent -- +// + +MidiEvent* MidiFile::addEvent(int aTrack, int aTick, + std::vector& midiData) { + m_timemapvalid = 0; + MidiEvent* me = new MidiEvent; + me->tick = aTick; + me->track = aTrack; + me->setMessage(midiData); + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addEvent -- Some bug here when joinedTracks(), but track==1... +// + +MidiEvent* MidiFile::addEvent(MidiEvent& mfevent) { + if (getTrackState() == TRACK_STATE_JOINED) { + m_events[0]->push_back(mfevent); + return &m_events[0]->back(); + } else { + m_events.at(mfevent.track)->push_back(mfevent); + return &m_events.at(mfevent.track)->back(); + } +} + +// +// Variant where the track is an input parameter: +// + +MidiEvent* MidiFile::addEvent(int aTrack, MidiEvent& mfevent) { + if (getTrackState() == TRACK_STATE_JOINED) { + m_events[0]->push_back(mfevent); + m_events[0]->back().track = aTrack; + return &m_events[0]->back(); + } else { + m_events.at(aTrack)->push_back(mfevent); + m_events.at(aTrack)->back().track = aTrack; + return &m_events.at(aTrack)->back(); + } +} + + + +/////////////////////////////// +// +// MidiFile::addMetaEvent -- +// + +MidiEvent* MidiFile::addMetaEvent(int aTrack, int aTick, int aType, + std::vector& metaData) { + m_timemapvalid = 0; + int i; + int length = (int)metaData.size(); + std::vector fulldata; + uchar size[23] = {0}; + int lengthsize = makeVLV(size, length); + + fulldata.resize(2+lengthsize+length); + fulldata[0] = 0xff; + fulldata[1] = aType & 0x7F; + for (i=0; i buffer; + buffer.resize(length); + int i; + for (i=0; imakeText(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addCopyright -- Add a copyright notice meta-message (#2). +// + +MidiEvent* MidiFile::addCopyright(int aTrack, int aTick, const std::string& text) { + MidiEvent* me = new MidiEvent; + me->makeCopyright(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addTrackName -- Add an track name meta-message (#3). +// + +MidiEvent* MidiFile::addTrackName(int aTrack, int aTick, const std::string& name) { + MidiEvent* me = new MidiEvent; + me->makeTrackName(name); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addInstrumentName -- Add an instrument name meta-message (#4). +// + +MidiEvent* MidiFile::addInstrumentName(int aTrack, int aTick, + const std::string& name) { + MidiEvent* me = new MidiEvent; + me->makeInstrumentName(name); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addLyric -- Add a lyric meta-message (meta #5). +// + +MidiEvent* MidiFile::addLyric(int aTrack, int aTick, const std::string& text) { + MidiEvent* me = new MidiEvent; + me->makeLyric(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addMarker -- Add a marker meta-message (meta #6). +// + +MidiEvent* MidiFile::addMarker(int aTrack, int aTick, const std::string& text) { + MidiEvent* me = new MidiEvent; + me->makeMarker(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addCue -- Add a cue-point meta-message (meta #7). +// + +MidiEvent* MidiFile::addCue(int aTrack, int aTick, const std::string& text) { + MidiEvent* me = new MidiEvent; + me->makeCue(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addTempo -- Add a tempo meta message (meta #0x51). +// + +MidiEvent* MidiFile::addTempo(int aTrack, int aTick, double aTempo) { + MidiEvent* me = new MidiEvent; + me->makeTempo(aTempo); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addKeySignature -- Add a key signature meta message +// (meta #0x59). +// +// Default values: +// fifths == 0 (C) +// mode == 0 (major) +// +// Key signature of b minor would be: +// fifths = 2 +// mode = 1 +// + +MidiEvent* MidiFile::addKeySignature (int aTrack, int aTick, int fifths, bool mode) { + MidiEvent* me = new MidiEvent; + me->makeKeySignature(fifths, mode); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addTimeSignature -- Add a time signature meta message +// (meta #0x58). The "bottom" parameter must be a power of two; +// otherwise, it will be set to the next highest power of two. +// +// Default values: +// clocksPerClick == 24 (quarter note) +// num32ndsPerQuarter == 8 (8 32nds per quarter note) +// +// Time signature of 4/4 would be: +// top = 4 +// bottom = 4 (converted to 2 in the MIDI file for 2nd power of 2). +// clocksPerClick = 24 (2 eighth notes based on num32ndsPerQuarter) +// num32ndsPerQuarter = 8 +// +// Time signature of 6/8 would be: +// top = 6 +// bottom = 8 (converted to 3 in the MIDI file for 3rd power of 2). +// clocksPerClick = 36 (3 eighth notes based on num32ndsPerQuarter) +// num32ndsPerQuarter = 8 +// + +MidiEvent* MidiFile::addTimeSignature(int aTrack, int aTick, int top, int bottom, + int clocksPerClick, int num32ndsPerQuarter) { + MidiEvent* me = new MidiEvent; + me->makeTimeSignature(top, bottom, clocksPerClick, num32ndsPerQuarter); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addCompoundTimeSignature -- Add a time signature meta message +// (meta #0x58), where the clocksPerClick parameter is set to three +// eighth notes for compound meters such as 6/8 which represents +// two beats per measure. +// +// Default values: +// clocksPerClick == 36 (quarter note) +// num32ndsPerQuarter == 8 (8 32nds per quarter note) +// + +MidiEvent* MidiFile::addCompoundTimeSignature(int aTrack, int aTick, int top, + int bottom, int clocksPerClick, int num32ndsPerQuarter) { + return addTimeSignature(aTrack, aTick, top, bottom, clocksPerClick, + num32ndsPerQuarter); +} + + + +////////////////////////////// +// +// MidiFile::makeVLV -- This function is used to create +// size byte(s) for meta-messages. If the size of the data +// in the meta-message is greater than 127, then the size +// should (?) be specified as a VLV. +// + +int MidiFile::makeVLV(uchar *buffer, int number) { + + unsigned long value = (unsigned long)number; + + if (value >= (1 << 28)) { + std::cerr << "Error: Meta-message size too large to handle" << std::endl; + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + return 1; + } + + buffer[0] = (value >> 21) & 0x7f; + buffer[1] = (value >> 14) & 0x7f; + buffer[2] = (value >> 7) & 0x7f; + buffer[3] = (value >> 0) & 0x7f; + + int i; + int flag = 0; + int length = -1; + for (i=0; i<3; i++) { + if (buffer[i] != 0) { + flag = 1; + } + if (flag) { + buffer[i] |= 0x80; + } + if (length == -1 && buffer[i] >= 0x80) { + length = 4-i; + } + } + + if (length == -1) { + length = 1; + } + + if (length < 4) { + for (i=0; imakeNoteOn(aChannel, key, vel); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addNoteOff -- Add a note-off message (using 0x80 messages). +// + +MidiEvent* MidiFile::addNoteOff(int aTrack, int aTick, int aChannel, int key, + int vel) { + MidiEvent* me = new MidiEvent; + me->makeNoteOff(aChannel, key, vel); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addNoteOff -- Add a note-off message (using 0x90 messages with +// zero attack velocity). +// + +MidiEvent* MidiFile::addNoteOff(int aTrack, int aTick, int aChannel, int key) { + MidiEvent* me = new MidiEvent; + me->makeNoteOff(aChannel, key); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addController -- Add a controller message in the given +// track at the given tick time in the given channel. +// + +MidiEvent* MidiFile::addController(int aTrack, int aTick, int aChannel, + int num, int value) { + MidiEvent* me = new MidiEvent; + me->makeController(aChannel, num, value); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addPatchChange -- Add a patch-change message in the given +// track at the given tick time in the given channel. +// + +MidiEvent* MidiFile::addPatchChange(int aTrack, int aTick, int aChannel, + int patchnum) { + MidiEvent* me = new MidiEvent; + me->makePatchChange(aChannel, patchnum); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addTimbre -- Add a patch-change message in the given +// track at the given tick time in the given channel. Alias for +// MidiFile::addPatchChange(). +// + +MidiEvent* MidiFile::addTimbre(int aTrack, int aTick, int aChannel, int patchnum) { + return addPatchChange(aTrack, aTick, aChannel, patchnum); +} + + + +////////////////////////////// +// +// MidiFile::addPitchBend -- convert number in the range from -1 to +1 +// into two 7-bit numbers (smallest piece first) +// +// -1.0 maps to 0 (0x0000) +// 0.0 maps to 8192 (0x2000 --> 0x40 0x00) +// +1.0 maps to 16383 (0x3FFF --> 0x7F 0x7F) +// + +MidiEvent* MidiFile::addPitchBend(int aTrack, int aTick, int aChannel, double amount) { + m_timemapvalid = 0; + amount += 1.0; + int value = int(amount * 8192 + 0.5); + + // prevent any wrap-around in case of round-off errors + if (value > 0x3fff) { + value = 0x3fff; + } + if (value < 0) { + value = 0; + } + + int lsbint = 0x7f & value; + int msbint = 0x7f & (value >> 7); + + std::vector mididata; + mididata.resize(3); + if (aChannel < 0) { + aChannel = 0; + } else if (aChannel > 15) { + aChannel = 15; + } + mididata[0] = uchar(0xe0 | aChannel); + mididata[1] = uchar(lsbint); + mididata[2] = uchar(msbint); + + return addEvent(aTrack, aTick, mididata); +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// RPN convenience functions: +// + +////////////////////////////// +// +// MidiFile::setPitchBendRange -- Set the range for the min/max pitch bend +// alteration of a note. Default is 2.0 (meaning +/- 2 semitones from given pitch). +// Fractional values are cents, so 2.5 means a range of two semitones plus 50 cents, +// which is two semitones plus a quarter tone. +// + +void MidiFile::setPitchBendRange(int aTrack, int aTick, int aChannel, double range) { + if (range < 0.0) { + range = -range; + } + if (range > 24.0) { + std::cerr << "Warning: pitch bend range is too large: " << range << std::endl; + std::cerr << "Setting to 24." << std::endl; + range = 24.0; + } + int irange = int(range); + int cents = int((range - irange) * 100.0 + 0.5); + + // Select pitch bend RPN: + addController(aTrack, aTick, aChannel, 101, 0); // RPN selector (byte 1) + addController(aTrack, aTick, aChannel, 100, 0); // RPN selector (byte 2) + + // Set the semitone range (will be +/-range above/below a note): + addController(aTrack, aTick, aChannel, 6, irange); // coarse: number of semitones + addController(aTrack, aTick, aChannel, 38, cents); // fine: cents (1/100ths of semitone) +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// Controller message adding convenience functions: +// + +////////////////////////////// +// +// MidiFile::addSustain -- Add a continuous controller message for the sustain pedal. +// + +MidiEvent* MidiFile::addSustain(int aTrack, int aTick, int aChannel, int value) { + return addController(aTrack, aTick, aChannel, 64, value); +} + +// +// MidiFile::addSustainPedal -- Alias for MidiFile::addSustain(). +// + +MidiEvent* MidiFile::addSustainPedal(int aTrack, int aTick, int aChannel, int value) { + return addSustain(aTrack, aTick, aChannel, value); +} + + + +////////////////////////////// +// +// MidiFile::addSustainOn -- Add a continuous controller message for the sustain pedal on. +// + +MidiEvent* MidiFile::addSustainOn(int aTrack, int aTick, int aChannel) { + return addSustain(aTrack, aTick, aChannel, 127); +} + +// +// MidiFile::addSustainPedalOn -- Alias for MidiFile::addSustainOn(). +// + +MidiEvent* MidiFile::addSustainPedalOn(int aTrack, int aTick, int aChannel) { + return addSustainOn(aTrack, aTick, aChannel); +} + + + +////////////////////////////// +// +// MidiFile::addSustainOff -- Add a continuous controller message for the sustain pedal off. +// + +MidiEvent* MidiFile::addSustainOff(int aTrack, int aTick, int aChannel) { + return addSustain(aTrack, aTick, aChannel, 0); +} + +// +// MidiFile::addSustainPedalOff -- Alias for MidiFile::addSustainOff(). +// + +MidiEvent* MidiFile::addSustainPedalOff(int aTrack, int aTick, int aChannel) { + return addSustainOff(aTrack, aTick, aChannel); +} + + + +////////////////////////////// +// +// MidiFile::addTrack -- adds a blank track at end of the +// track list. Returns the track number of the added +// track. +// + +int MidiFile::addTrack(void) { + int length = getNumTracks(); + m_events.resize(length+1); + m_events[length] = new MidiEventList; + m_events[length]->reserve(10000); + m_events[length]->clear(); + return length; +} + +int MidiFile::addTrack(int count) { + int length = getNumTracks(); + m_events.resize(length+count); + int i; + for (i=0; ireserve(10000); + m_events[length + i]->clear(); + } + return length + count - 1; +} + +// +// MidiFile::addTracks -- Alias for MidiFile::addTrack(). +// + +int MidiFile::addTracks(int count) { + return addTrack(count); +} + + + + +////////////////////////////// +// +// MidiFile::allocateEvents -- +// + +void MidiFile::allocateEvents(int track, int aSize) { + int oldsize = m_events[track]->size(); + if (oldsize < aSize) { + m_events[track]->reserve(aSize); + } +} + + + +////////////////////////////// +// +// MidiFile::deleteTrack -- remove a track from the MidiFile. +// Tracks are numbered starting at track 0. +// + +void MidiFile::deleteTrack(int aTrack) { + int length = getNumTracks(); + if (aTrack < 0 || aTrack >= length) { + return; + } + if (length == 1) { + return; + } + delete m_events[aTrack]; + for (int i=aTrack; isize(); +} + + +int MidiFile::getNumEvents(int aTrack) const { + return m_events[aTrack]->size(); +} + + + +////////////////////////////// +// +// MidiFile::mergeTracks -- combine the data from two +// tracks into one. Placing the data in the first +// track location listed, and Moving the other tracks +// in the file around to fill in the spot where Track2 +// used to be. The results of this function call cannot +// be reversed. +// + +void MidiFile::mergeTracks(int aTrack1, int aTrack2) { + MidiEventList* mergedTrack; + mergedTrack = new MidiEventList; + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_DELTA) { + makeAbsoluteTicks(); + } + int length = getNumTracks(); + for (int i=0; i<(int)m_events[aTrack1]->size(); i++) { + mergedTrack->push_back((*m_events[aTrack1])[i]); + } + for (int j=0; j<(int)m_events[aTrack2]->size(); j++) { + (*m_events[aTrack2])[j].track = aTrack1; + mergedTrack->push_back((*m_events[aTrack2])[j]); + } + + mergedTrack->sort(); + + delete m_events[aTrack1]; + + m_events[aTrack1] = mergedTrack; + + for (int i=aTrack2; isize(); j++) { + (*m_events[i])[j].track = i; + } + } + + m_events[length-1] = NULL; + m_events.resize(length-1); + + if (oldTimeState == TIME_STATE_DELTA) { + deltaTicks(); + } +} + + + +////////////////////////////// +// +// MidiFile::setTicksPerQuarterNote -- +// + +void MidiFile::setTicksPerQuarterNote(int ticks) { + m_ticksPerQuarterNote = ticks; +} + +// +// Alias for setTicksPerQuarterNote: +// + +void MidiFile::setTPQ(int ticks) { + setTicksPerQuarterNote(ticks); +} + + +////////////////////////////// +// +// MidiFile::setMillisecondTicks -- set the ticks per quarter note +// value to milliseconds. The format for this specification is +// highest 8-bits: SMPTE Frame rate (as a negative 2's compliment value). +// lowest 8-bits: divisions per frame (as a positive number). +// for millisecond resolution, the SMPTE value is -25, and the +// frame rate is 40 frame per division. In hexadecimal, these +// values are: -25 = 1110,0111 = 0xE7 and 40 = 0010,1000 = 0x28 +// So setting the ticks per quarter note value to 0xE728 will cause +// delta times in the MIDI file to represent milliseconds. Calling +// this function will not change any exiting timestamps, it will +// only change the meaning of the timestamps. +// + +void MidiFile::setMillisecondTicks(void) { + m_ticksPerQuarterNote = 0xE728; +} + + + +////////////////////////////// +// +// MidiFile::sortTrack -- Sort the specified track in tick order. +// If the MidiEvent::seq variables have been filled in with +// a sequence value, this will preserve the order of the +// events that occur at the same tick time before the sort +// was done. +// + +void MidiFile::sortTrack(int track) { + if ((track >= 0) && (track < getTrackCount())) { + m_events.at(track)->sort(); + } else { + std::cerr << "Warning: track " << track << " does not exist." << std::endl; + } +} + + + +////////////////////////////// +// +// MidiFile::sortTracks -- sort all tracks in the MidiFile. +// + +void MidiFile::sortTracks(void) { + if (m_theTimeState == TIME_STATE_ABSOLUTE) { + for (int i=0; isort(); + } + } else { + std::cerr << "Warning: Sorting only allowed in absolute tick mode."; + } +} + + + +////////////////////////////// +// +// MidiFile::getTrackCountAsType1 -- Return the number of tracks in the +// MIDI file. Returns the size of the events if not in joined state. +// If in joined state, reads track 0 to find the maximum track +// value from the original unjoined tracks. +// + +int MidiFile::getTrackCountAsType1(void) { + if (getTrackState() == TRACK_STATE_JOINED) { + int output = 0; + int i; + for (i=0; i<(int)m_events[0]->size(); i++) { + if (getEvent(0,i).track > output) { + output = getEvent(0,i).track; + } + } + return output+1; // I think the track values are 0 offset... + } else { + return (int)m_events.size(); + } +} + + + +////////////////////////////// +// +// MidiFile::clearLinks -- +// + +void MidiFile::clearLinks(void) { + for (int i=0; iclearLinks(); + } + m_linkedEventsQ = false; +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// private functions +// + +////////////////////////////// +// +// MidiFile::linearTickInterpolationAtSecond -- return the tick value at the +// given input time. +// + +double MidiFile::linearTickInterpolationAtSecond(double seconds) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + + int i; + double lasttime = m_timemap[m_timemap.size()-1].seconds; + // give an error value of -1 if time is out of range of data. + if (seconds < 0.0) { + return -1.0; + } + if (seconds > m_timemap[m_timemap.size()-1].seconds) { + return -1.0; + } + + // Guess which side of the list is closest to target: + // Could do a more efficient algorithm since time values are sorted, + // but good enough for now... + int startindex = -1; + if (seconds < lasttime / 2) { + for (i=0; i<(int)m_timemap.size(); i++) { + if (m_timemap[i].seconds > seconds) { + startindex = i-1; + break; + } else if (m_timemap[i].seconds == seconds) { + startindex = i; + break; + } + } + } else { + for (i=(int)m_timemap.size()-1; i>0; i--) { + if (m_timemap[i].seconds < seconds) { + startindex = i+1; + break; + } else if (m_timemap[i].seconds == seconds) { + startindex = i; + break; + } + } + } + + if (startindex < 0) { + return -1.0; + } + if (startindex >= (int)m_timemap.size()-1) { + return -1.0; + } + + double x1 = m_timemap[startindex].seconds; + double x2 = m_timemap[startindex+1].seconds; + double y1 = m_timemap[startindex].tick; + double y2 = m_timemap[startindex+1].tick; + double xi = seconds; + + return (xi-x1) * ((y2-y1)/(x2-x1)) + y1; +} + + + +////////////////////////////// +// +// MidiFile::linearSecondInterpolationAtTick -- return the time in seconds +// value at the given input tick time. (Ticks input could be made double). +// + +double MidiFile::linearSecondInterpolationAtTick(int ticktime) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + + int i; + double lasttick = m_timemap[m_timemap.size()-1].tick; + // give an error value of -1 if time is out of range of data. + if (ticktime < 0.0) { + return -1; + } + if (ticktime > m_timemap.back().tick) { + return -1; // don't try to extrapolate + } + + // Guess which side of the list is closest to target: + // Could do a more efficient algorithm since time values are sorted, + // but good enough for now... + int startindex = -1; + if (ticktime < lasttick / 2) { + for (i=0; i<(int)m_timemap.size(); i++) { + if (m_timemap[i].tick > ticktime) { + startindex = i-1; + break; + } else if (m_timemap[i].tick == ticktime) { + startindex = i; + break; + } + } + } else { + for (i=(int)m_timemap.size()-1; i>0; i--) { + if (m_timemap[i].tick < ticktime) { + startindex = i; + break; + } else if (m_timemap[i].tick == ticktime) { + startindex = i; + break; + } + } + } + + if (startindex < 0) { + return -1; + } + if (startindex >= (int)m_timemap.size()-1) { + return -1; + } + + if (m_timemap[startindex].tick == ticktime) { + return m_timemap[startindex].seconds; + } + + double x1 = m_timemap[startindex].tick; + double x2 = m_timemap[startindex+1].tick; + double y1 = m_timemap[startindex].seconds; + double y2 = m_timemap[startindex+1].seconds; + double xi = ticktime; + + return (xi-x1) * ((y2-y1)/(x2-x1)) + y1; +} + + + +////////////////////////////// +// +// MidiFile::buildTimeMap -- build an index of the absolute tick values +// found in a MIDI file, and their corresponding time values in +// seconds, taking into consideration tempo change messages. If no +// tempo messages are given (or until they are given, then the +// tempo is set to 120 beats per minute). If SMPTE time code is +// used, then ticks are actually time values. So don't build +// a time map for SMPTE ticks, and just calculate the time in +// seconds from the tick value (1000 ticks per second SMPTE +// is the only mode tested (25 frames per second and 40 subframes +// per frame). +// + +void MidiFile::buildTimeMap(void) { + + // convert the MIDI file to absolute time representation + // in single track mode (and undo if the MIDI file was not + // in that state when this function was called. + // + int trackstate = getTrackState(); + int timestate = getTickState(); + + makeAbsoluteTicks(); + joinTracks(); + + int allocsize = getNumEvents(0); + m_timemap.reserve(allocsize+10); + m_timemap.clear(); + + _TickTime value; + + int lasttick = 0; + int tickinit = 0; + + int i; + int tpq = getTicksPerQuarterNote(); + double defaultTempo = 120.0; + double secondsPerTick = 60.0 / (defaultTempo * tpq); + + double lastsec = 0.0; + double cursec = 0.0; + + for (i=0; i lasttick) || !tickinit) { + tickinit = 1; + + // calculate the current time in seconds: + cursec = lastsec + (curtick - lasttick) * secondsPerTick; + getEvent(0, i).seconds = cursec; + + // store the new tick to second mapping + value.tick = curtick; + value.seconds = cursec; + m_timemap.push_back(value); + lasttick = curtick; + lastsec = cursec; + } + + // update the tempo if needed: + if (getEvent(0,i).isTempo()) { + secondsPerTick = getEvent(0,i).getTempoSPT(getTicksPerQuarterNote()); + } + } + + // reset the states of the tracks or time values if necessary here: + if (timestate == TIME_STATE_DELTA) { + deltaTicks(); + } + if (trackstate == TRACK_STATE_SPLIT) { + splitTracks(); + } + + m_timemapvalid = 1; + +} + + + +////////////////////////////// +// +// MidiFile::extractMidiData -- Extract MIDI data from input +// stream. Return value is 0 if failure; otherwise, returns 1. +// + +int MidiFile::extractMidiData(std::istream& input, std::vector& array, + uchar& runningCommand) { + + int character; + uchar byte; + array.clear(); + int runningQ; + + character = input.get(); + if (character == EOF) { + std::cerr << "Error: unexpected end of file." << std::endl; + return 0; + } else { + byte = (uchar)character; + } + + if (byte < 0x80) { + runningQ = 1; + if (runningCommand == 0) { + std::cerr << "Error: running command with no previous command" << std::endl; + return 0; + } + if (runningCommand >= 0xf0) { + std::cerr << "Error: running status not permitted with meta and sysex" + << " event." << std::endl; + std::cerr << "Byte is 0x" << std::hex << (int)byte << std::dec << std::endl; + return 0; + } + } else { + runningCommand = byte; + runningQ = 0; + } + + array.push_back(runningCommand); + if (runningQ) { + array.push_back(byte); + } + + switch (runningCommand & 0xf0) { + case 0x80: // note off (2 more bytes) + case 0x90: // note on (2 more bytes) + case 0xA0: // aftertouch (2 more bytes) + case 0xB0: // cont. controller (2 more bytes) + case 0xE0: // pitch wheel (2 more bytes) + byte = readByte(input); + if (!status()) { return m_rwstatus; } + if (byte > 0x7f) { + std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; + m_rwstatus = false; return m_rwstatus; + } + array.push_back(byte); + if (!runningQ) { + byte = readByte(input); + if (!status()) { return m_rwstatus; } + if (byte > 0x7f) { + std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; + m_rwstatus = false; return m_rwstatus; + } + array.push_back(byte); + } + break; + case 0xC0: // patch change (1 more byte) + case 0xD0: // channel pressure (1 more byte) + if (!runningQ) { + byte = readByte(input); + if (!status()) { return m_rwstatus; } + if (byte > 0x7f) { + std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; + m_rwstatus = false; return m_rwstatus; + } + array.push_back(byte); + } + break; + case 0xF0: + switch (runningCommand) { + case 0xff: // meta event + { + if (!runningQ) { + byte = readByte(input); // meta type + if (!status()) { return m_rwstatus; } + array.push_back(byte); + } + ulong length = 0; + uchar byte1 = 0; + uchar byte2 = 0; + uchar byte3 = 0; + uchar byte4 = 0; + byte1 = readByte(input); + if (!status()) { return m_rwstatus; } + array.push_back(byte1); + if (byte1 >= 0x80) { + byte2 = readByte(input); + if (!status()) { return m_rwstatus; } + array.push_back(byte2); + if (byte2 > 0x80) { + byte3 = readByte(input); + if (!status()) { return m_rwstatus; } + array.push_back(byte3); + if (byte3 >= 0x80) { + byte4 = readByte(input); + if (!status()) { return m_rwstatus; } + array.push_back(byte4); + if (byte4 >= 0x80) { + std::cerr << "Error: cannot handle large VLVs" << std::endl; + m_rwstatus = false; return m_rwstatus; + } else { + length = unpackVLV(byte1, byte2, byte3, byte4); + if (!m_rwstatus) { return m_rwstatus; } + } + } else { + length = unpackVLV(byte1, byte2, byte3); + if (!m_rwstatus) { return m_rwstatus; } + } + } else { + length = unpackVLV(byte1, byte2); + if (!m_rwstatus) { return m_rwstatus; } + } + } else { + length = byte1; + } + for (int j=0; j<(int)length; j++) { + byte = readByte(input); // meta type + if (!status()) { return m_rwstatus; } + array.push_back(byte); + } + } + break; + + // The 0xf0 and 0xf7 meta commands deal with system-exclusive + // messages. 0xf0 is used to either start a message or to store + // a complete message. The 0xf0 is part of the outgoing MIDI + // bytes. The 0xf7 message is used to send arbitrary bytes, + // typically the middle or ends of system exclusive messages. The + // 0xf7 byte at the start of the message is not part of the + // outgoing raw MIDI bytes, but is kept in the MidiFile message + // to indicate a raw MIDI byte message (typically a partial + // system exclusive message). + case 0xf7: // Raw bytes. 0xf7 is not part of the raw + // bytes, but are included to indicate + // that this is a raw byte message. + case 0xf0: // System Exclusive message + { // (complete, or start of message). + int length = (int)readVLValue(input); + for (int i=0; i 0x7f)) { + count++; + } + count++; + if (count >= 6) { + std::cerr << "VLV number is too large" << std::endl; + m_rwstatus = false; + return 0; + } + + ulong output = 0; + for (int i=0; i& outdata) { + uchar bytes[4] = {0}; + + if ((unsigned long)aValue >= (1 << 28)) { + std::cerr << "Error: number too large to convert to VLV" << std::endl; + aValue = 0x0FFFffff; + } + + bytes[0] = (uchar)(((ulong)aValue >> 21) & 0x7f); // most significant 7 bits + bytes[1] = (uchar)(((ulong)aValue >> 14) & 0x7f); + bytes[2] = (uchar)(((ulong)aValue >> 7) & 0x7f); + bytes[3] = (uchar)(((ulong)aValue) & 0x7f); // least significant 7 bits + + int start = 0; + while ((start<4) && (bytes[start] == 0)) start++; + + for (int i=start; i<3; i++) { + bytes[i] = bytes[i] | 0x80; + outdata.push_back(bytes[i]); + } + outdata.push_back(bytes[3]); +} + + + +////////////////////////////// +// +// MidiFile::clear_no_deallocate -- Similar to clear() but does not +// delete the Events in the lists. This is primarily used internally +// to the MidiFile class, so don't use unless you really know what you +// are doing (otherwise you will end up with memory leaks or +// segmentation faults). +// + +void MidiFile::clear_no_deallocate(void) { + for (int i=0; idetach(); + delete m_events[i]; + m_events[i] = NULL; + } + m_events.resize(1); + m_events[0] = new MidiEventList; + m_timemapvalid=0; + m_timemap.clear(); + // m_events.resize(0); // causes a memory leak [20150205 Jorden Thatcher] +} + + + +////////////////////////////// +// +// MidiFile::ticksearch -- for finding a tick entry in the time map. +// + +int MidiFile::ticksearch(const void* A, const void* B) { + _TickTime& a = *((_TickTime*)A); + _TickTime& b = *((_TickTime*)B); + + if (a.tick < b.tick) { + return -1; + } else if (a.tick > b.tick) { + return 1; + } + return 0; +} + + + +////////////////////////////// +// +// MidiFile::secondsearch -- for finding a second entry in the time map. +// + +int MidiFile::secondsearch(const void* A, const void* B) { + _TickTime& a = *((_TickTime*)A); + _TickTime& b = *((_TickTime*)B); + + if (a.seconds < b.seconds) { + return -1; + } else if (a.seconds > b.seconds) { + return 1; + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////// +// +// Static functions: +// + + +////////////////////////////// +// +// MidiFile::readLittleEndian4Bytes -- Read four bytes which are in +// little-endian order (smallest byte is first). Then flip +// the order of the bytes to create the return value. +// + +ulong MidiFile::readLittleEndian4Bytes(std::istream& input) { + uchar buffer[4] = {0}; + input.read((char*)buffer, 4); + if (input.eof()) { + std::cerr << "Error: unexpected end of file." << std::endl; + return 0; + } + return buffer[3] | (buffer[2] << 8) | (buffer[1] << 16) | (buffer[0] << 24); +} + + + +////////////////////////////// +// +// MidiFile::readLittleEndian2Bytes -- Read two bytes which are in +// little-endian order (smallest byte is first). Then flip +// the order of the bytes to create the return value. +// + +ushort MidiFile::readLittleEndian2Bytes(std::istream& input) { + uchar buffer[2] = {0}; + input.read((char*)buffer, 2); + if (input.eof()) { + std::cerr << "Error: unexpected end of file." << std::endl; + return 0; + } + return buffer[1] | (buffer[0] << 8); +} + + + +////////////////////////////// +// +// MidiFile::readByte -- Read one byte from input stream. Set +// fail status error if there was a problem (calling function +// has to check this status for an error after reading). +// + +uchar MidiFile::readByte(std::istream& input) { + uchar buffer[1] = {0}; + input.read((char*)buffer, 1); + if (input.eof()) { + std::cerr << "Error: unexpected end of file." << std::endl; + m_rwstatus = false; + return 0; + } + return buffer[0]; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianUShort -- +// + +std::ostream& MidiFile::writeLittleEndianUShort(std::ostream& out, ushort value) { + union { char bytes[2]; ushort us; } data; + data.us = value; + out << data.bytes[0]; + out << data.bytes[1]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianUShort -- +// + +std::ostream& MidiFile::writeBigEndianUShort(std::ostream& out, ushort value) { + union { char bytes[2]; ushort us; } data; + data.us = value; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianShort -- +// + +std::ostream& MidiFile::writeLittleEndianShort(std::ostream& out, short value) { + union { char bytes[2]; short s; } data; + data.s = value; + out << data.bytes[0]; + out << data.bytes[1]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianShort -- +// + +std::ostream& MidiFile::writeBigEndianShort(std::ostream& out, short value) { + union { char bytes[2]; short s; } data; + data.s = value; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianULong -- +// + +std::ostream& MidiFile::writeLittleEndianULong(std::ostream& out, ulong value) { + union { char bytes[4]; ulong ul; } data; + data.ul = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianULong -- +// + +std::ostream& MidiFile::writeBigEndianULong(std::ostream& out, ulong value) { + union { char bytes[4]; long ul; } data; + data.ul = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianLong -- +// + +std::ostream& MidiFile::writeLittleEndianLong(std::ostream& out, long value) { + union { char bytes[4]; long l; } data; + data.l = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianLong -- +// + +std::ostream& MidiFile::writeBigEndianLong(std::ostream& out, long value) { + union { char bytes[4]; long l; } data; + data.l = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; + +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianFloat -- +// + +std::ostream& MidiFile::writeBigEndianFloat(std::ostream& out, float value) { + union { char bytes[4]; float f; } data; + data.f = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianFloat -- +// + +std::ostream& MidiFile::writeLittleEndianFloat(std::ostream& out, float value) { + union { char bytes[4]; float f; } data; + data.f = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianDouble -- +// + +std::ostream& MidiFile::writeBigEndianDouble(std::ostream& out, double value) { + union { char bytes[8]; double d; } data; + data.d = value; + out << data.bytes[7]; + out << data.bytes[6]; + out << data.bytes[5]; + out << data.bytes[4]; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianDouble -- +// + +std::ostream& MidiFile::writeLittleEndianDouble(std::ostream& out, double value) { + union { char bytes[8]; double d; } data; + data.d = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + out << data.bytes[4]; + out << data.bytes[5]; + out << data.bytes[6]; + out << data.bytes[7]; + return out; +} + + + +//////////////////// +// +// MidiFile::getGMInstrumentName -- return the General MIDI instrument name +// for the given patch change index (in the range from 0 to 127). +// + +std::string MidiFile::getGMInstrumentName(int patchIndex) { + if (patchIndex < 0) { + return ""; + } + if (patchIndex > 127) { + return ""; + } + return GMinstrument[patchIndex]; +} + + + +////////////////////////////// +// +// MidiFile::base64Encode -- Encode a string as base64. +// + +std::string MidiFile::base64Encode(const std::string& input) { + std::string output; + output.reserve(((input.size()/3) + (input.size() % 3 > 0)) * 4); + int vala = 0; + int valb = -6; + for (uchar c : input) { + vala = (vala << 8) + c; + valb += 8; + while (valb >=0) { + output.push_back(MidiFile::encodeLookup[(vala >> valb) & 0x3F]); + valb -= 6; + } + } + if (valb > -6) { + output.push_back(MidiFile::encodeLookup[((vala << 8) >> (valb + 8)) & 0x3F]); + } + while (output.size() % 4) { + output.push_back(MidiFile::encodeLookup.back()); + } + return output; +} + + + +////////////////////////////// +// +// MidiFile::base64Decode -- Decode a base64 string. +// + +std::string MidiFile::base64Decode(const std::string& input) { + // vector decodeLookup(256,-1); + // for (int i=0; i<64; i++) decodeLookup[encodeLookup[i]] = i; + + std::string output; + int vala = 0; + int valb = -8; + for (uchar c : input) { + if (c == '=') { + break; + } else if (MidiFile::decodeLookup[c] == -1) { + // Ignore whitespace, for example. + continue; + } + vala = (vala << 6) + MidiFile::decodeLookup[c]; + valb += 6; + if (valb >= 0) { + output.push_back(char((vala >> valb) & 0xFF)); + valb -= 8; + } + } + return output; +} + + + +} // end namespace smf + +/////////////////////////////////////////////////////////////////////////// +// +// external functions +// + +////////////////////////////// +// +// operator<< -- for printing an ASCII version of the MIDI file +// + +std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile) { + aMidiFile.writeBinascWithComments(out); + return out; +} + + + diff --git a/src/3rdparty/midifile/MidiFile.h b/src/3rdparty/midifile/MidiFile.h new file mode 100644 index 0000000000..4972218f62 --- /dev/null +++ b/src/3rdparty/midifile/MidiFile.h @@ -0,0 +1,332 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Fri Nov 26 14:12:01 PST 1999 +// Last Modified: Mon Jan 18 20:54:04 PST 2021 Added readSmf(). +// Filename: midifile/include/MidiFile.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class that can read/write Standard MIDI files. +// MIDI data is stored by track in an array. +// + +#ifndef _MIDIFILE_H_INCLUDED +#define _MIDIFILE_H_INCLUDED + +#include "MidiEventList.h" + +#include +#include +#include +#include + + +namespace smf { + +enum { + TRACK_STATE_SPLIT = 0, // Tracks are separated into separate vector postions. + TRACK_STATE_JOINED = 1 // Tracks are merged into a single vector position, +}; // like a Type-0 MIDI file, but reversible. + +enum { + TIME_STATE_DELTA = 0, // MidiMessage::ticks are in delta time format (like MIDI file). + TIME_STATE_ABSOLUTE = 1 // MidiMessage::ticks are in absolute time format (0=start time). +}; + +class _TickTime { + public: + int tick; + double seconds; +}; + + +class MidiFile { + public: + MidiFile (void); + MidiFile (const std::string& filename); + MidiFile (std::istream& input); + MidiFile (const MidiFile& other); + MidiFile (MidiFile&& other); + + ~MidiFile (); + + MidiFile& operator= (const MidiFile& other); + MidiFile& operator= (MidiFile&& other); + + // Reading/writing functions: + + // Auto-detected SMF or ASCII-encoded SMF (decoded with Binasc class): + bool read (const std::string& filename); + bool read (std::istream& instream); + bool readBase64 (const std::string& base64data); + bool readBase64 (std::istream& instream); + + // Only allow Standard MIDI File input: + bool readSmf (const std::string& filename); + bool readSmf (std::istream& instream); + + bool write (const std::string& filename); + bool write (std::ostream& out); + bool writeBase64 (const std::string& out, int width = 0); + bool writeBase64 (std::ostream& out, int width = 0); + std::string getBase64 (int width = 0); + bool writeHex (const std::string& filename, int width = 25); + bool writeHex (std::ostream& out, int width = 25); + bool writeBinasc (const std::string& filename); + bool writeBinasc (std::ostream& out); + bool writeBinascWithComments (const std::string& filename); + bool writeBinascWithComments (std::ostream& out); + bool status (void) const; + + // track-related functions: + const MidiEventList& operator[] (int aTrack) const; + MidiEventList& operator[] (int aTrack); + int getTrackCount (void) const; + int getNumTracks (void) const; + int size (void) const; + void removeEmpties (void); + + // tick-related functions: + void makeDeltaTicks (void); + void deltaTicks (void); + void makeAbsoluteTicks (void); + void absoluteTicks (void); + int getTickState (void) const; + bool isDeltaTicks (void) const; + bool isAbsoluteTicks (void) const; + + // join/split track functionality: + void joinTracks (void); + void splitTracks (void); + void splitTracksByChannel (void); + int getTrackState (void) const; + int hasJoinedTracks (void) const; + int hasSplitTracks (void) const; + int getSplitTrack (int track, int index) const; + int getSplitTrack (int index) const; + + // track sorting funcionality: + void sortTrack (int track); + void sortTracks (void); + void markSequence (void); + void markSequence (int track, int sequence = 1); + void clearSequence (void); + void clearSequence (int track); + + // track manipulation functionality: + int addTrack (void); + int addTrack (int count); + int addTracks (int count); + void deleteTrack (int aTrack); + void mergeTracks (int aTrack1, int aTrack2); + int getTrackCountAsType1 (void); + + // ticks-per-quarter related functions: + void setMillisecondTicks (void); + int getTicksPerQuarterNote (void) const; + int getTPQ (void) const; + void setTicksPerQuarterNote (int ticks); + void setTPQ (int ticks); + + // physical-time analysis functions: + void doTimeAnalysis (void); + double getTimeInSeconds (int aTrack, int anIndex); + double getTimeInSeconds (int tickvalue); + double getAbsoluteTickTime (double starttime); + int getFileDurationInTicks (void); + double getFileDurationInQuarters (void); + double getFileDurationInSeconds (void); + + // note-analysis functions: + int linkNotePairs (void); + int linkEventPairs (void); + void clearLinks (void); + + // filename functions: + void setFilename (const std::string& aname); + const char* getFilename (void) const; + + // event functionality: + MidiEvent* addEvent (int aTrack, int aTick, + std::vector& midiData); + MidiEvent* addEvent (MidiEvent& mfevent); + MidiEvent* addEvent (int aTrack, MidiEvent& mfevent); + MidiEvent& getEvent (int aTrack, int anIndex); + const MidiEvent& getEvent (int aTrack, int anIndex) const; + int getEventCount (int aTrack) const; + int getNumEvents (int aTrack) const; + void allocateEvents (int track, int aSize); + void erase (void); + void clear (void); + void clear_no_deallocate (void); + + // MIDI message adding convenience functions: + MidiEvent* addNoteOn (int aTrack, int aTick, + int aChannel, int key, + int vel); + MidiEvent* addNoteOff (int aTrack, int aTick, + int aChannel, int key, + int vel); + MidiEvent* addNoteOff (int aTrack, int aTick, + int aChannel, int key); + MidiEvent* addController (int aTrack, int aTick, + int aChannel, int num, + int value); + MidiEvent* addPatchChange (int aTrack, int aTick, + int aChannel, int patchnum); + MidiEvent* addTimbre (int aTrack, int aTick, + int aChannel, int patchnum); + MidiEvent* addPitchBend (int aTrack, int aTick, + int aChannel, double amount); + + // RPN settings: + void setPitchBendRange (int aTrack, int aTick, + int aChannel, double range); + + // Controller message adding convenience functions: + MidiEvent* addSustain (int aTrack, int aTick, + int aChannel, int value); + MidiEvent* addSustainPedal (int aTrack, int aTick, + int aChannel, int value); + MidiEvent* addSustainOn (int aTrack, int aTick, + int aChannel); + MidiEvent* addSustainPedalOn (int aTrack, int aTick, + int aChannel); + MidiEvent* addSustainOff (int aTrack, int aTick, + int aChannel); + MidiEvent* addSustainPedalOff (int aTrack, int aTick, + int aChannel); + + // Meta-event adding convenience functions: + MidiEvent* addMetaEvent (int aTrack, int aTick, + int aType, + std::vector& metaData); + MidiEvent* addMetaEvent (int aTrack, int aTick, + int aType, + const std::string& metaData); + MidiEvent* addText (int aTrack, int aTick, + const std::string& text); + MidiEvent* addCopyright (int aTrack, int aTick, + const std::string& text); + MidiEvent* addTrackName (int aTrack, int aTick, + const std::string& name); + MidiEvent* addInstrumentName (int aTrack, int aTick, + const std::string& name); + MidiEvent* addLyric (int aTrack, int aTick, + const std::string& text); + MidiEvent* addMarker (int aTrack, int aTick, + const std::string& text); + MidiEvent* addCue (int aTrack, int aTick, + const std::string& text); + MidiEvent* addTempo (int aTrack, int aTick, + double aTempo); + MidiEvent* addKeySignature (int aTrack, int aTick, + int fifths, bool mode = 0); + MidiEvent* addTimeSignature (int aTrack, int aTick, + int top, int bottom, + int clocksPerClick = 24, + int num32dsPerQuarter = 8); + MidiEvent* addCompoundTimeSignature(int aTrack, int aTick, + int top, int bottom, + int clocksPerClick = 36, + int num32dsPerQuarter = 8); + + uchar readByte (std::istream& input); + + // static functions: + static ushort readLittleEndian2Bytes (std::istream& input); + static ulong readLittleEndian4Bytes (std::istream& input); + static std::ostream& writeLittleEndianUShort (std::ostream& out, + ushort value); + static std::ostream& writeBigEndianUShort (std::ostream& out, + ushort value); + static std::ostream& writeLittleEndianShort (std::ostream& out, + short value); + static std::ostream& writeBigEndianShort (std::ostream& out, + short value); + static std::ostream& writeLittleEndianULong (std::ostream& out, + ulong value); + static std::ostream& writeBigEndianULong (std::ostream& out, + ulong value); + static std::ostream& writeLittleEndianLong (std::ostream& out, + long value); + static std::ostream& writeBigEndianLong (std::ostream& out, + long value); + static std::ostream& writeLittleEndianFloat (std::ostream& out, + float value); + static std::ostream& writeBigEndianFloat (std::ostream& out, + float value); + static std::ostream& writeLittleEndianDouble (std::ostream& out, + double value); + static std::ostream& writeBigEndianDouble (std::ostream& out, + double value); + static std::string getGMInstrumentName (int patchIndex); + + protected: + // m_events == Lists of MidiEvents for each MIDI file track. + std::vector m_events; + + // m_ticksPerQuarterNote == A value for the MIDI file header + // which represents the number of ticks in a quarter note + // that are used as units for the delta times for MIDI events + // in MIDI file track data. + int m_ticksPerQuarterNote = 120; + + // m_theTrackState == state variable for whether the tracks + // are joined or split. + int m_theTrackState = TRACK_STATE_SPLIT; + + // m_theTimeState == state variable for whether the MidiEvent::tick + // variable contain absolute ticks since the start of the file's + // time, or delta ticks since the last MIDI event in the track. + int m_theTimeState = TIME_STATE_ABSOLUTE; + + // m_readFileName == the filename of the last file read into + // the object. + std::string m_readFileName; + + // m_timemapvalid == + bool m_timemapvalid = false; + + // m_timemap == + std::vector<_TickTime> m_timemap; + + // m_rwstatus == True if last read was successful, false if a problem. + bool m_rwstatus = true; + + // m_linkedEventQ == True if link analysis has been done. + bool m_linkedEventsQ = false; + + private: + int extractMidiData (std::istream& inputfile, + std::vector& array, + uchar& runningCommand); + ulong readVLValue (std::istream& inputfile); + ulong unpackVLV (uchar a = 0, uchar b = 0, + uchar c = 0, uchar d = 0, + uchar e = 0); + void writeVLValue (long aValue, + std::vector& data); + int makeVLV (uchar *buffer, int number); + static int ticksearch (const void* A, const void* B); + static int secondsearch (const void* A, const void* B); + void buildTimeMap (void); + double linearTickInterpolationAtSecond (double seconds); + double linearSecondInterpolationAtTick (int ticktime); + std::string base64Encode (const std::string &input); + std::string base64Decode (const std::string &input); + + static const std::string encodeLookup; + static const std::vector decodeLookup; + static const char *GMinstrument[128]; +}; + +} // end of namespace smf + +std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile); + +#endif /* _MIDIFILE_H_INCLUDED */ + + + diff --git a/src/3rdparty/midifile/MidiMessage.cpp b/src/3rdparty/midifile/MidiMessage.cpp new file mode 100644 index 0000000000..616cf9b05b --- /dev/null +++ b/src/3rdparty/midifile/MidiMessage.cpp @@ -0,0 +1,2381 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 20:49:21 PST 2015 +// Last Modified: Sun Apr 15 11:11:05 PDT 2018 Added event removal system. +// Filename: midifile/src/MidiMessage.cpp +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: Storage for bytes of a MIDI message for Standard +// MIDI Files. +// + +#include "MidiMessage.h" + +#include +#include +#include +#include +#include + + +namespace smf { + +////////////////////////////// +// +// MidiMessage::MidiMessage -- Constructor. +// + +MidiMessage::MidiMessage(void) : vector() { + // do nothing +} + + +MidiMessage::MidiMessage(int command) : vector(1, (uchar)command) { + // do nothing +} + + +MidiMessage::MidiMessage(int command, int p1) : vector(2) { + (*this)[0] = (uchar)command; + (*this)[1] = (uchar)p1; +} + + +MidiMessage::MidiMessage(int command, int p1, int p2) : vector(3) { + (*this)[0] = (uchar)command; + (*this)[1] = (uchar)p1; + (*this)[2] = (uchar)p2; +} + + +MidiMessage::MidiMessage(const MidiMessage& message) : vector() { + (*this) = message; +} + + +MidiMessage::MidiMessage(const std::vector& message) : vector() { + setMessage(message); +} + + +MidiMessage::MidiMessage(const std::vector& message) : vector() { + setMessage(message); +} + + +MidiMessage::MidiMessage(const std::vector& message) : vector() { + setMessage(message); +} + + + +////////////////////////////// +// +// MidiMessage::~MidiMessage -- Deconstructor. +// + +MidiMessage::~MidiMessage() { + resize(0); +} + + + +////////////////////////////// +// +// MidiMessage::operator= -- +// + +MidiMessage& MidiMessage::operator=(const MidiMessage& message) { + if (this == &message) { + return *this; + } + std::vector::operator=(static_cast &>(message)); + return *this; +} + + +MidiMessage& MidiMessage::operator=(const std::vector& bytes) { + if (this == &bytes) { + return *this; + } + setMessage(bytes); + return *this; +} + + +MidiMessage& MidiMessage::operator=(const std::vector& bytes) { + setMessage(bytes); + return *this; +} + + +MidiMessage& MidiMessage::operator=(const std::vector& bytes) { + setMessage(bytes); + return *this; +} + + + +////////////////////////////// +// +// MidiMessage::setSize -- Change the size of the message byte list. +// If the size is increased, then the new bytes are not initialized +// to any specific values. +// + +void MidiMessage::setSize(int asize) { + this->resize(asize); +} + + + +////////////////////////////// +// +// MidiMessage::getSize -- Return the size of the MIDI message bytes. +// + +int MidiMessage::getSize(void) const { + return (int)this->size(); +} + + + +////////////////////////////// +// +// MidiMessage::setSizeToCommand -- Set the number of parameters if the +// command byte is set in the range from 0x80 to 0xef. Any newly +// added parameter bytes will be set to 0. Commands in the range +// of 0xF) should not use this function, and they will ignore +// modification by this command. +// + +int MidiMessage::setSizeToCommand(void) { + int osize = (int)this->size(); + if (osize < 1) { + return 0; + } + int command = getCommandNibble(); + if (command < 0) { + return 0; + } + int bytecount = 1; + switch (command) { + case 0x80: bytecount = 2; break; // Note Off + case 0x90: bytecount = 2; break; // Note On + case 0xA0: bytecount = 2; break; // Aftertouch + case 0xB0: bytecount = 2; break; // Continuous Controller + case 0xC0: bytecount = 1; break; // Patch Change + case 0xD0: bytecount = 1; break; // Channel Pressure + case 0xE0: bytecount = 2; break; // Pitch Bend + case 0xF0: + default: + return (int)size(); + } + if (bytecount + 1 < osize) { + resize(bytecount+1); + for (int i=osize; i& chars = message; + if (message.size() != 3) { + return false; + } else if ((chars[0] & 0xf0) == 0x80) { + return true; + } else if (((chars[0] & 0xf0) == 0x90) && (chars[2] == 0x00)) { + return true; + } else { + return false; + } +} + + + +////////////////////////////// +// +// MidiMessage::isNoteOn -- Returns true if the command byte is in the 0x90 +// range and the velocity is non-zero +// + +bool MidiMessage::isNoteOn(void) const { + if (size() != 3) { + return false; + } else if (((*this)[0] & 0xf0) != 0x90) { + return false; + } else if ((*this)[2] == 0) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isNote -- Returns true if either a note-on or a note-off +// message. +// + +bool MidiMessage::isNote(void) const { + return isNoteOn() || isNoteOff(); +} + + + +////////////////////////////// +// +// MidiMessage::isAftertouch -- Returns true if the command byte is in the 0xA0 +// range. +// + +bool MidiMessage::isAftertouch(void) const { + if (size() != 3) { + return false; + } else if (((*this)[0] & 0xf0) != 0xA0) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isController -- Returns true if the command byte is in the 0xB0 +// range and there are two additional data bytes. +// + +bool MidiMessage::isController(void) const { + if (size() != 3) { + return false; + } else if (((*this)[0] & 0xf0) != 0xB0) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isSustain -- Returns true if the MidiMessages is a sustain pedal +// control event. Controller 64 is the sustain pedal for general MIDI. +// + +bool MidiMessage::isSustain(void) const { + if (!isController()) { + return false; + } + if (getP1() == 64) { + return true; + } else { + return false; + } +} + + + +////////////////////////////// +// +// MidiMessage::isSustainOn -- Returns true if a sustain-pedal-on control message. +// Sustain-on is a value in the range from 64-127 for controller 64. +// + +bool MidiMessage::isSustainOn(void) const { + if (!isSustain()) { + return false; + } + if (getP2() >= 64) { + return true; + } else { + return false; + } +} + + + +////////////////////////////// +// +// MidiMessage::isSustainOff -- Returns true if a sustain-pedal-off control message. +// Sustain-off is a value in the range from 0-63 for controller 64. +// + +bool MidiMessage::isSustainOff(void) const { + if (!isSustain()) { + return false; + } + if (getP2() < 64) { + return true; + } else { + return false; + } +} + + + +////////////////////////////// +// +// MidiMessage::isSoft -- Returns true if the MidiMessages is a soft pedal +// control event. Controller 67 is the sustain pedal for general MIDI. +// + +bool MidiMessage::isSoft(void) const { + if (!isController()) { + return false; + } + if (getP1() == 67) { + return true; + } else { + return false; + } +} + + + +////////////////////////////// +// +// MidiMessage::isSoftOn -- Returns true if a sustain-pedal-on control message. +// Soft-on is a value in the range from 64-127 for controller 67. +// + +bool MidiMessage::isSoftOn(void) const { + if (!isSoft()) { + return false; + } + if (getP2() >= 64) { + return true; + } else { + return false; + } +} + + + +////////////////////////////// +// +// MidiMessage::isSoftOff -- Returns true if a sustain-pedal-off control message. +// Soft-off is a value in the range from 0-63 for controller 67. +// + +bool MidiMessage::isSoftOff(void) const { + if (!isSoft()) { + return false; + } + if (getP2() < 64) { + return true; + } else { + return false; + } +} + + + +////////////////////////////// +// +// MidiMessage::isTimbre -- Returns true of a patch change message +// (command nibble 0xc0). +// + +bool MidiMessage::isTimbre(void) const { + if (((*this)[0] & 0xf0) != 0xc0) { + return false; + } else if (size() != 2) { + return false; + } else { + return true; + } +} + + +bool MidiMessage::isPatchChange(void) const { + return isTimbre(); +} + + + +////////////////////////////// +// +// MidiMessage::isPressure -- Returns true of a channel pressure message +// (command nibble 0xd0). +// + +bool MidiMessage::isPressure(void) const { + if (((*this)[0] & 0xf0) != 0xd0) { + return false; + } else if (size() != 2) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isPitchbend -- Returns true of a pitch-bend message +// (command nibble 0xe0). +// + +bool MidiMessage::isPitchbend(void) const { + if (((*this)[0] & 0xf0) != 0xe0) { + return false; + } else if (size() != 3) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isEmpty -- Returns true if size of data array is zero. +// + +bool MidiMessage::isEmpty(void) const { + return empty(); +} + + + +/////////////////////////////// +// +// MidiMessage::getMetaType -- returns the meta-message type for the +// MidiMessage. If the message is not a meta message, then returns +// -1. +// + +int MidiMessage::getMetaType(void) const { + if (!isMetaMessage()) { + return -1; + } else { + return (int)(*this)[1]; + } +} + + + +////////////////////////////// +// +// MidiMessage::isText -- Returns true if message is a meta +// message describing some text (meta message type 0x01). +// + +bool MidiMessage::isText(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x01) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isCopyright -- Returns true if message is a meta +// message describing a copyright notice (meta message type 0x02). +// Copyright messages should be at absolute tick position 0 +// (and be the first event in the track chunk as well), but this +// function does not check for those requirements. +// + +bool MidiMessage::isCopyright(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x02) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isTrackName -- Returns true if message is a meta +// message describing a track name (meta message type 0x03). +// + +bool MidiMessage::isTrackName(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x03) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isInstrumentName -- Returns true if message is a +// meta message describing an instrument name (for the track) +// (meta message type 0x04). +// + +bool MidiMessage::isInstrumentName(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x04) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isLyricText -- Returns true if message is a meta message +// describing some lyric text (for karaoke MIDI files) +// (meta message type 0x05). +// + +bool MidiMessage::isLyricText(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x05) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isMarkerText -- Returns true if message is a meta message +// describing a marker text (meta message type 0x06). +// + +bool MidiMessage::isMarkerText(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x06) { + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isTempo -- Returns true if message is a meta message +// describing tempo (meta message type 0x51). +// + +bool MidiMessage::isTempo(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x51) { + return false; + } else if (size() != 6) { + // Meta tempo message can only be 6 bytes long. + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isTimeSignature -- Returns true if message is +// a meta message describing a time signature (meta message +// type 0x58). +// + +bool MidiMessage::isTimeSignature(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x58) { + return false; + } else if (size() != 7) { + // Meta time signature message can only be 7 bytes long: + // FF 58 <32nds> + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isKeySignature -- Returns true if message is +// a meta message describing a key signature (meta message +// type 0x59). +// + +bool MidiMessage::isKeySignature(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x59) { + return false; + } else if (size() != 5) { + // Meta key signature message can only be 5 bytes long: + // FF 59 + return false; + } else { + return true; + } +} + + +////////////////////////////// +// +// MidiMessage::isEndOfTrack -- Returns true if message is a meta message +// for end-of-track (meta message type 0x2f). +// + +bool MidiMessage::isEndOfTrack(void) const { + return getMetaType() == 0x2f ? 1 : 0; +} + + + +////////////////////////////// +// +// MidiMessage::getP0 -- Return index 1 byte, or -1 if it doesn't exist. +// + +int MidiMessage::getP0(void) const { + return size() < 1 ? -1 : (*this)[0]; +} + + + +////////////////////////////// +// +// MidiMessage::getP1 -- Return index 1 byte, or -1 if it doesn't exist. +// + +int MidiMessage::getP1(void) const { + return size() < 2 ? -1 : (*this)[1]; +} + + + +////////////////////////////// +// +// MidiMessage::getP2 -- Return index 2 byte, or -1 if it doesn't exist. +// + +int MidiMessage::getP2(void) const { + return size() < 3 ? -1 : (*this)[2]; +} + + + +////////////////////////////// +// +// MidiMessage::getP3 -- Return index 3 byte, or -1 if it doesn't exist. +// + +int MidiMessage::getP3(void) const { + return size() < 4 ? -1 : (*this)[3]; +} + + + +////////////////////////////// +// +// MidiMessage::getKeyNumber -- Return the key number (such as 60 for +// middle C). If the message does not have a note parameter, then +// return -1; if the key is invalid (above 127 in value), then +// limit to the range 0 to 127. +// + +int MidiMessage::getKeyNumber(void) const { + if (isNote() || isAftertouch()) { + int output = getP1(); + if (output < 0) { + return output; + } else { + return 0xff & output; + } + } else { + return -1; + } +} + + + +////////////////////////////// +// +// MidiMessage::getVelocity -- Return the key velocity. If the message +// is not a note-on or a note-off, then return -1. If the value is +// out of the range 0-127, then chop off the high-bits. +// + +int MidiMessage::getVelocity(void) const { + if (isNote()) { + int output = getP2(); + if (output < 0) { + return output; + } else { + return 0xff & output; + } + } else { + return -1; + } +} + + + +////////////////////////////// +// +// MidiMessage::getControllerNumber -- Return the controller number (such as 1 +// for modulation wheel). If the message does not have a controller number +// parameter, then return -1. If the controller number is invalid (above 127 +// in value), then limit the range to to 0-127. +// + +int MidiMessage::getControllerNumber(void) const { + if (isController()) { + int output = getP1(); + if (output < 0) { + // -1 means no P1, although isController() is false in such a case. + return output; + } else { + return 0x7f & output; + } + } else { + return -1; + } +} + + + +////////////////////////////// +// +// MidiMessage::getControllerValue -- Return the controller value. If the +// message is not a control change message, then return -1. If the value is +// out of the range 0-127, then chop off the high-bits. +// + +int MidiMessage::getControllerValue(void) const { + if (isController()) { + int output = getP2(); + if (output < 0) { + // -1 means no P2, although isController() is false in such a case. + return output; + } else { + return 0x7f & output; + } + } else { + return -1; + } +} + + + +////////////////////////////// +// +// MidiMessage::setP0 -- Set the command byte. +// If the MidiMessage is too short, add extra spaces to +// allow for P0. The value should be in the range from +// 128 to 255, but this function will not babysit you. +// + +void MidiMessage::setP0(int value) { + if (getSize() < 1) { + resize(1); + } + (*this)[0] = static_cast(value); +} + + + +////////////////////////////// +// +// MidiMessage::setP1 -- Set the first parameter value. +// If the MidiMessage is too short, add extra spaces to +// allow for P1. The command byte will be undefined if +// it was added. The value should be in the range from +// 0 to 127, but this function will not babysit you. +// + +void MidiMessage::setP1(int value) { + if (getSize() < 2) { + resize(2); + } + (*this)[1] = static_cast(value); +} + + + +////////////////////////////// +// +// MidiMessage::setP2 -- Set the second paramater value. +// If the MidiMessage is too short, add extra spaces +// to allow for P2. The command byte and/or the P1 value +// will be undefined if extra space needs to be added and +// those slots are created. The value should be in the range +// from 0 to 127, but this function will not babysit you. +// + +void MidiMessage::setP2(int value) { + if (getSize() < 3) { + resize(3); + } + (*this)[2] = static_cast(value); +} + + + +////////////////////////////// +// +// MidiMessage::setP3 -- Set the third paramater value. +// If the MidiMessage is too short, add extra spaces +// to allow for P3. The command byte and/or the P1/P2 values +// will be undefined if extra space needs to be added and +// those slots are created. The value should be in the range +// from 0 to 127, but this function will not babysit you. +// + +void MidiMessage::setP3(int value) { + if (getSize() < 4) { + resize(4); + } + (*this)[3] = static_cast(value); +} + + + +////////////////////////////// +// +// MidiMessage::setKeyNumber -- Set the note on/off key number (or +// aftertouch key). Ignore if not note or aftertouch message. +// Limits the input value to the range from 0 to 127. +// + +void MidiMessage::setKeyNumber(int value) { + if (isNote() || isAftertouch()) { + setP1(value & 0xff); + } else { + // don't do anything since this is not a note-related message. + } +} + + + +////////////////////////////// +// +// MidiMessage::setVelocity -- Set the note on/off velocity; ignore +// if note a note message. Limits the input value to the range +// from 0 to 127. +// + +void MidiMessage::setVelocity(int value) { + if (isNote()) { + setP2(value & 0xff); + } else { + // don't do anything since this is not a note-related message. + } +} + + + +////////////////////////////// +// +// MidiMessage::getCommandNibble -- Returns the top 4 bits of the (*this)[0] +// entry, or -1 if there is not (*this)[0]. +// + +int MidiMessage::getCommandNibble(void) const { + if (size() < 1) { + return -1; + } else { + return (*this)[0] & 0xf0; + } +} + + + +////////////////////////////// +// +// MidiMessage::getCommandByte -- Return the command byte or -1 if not +// allocated. +// + +int MidiMessage::getCommandByte(void) const { + if (size() < 1) { + return -1; + } else { + return (*this)[0]; + } +} + + + +////////////////////////////// +// +// MidiMessage::getChannelNibble -- Returns the bottom 4 bites of the +// (*this)[0] entry, or -1 if there is not (*this)[0]. Should be refined +// to return -1 if the top nibble is 0xf0, since those commands are +// not channel specific. +// + +int MidiMessage::getChannelNibble(void) const { + if (size() < 1) { + return -1; + } else { + return (*this)[0] & 0x0f; + } +} + + +int MidiMessage::getChannel(void) const { + return getChannelNibble(); +} + + + +////////////////////////////// +// +// MidiMessage::setCommandByte -- +// + +void MidiMessage::setCommandByte(int value) { + if (size() < 1) { + resize(1); + } else { + (*this)[0] = (uchar)(value & 0xff); + } +} + +void MidiMessage::setCommand(int value) { + setCommandByte(value); +} + + + +////////////////////////////// +// +// MidiMessage::setCommand -- Set the command byte and parameter bytes +// for a MidiMessage. The size of the message will be adjusted to +// the number of input parameters. +// + +void MidiMessage::setCommand(int value, int p1) { + this->resize(2); + (*this)[0] = (uchar)value; + (*this)[1] = (uchar)p1; +} + + +void MidiMessage::setCommand(int value, int p1, int p2) { + this->resize(3); + (*this)[0] = (uchar)value; + (*this)[1] = (uchar)p1; + (*this)[2] = (uchar)p2; +} + + + +////////////////////////////// +// +// MidiMessage::setCommandNibble -- +// + +void MidiMessage::setCommandNibble(int value) { + if (this->size() < 1) { + this->resize(1); + } + if (value <= 0x0f) { + (*this)[0] = ((*this)[0] & 0x0f) | ((uchar)((value << 4) & 0xf0)); + } else { + (*this)[0] = ((*this)[0] & 0x0f) | ((uchar)(value & 0xf0)); + } +} + + + + +////////////////////////////// +// +// MidiMessage::setChannelNibble -- +// + +void MidiMessage::setChannelNibble(int value) { + if (this->size() < 1) { + this->resize(1); + } + (*this)[0] = ((*this)[0] & 0xf0) | ((uchar)(value & 0x0f)); +} + + +void MidiMessage::setChannel(int value) { + setChannelNibble(value); +} + + + +////////////////////////////// +// +// MidiMessage::setParameters -- Set the second and optionally the +// third MIDI byte of a MIDI message. The command byte will not +// be altered, and will be set to 0 if it currently does not exist. +// + +void MidiMessage::setParameters(int p1) { + int oldsize = (int)size(); + resize(2); + (*this)[1] = (uchar)p1; + if (oldsize < 1) { + (*this)[0] = 0; + } +} + + +void MidiMessage::setParameters(int p1, int p2) { + int oldsize = (int)size(); + resize(3); + (*this)[1] = (uchar)p1; + (*this)[2] = (uchar)p2; + if (oldsize < 1) { + (*this)[0] = 0; + } +} + + +////////////////////////////// +// +// MidiMessage::setMessage -- Set the contents of MIDI bytes to the +// input list of bytes. +// + +void MidiMessage::setMessage(const std::vector& message) { + this->resize(message.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = message[i]; + } +} + + +void MidiMessage::setMessage(const std::vector& message) { + resize(message.size()); + for (int i=0; i<(int)size(); i++) { + (*this)[i] = (uchar)message[i]; + } +} + + +void MidiMessage::setMessage(const std::vector& message) { + resize(message.size()); + for (int i=0; i<(int)size(); i++) { + (*this)[i] = (uchar)message[i]; + } +} + + + +////////////////////////////// +// +// MidiMessage::setSpelling -- Encode a MidiPlus accidental state for a note. +// For example, if a note's key number is 60, the enharmonic pitch name +// could be any of these possibilities: +// C, B-sharp, D-double-flat +// MIDI note 60 is ambiguous as to which of these names are intended, +// so MIDIPlus allows these mappings to be preserved for later recovery. +// See Chapter 5 (pp. 99-104) of Beyond MIDI (1997). +// +// The first parameter is the diatonic pitch number (or pitch class +// if the octave is set to 0): +// octave * 7 + 0 = C pitches +// octave * 7 + 1 = D pitches +// octave * 7 + 2 = E pitches +// octave * 7 + 3 = F pitches +// octave * 7 + 4 = G pitches +// octave * 7 + 5 = A pitches +// octave * 7 + 6 = B pitches +// +// The second parameter is the semitone alteration (accidental). +// 0 = natural state, 1 = sharp, 2 = double sharp, -1 = flat, +// -2 = double flat. +// +// Only note-on messages can be processed (other messages will be +// silently ignored). +// + +void MidiMessage::setSpelling(int base7, int accidental) { + if (!isNoteOn()) { + return; + } + // The bottom two bits of the attack velocity are used for the + // spelling, so need to make sure the velocity will not accidentally + // be set to zero (and make the note-on a note-off). + if (getVelocity() < 4) { + setVelocity(4); + } + int dpc = base7 % 7; + uchar spelling = 0; + + // Table 5.1, page 101 in Beyond MIDI (1997) + // http://beyondmidi.ccarh.org/beyondmidi-600dpi.pdf + switch (dpc) { + + case 0: + switch (accidental) { + case -2: spelling = 1; break; // Cbb + case -1: spelling = 1; break; // Cb + case 0: spelling = 2; break; // C + case +1: spelling = 2; break; // C# + case +2: spelling = 3; break; // C## + } + break; + + case 1: + switch (accidental) { + case -2: spelling = 1; break; // Dbb + case -1: spelling = 1; break; // Db + case 0: spelling = 2; break; // D + case +1: spelling = 3; break; // D# + case +2: spelling = 3; break; // D## + } + break; + + case 2: + switch (accidental) { + case -2: spelling = 1; break; // Ebb + case -1: spelling = 2; break; // Eb + case 0: spelling = 2; break; // E + case +1: spelling = 3; break; // E# + case +2: spelling = 3; break; // E## + } + break; + + case 3: + switch (accidental) { + case -2: spelling = 1; break; // Fbb + case -1: spelling = 1; break; // Fb + case 0: spelling = 2; break; // F + case +1: spelling = 2; break; // F# + case +2: spelling = 3; break; // F## + case +3: spelling = 3; break; // F### + } + break; + + case 4: + switch (accidental) { + case -2: spelling = 1; break; // Gbb + case -1: spelling = 1; break; // Gb + case 0: spelling = 2; break; // G + case +1: spelling = 2; break; // G# + case +2: spelling = 3; break; // G## + } + break; + + case 5: + switch (accidental) { + case -2: spelling = 1; break; // Abb + case -1: spelling = 1; break; // Ab + case 0: spelling = 2; break; // A + case +1: spelling = 3; break; // A# + case +2: spelling = 3; break; // A## + } + break; + + case 6: + switch (accidental) { + case -2: spelling = 1; break; // Bbb + case -1: spelling = 2; break; // Bb + case 0: spelling = 2; break; // B + case +1: spelling = 3; break; // B# + case +2: spelling = 3; break; // B## + } + break; + + } + + uchar vel = static_cast(getVelocity()); + // suppress any previous content in the first two bits: + vel = vel & 0xFC; + // insert the spelling code: + vel = vel | spelling; + setVelocity(vel); +} + + + +////////////////////////////// +// +// MidiMessage::getSpelling -- Return the diatonic pitch class and accidental +// for a note-on's key number. The MIDI file must be encoded with MIDIPlus +// pitch spelling codes for this function to return valid data; otherwise, +// it will return a neutral fixed spelling for each MIDI key. +// +// The first parameter will be filled in with the base-7 diatonic pitch: +// pc + octave * 7 +// where pc is the numbers 0 through 6 representing the pitch classes +// C through B, the octave is MIDI octave (not the scientific pitch +// octave which is one less than the MIDI octave, such as C4 = middle C). +// The second number is the accidental for the base-7 pitch. +// + +void MidiMessage::getSpelling(int& base7, int& accidental) { + if (!isNoteOn()) { + return; + } + base7 = -123456; + accidental = 123456; + int base12 = getKeyNumber(); + int octave = base12 / 12; + int base12pc = base12 - octave * 12; + int base7pc = 0; + int spelling = 0x03 & getVelocity(); + + // Table 5.1, page 101 in Beyond MIDI (1997) + // http://beyondmidi.ccarh.org/beyondmidi-600dpi.pdf + switch (base12pc) { + + case 0: + switch (spelling) { + case 1: base7pc = 1; accidental = -2; break; // Dbb + case 0: case 2: base7pc = 0; accidental = 0; break; // C + case 3: base7pc = 6; accidental = +1; octave--; break; // B# + } + break; + + case 1: + switch (spelling) { + case 1: base7pc = 1; accidental = -1; break; // Db + case 0: case 2: base7pc = 0; accidental = +1; break; // C# + case 3: base7pc = 6; accidental = +2; octave--; break; // B## + } + break; + + case 2: + switch (spelling) { + case 1: base7pc = 2; accidental = -2; break; // Ebb + case 0: case 2: base7pc = 1; accidental = 0; break; // D + case 3: base7pc = 0; accidental = +2; break; // C## + } + break; + + case 3: + switch (spelling) { + case 1: base7pc = 3; accidental = -2; break; // Fbb + case 0: case 2: base7pc = 2; accidental = -1; break; // Eb + case 3: base7pc = 1; accidental = +1; break; // D# + } + break; + + case 4: + switch (spelling) { + case 1: base7pc = 3; accidental = -1; break; // Fb + case 0: case 2: base7pc = 2; accidental = 0; break; // E + case 3: base7pc = 1; accidental = +2; break; // D## + } + break; + + case 5: + switch (spelling) { + case 1: base7pc = 4; accidental = -2; break; // Gbb + case 0: case 2: base7pc = 3; accidental = 0; break; // F + case 3: base7pc = 2; accidental = +1; break; // E# + } + break; + + case 6: + switch (spelling) { + case 1: base7pc = 4; accidental = -1; break; // Gb + case 0: case 2: base7pc = 3; accidental = +1; break; // F# + case 3: base7pc = 2; accidental = +2; break; // E## + } + break; + + case 7: + switch (spelling) { + case 1: base7pc = 5; accidental = -2; break; // Abb + case 0: case 2: base7pc = 4; accidental = 0; break; // G + case 3: base7pc = 3; accidental = +2; break; // F## + } + break; + + case 8: + switch (spelling) { + case 1: base7pc = 5; accidental = -1; break; // Ab + case 0: case 2: base7pc = 4; accidental = +1; break; // G# + case 3: base7pc = 3; accidental = +3; break; // F### + } + break; + + case 9: + switch (spelling) { + case 1: base7pc = 6; accidental = -2; break; // Bbb + case 0: case 2: base7pc = 5; accidental = 0; break; // A + case 3: base7pc = 4; accidental = +2; break; // G## + } + break; + + case 10: + switch (spelling) { + case 1: base7pc = 0; accidental = -2; octave++; break; // Cbb + case 0: case 2: base7pc = 6; accidental = -1; break; // Bb + case 3: base7pc = 5; accidental = +1; break; // A# + } + break; + + case 11: + switch (spelling) { + case 1: base7pc = 0; accidental = -1; octave++; break; // Cb + case 0: case 2: base7pc = 6; accidental = 0; break; // B + case 3: base7pc = 5; accidental = +2; break; // A## + } + break; + + } + + base7 = base7pc + 7 * octave; +} + + + +////////////////////////////// +// +// MidiMessage::getMetaContent -- Returns the bytes of the meta +// message after the length (which is a variable-length-value). +// + +std::string MidiMessage::getMetaContent(void) const { + std::string output; + if (!isMetaMessage()) { + return output; + } + int start = 3; + if (operator[](2) > 0x7f) { + start++; + if (operator[](3) > 0x7f) { + start++; + if (operator[](4) > 0x7f) { + start++; + if (operator[](5) > 0x7f) { + start++; + // maximum of 5 bytes in VLV, so last must be < 0x80 + } + } + } + } + output.reserve(this->size()); + for (int i=start; i<(int)this->size(); i++) { + output.push_back(operator[](i)); + } + return output; +} + + + +////////////////////////////// +// +// MidiMessage::setMetaContent - Set the content of a meta-message. This +// function handles the size of the message starting at byte 3 in the +// message, and it does not alter the meta message type. The message +// must be a meta-message before calling this function and be assigned +// a meta-message type. +// + +void MidiMessage::setMetaContent(const std::string& content) { + if (this->size() < 2) { + // invalid message, so ignore request + return; + } + if (operator[](0) != 0xFF) { + // not a meta message, so ignore request + return; + } + this->resize(2); + + // add the size of the meta message data (VLV) + int dsize = (int)content.size(); + std::vector vlv = intToVlv(dsize); + for (uchar item : vlv) { + this->push_back(item); + } + std::copy(content.begin(), content.end(), std::back_inserter(*this)); +} + + + +////////////////////////////// +// +// MidiMessage::setMetaTempo -- Input tempo is in quarter notes per minute +// (meta message #0x51). +// + +void MidiMessage::setMetaTempo(double tempo) { + int microseconds = (int)(60.0 / tempo * 1000000.0 + 0.5); + setTempoMicroseconds(microseconds); +} + + + +////////////////////////////// +// +// MidiMessage::setTempo -- Alias for MidiMessage::setMetaTempo(). +// + +void MidiMessage::setTempo(double tempo) { + setMetaTempo(tempo); +} + + + +////////////////////////////// +// +// MidiMessage::setTempoMicroseconds -- Set the tempo in terms +// of microseconds per quarter note. +// + +void MidiMessage::setTempoMicroseconds(int microseconds) { + resize(6); + (*this)[0] = 0xff; + (*this)[1] = 0x51; + (*this)[2] = 3; + (*this)[3] = (microseconds >> 16) & 0xff; + (*this)[4] = (microseconds >> 8) & 0xff; + (*this)[5] = (microseconds >> 0) & 0xff; +} + + + +////////////////////////////// +// +// MidiMessage::makeKeySignature -- create a key signature meta message +// (meta #0x59). +// +// Default values: +// fifths == 0 (C) +// mode == 0 (major) +// +// Key signature of b minor would be: +// fifths = 2 +// mode = 1 +// + +void MidiMessage::makeKeySignature(int fifths, bool mode) { + resize(5); + (*this)[0] = 0xff; + (*this)[1] = 0x59; + (*this)[2] = 0x02; + (*this)[3] = 0xff & fifths; + (*this)[4] = 0xff & (int)mode; +} + + + +////////////////////////////// +// +// MidiMessage::makeTimeSignature -- create a time signature meta message +// (meta #0x58). The "bottom" parameter should be a power of two; +// otherwise, it will be forced to be the next highest power of two, +// as MIDI time signatures must have a power of two in the denominator. +// +// Default values: +// clocksPerClick == 24 (quarter note) +// num32ndsPerQuarter == 8 (8 32nds per quarter note) +// +// Time signature of 4/4 would be: +// top = 4 +// bottom = 4 (converted to 2 in the MIDI file for 2nd power of 2). +// clocksPerClick = 24 (2 eighth notes based on num32ndsPerQuarter) +// num32ndsPerQuarter = 8 +// +// Time signature of 6/8 would be: +// top = 6 +// bottom = 8 (converted to 3 in the MIDI file for 3rd power of 2). +// clocksPerClick = 36 (3 eighth notes based on num32ndsPerQuarter) +// num32ndsPerQuarter = 8 +// + +void MidiMessage::makeTimeSignature(int top, int bottom, int clocksPerClick, + int num32ndsPerQuarter) { + int base2 = 0; + while (bottom >>= 1) base2++; + resize(7); + (*this)[0] = 0xff; + (*this)[1] = 0x58; + (*this)[2] = 4; + (*this)[3] = 0xff & top; + (*this)[4] = 0xff & base2; + (*this)[5] = 0xff & clocksPerClick; + (*this)[6] = 0xff & num32ndsPerQuarter; +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// make functions to create various MIDI message -- +// + + +////////////////////////////// +// +// MidiMessage::makeNoteOn -- create a note-on message. +// +// default value: channel = 0 +// +// Note: The channel parameter used to be last, but makes more sense to +// have it first... +// + +void MidiMessage::makeNoteOn(int channel, int key, int velocity) { + resize(3); + (*this)[0] = 0x90 | (0x0f & channel); + (*this)[1] = key & 0x7f; + (*this)[2] = velocity & 0x7f; +} + + + +////////////////////////////// +// +// MidiMessage::makeNoteOff -- create a note-off message. If no +// parameters are given, the current contents is presumed to be a +// note-on message, which will be converted into a note-off message. +// +// default value: channel = 0 +// +// Note: The channel parameter used to be last, but makes more sense to +// have it first... +// + + +void MidiMessage::makeNoteOff(int channel, int key, int velocity) { + resize(3); + (*this)[0] = 0x80 | (0x0f & channel); + (*this)[1] = key & 0x7f; + (*this)[2] = velocity & 0x7f; +} + + +void MidiMessage::makeNoteOff(int channel, int key) { + resize(3); + (*this)[0] = 0x90 | (0x0f & channel); + (*this)[1] = key & 0x7f; + (*this)[2] = 0x00; +} + +// +// MidiMessage::makeNoteOff(void) -- create a 0x90 note message with +// The key and velocity set to 0. +// + +void MidiMessage::makeNoteOff(void) { + if (!isNoteOn()) { + resize(3); + (*this)[0] = 0x90; + (*this)[1] = 0; + (*this)[2] = 0; + } else { + (*this)[2] = 0; + } +} + + + +///////////////////////////// +// +// MidiMessage::makePatchChange -- Create a patch-change message. +// + +void MidiMessage::makePatchChange(int channel, int patchnum) { + resize(0); + push_back(0xc0 | (0x0f & channel)); + push_back(0x7f & patchnum); +} + +// +// MidiMessage::makeTimbre -- alias for MidiMessage::makePatchChange(). +// + +void MidiMessage::makeTimbre(int channel, int patchnum) { + makePatchChange(channel, patchnum); +} + + +///////////////////////////// +// +// MidiMessage::makeController -- Create a controller message. +// + +void MidiMessage::makeController(int channel, int num, int value) { + resize(0); + push_back(0xb0 | (0x0f & channel)); + push_back(0x7f & num); + push_back(0x7f & value); +} + + + +///////////////////////////// +// +// MidiMessage::makePitchBend -- Create a pitch-bend message. lsb is +// least-significant 7 bits of the 14-bit range, and msb is the +// most-significant 7 bits of the 14-bit range. The range depth +// is determined by a setting in the synthesizer. Typically it is +// +/- two semitones by default. See MidiFile::setPitchBendRange() +// to change the default (or change to the typical default). +// + +void MidiMessage::makePitchBend(int channel, int lsb, int msb) { + resize(0); + push_back(0xe0 | (0x0e & channel)); + push_back(0x7f & lsb); + push_back(0x7f & msb); +} + +// +// value is a 14-bit number, where 0 is the lowest pitch of the range, and +// 2^15-1 is the highest pitch of the range. +// + +void MidiMessage::makePitchBend(int channel, int value) { + resize(0); + int lsb = value & 0x7f; + int msb = (value >> 7) & 0x7f; + push_back(0xe0 | (0x7f & channel)); + push_back(lsb); + push_back(msb); +} + +// +// Input value is a number between -1.0 and +1.0. +// + +void MidiMessage::makePitchBendDouble(int channel, double value) { + // value is in the range from -1 for minimum and 2^18 - 1 for the maximum + resize(0); + double dvalue = (value + 1.0) * (pow(2.0, 15.0)); + if (dvalue < 0.0) { + dvalue = 0.0; + } + if (dvalue > pow(2.0, 15.0) - 1.0) { + dvalue = pow(2.0, 15.0) - 1.0; + } + ulong uivalue = (ulong)dvalue; + uchar lsb = uivalue & 0x7f; + uchar msb = (uivalue >> 7) & 0x7f; + push_back(0xe0 | (0x7f & channel)); + push_back(lsb); + push_back(msb); +} + + + +///////////////////////////// +// +// MidiMessage::makeSustain -- Create a sustain pedal message. +// Value in 0-63 range is a sustain off. Value in the +// 64-127 value is a sustain on. +// + +void MidiMessage::makeSustain(int channel, int value) { + makeController(channel, 64, value); +} + +// +// MidiMessage::makeSustain -- Alias for MidiMessage::makeSustain(). +// + +void MidiMessage::makeSustainPedal(int channel, int value) { + makeSustain(channel, value); +} + + + +///////////////////////////// +// +// MidiMessage::makeSustainOn -- Create sustain-on controller message. +// + +void MidiMessage::makeSustainOn(int channel) { + makeController(channel, 64, 127); +} + +// +// MidiMessage::makeSustainPedalOn -- Alias for MidiMessage::makeSustainOn(). +// + +void MidiMessage::makeSustainPedalOn(int channel) { + makeSustainOn(channel); +} + + + +///////////////////////////// +// +// MidiMessage::makeSustainOff -- Create a sustain-off controller message. +// + +void MidiMessage::makeSustainOff(int channel) { + makeController(channel, 64, 0); +} + +// +// MidiMessage::makeSustainPedalOff -- Alias for MidiMessage::makeSustainOff(). +// + +void MidiMessage::makeSustainPedalOff(int channel) { + makeSustainOff(channel); +} + + + +////////////////////////////// +// +// MidiMessage::makeMetaMessage -- Create a Meta event with the +// given text string as the parameter. The length of the string should +// is a VLV. If the length is larger than 127 byte, then the length +// will contain more than one byte. +// + +void MidiMessage::makeMetaMessage(int mnum, const std::string& data) { + resize(0); + push_back(0xff); + push_back(mnum & 0x7f); // max meta-message number is 0x7f. + setMetaContent(data); +} + + + +////////////////////////////// +// +// MidiMessage::makeText -- Create a metaevent text message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeText(const std::string& text) { + makeMetaMessage(0x01, text); +} + + + +////////////////////////////// +// +// MidiMessage::makeCopyright -- Create a metaevent copyright message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeCopyright(const std::string& text) { + makeMetaMessage(0x02, text); +} + + + +////////////////////////////// +// +// MidiMessage::makeTrackName -- Create a metaevent track name message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeTrackName(const std::string& name) { + makeMetaMessage(0x03, name); +} + + + +////////////////////////////// +// +// MidiMessage::makeTrackName -- Create a metaevent instrument name message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeInstrumentName(const std::string& name) { + makeMetaMessage(0x04, name); +} + + + +////////////////////////////// +// +// MidiMessage::makeLyric -- Create a metaevent lyrics/text message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeLyric(const std::string& text) { + makeMetaMessage(0x05, text); +} + + + +////////////////////////////// +// +// MidiMessage::makeMarker -- Create a metaevent marker message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeMarker(const std::string& text) { + makeMetaMessage(0x06, text); +} + + + +////////////////////////////// +// +// MidiMessage::makeCue -- Create a metaevent cue-point message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeCue(const std::string& text) { + makeMetaMessage(0x07, text); +} + + +////////////////////////////// +// +// MidiMessage::intToVlv -- Convert an integer into a VLV byte sequence. +// + +std::vector MidiMessage::intToVlv(int value) { + std::vector output; + if (value < 128) { + output.push_back((uchar)value); + } else { + // calculate VLV bytes and insert into message + uchar byte1 = value & 0x7f; + uchar byte2 = (value >> 7) & 0x7f; + uchar byte3 = (value >> 14) & 0x7f; + uchar byte4 = (value >> 21) & 0x7f; + uchar byte5 = (value >> 28) & 0x7f; + if (byte5) { + byte4 |= 0x80; + } + if (byte4) { + byte4 |= 0x80; + byte3 |= 0x80; + } + if (byte3) { + byte3 |= 0x80; + byte2 |= 0x80; + } + if (byte2) { + byte2 |= 0x80; + } + if (byte5) { output.push_back(byte5); } + if (byte4) { output.push_back(byte4); } + if (byte3) { output.push_back(byte3); } + if (byte2) { output.push_back(byte2); } + output.push_back(byte1); + } + + return output; +} + + + +////////////////////////////// +// +// MidiMessage::makeSysExMessage -- Add F0 at start and F7 at end (do not include +// in data, but they will be double-checked for and ignored if found. +// + +void MidiMessage::makeSysExMessage(const std::vector& data) { + int startindex = 0; + int endindex = (int)data.size() - 1; + if (data.size() > 0) { + if (data[0] == 0xf0) { + startindex++; + } + } + if (data.size() > 0) { + if (data.back() == 0xf7) { + endindex--; + } + } + + this->clear(); + this->reserve(data.size() + 7); + + this->push_back((uchar)0xf0); + + int msize = endindex - startindex + 2; + std::vector vlv = intToVlv(msize); + for (uchar item : vlv) { + this->push_back(item); + } + for (int i=startindex; i<=endindex; i++) { + this->push_back(data.at(i)); + } + this->push_back((uchar)0xf7); +} + + + +////////////////////////////// +// +// MidiMessage::frequencyToSemitones -- convert from frequency in Hertz to +// semitones (MIDI key numbers with fractional values). Returns 0.0 +// if too low, and returns 127.0 if too high. +// + +double MidiMessage::frequencyToSemitones(double frequency, double a4frequency) { + if (frequency < 1) { + return 0.0; + } + if (a4frequency <= 0) { + return 0.0; + } + double semitones = 69.0 + 12.0 * log2(frequency/a4frequency); + if (semitones >= 128.0) { + return 127.0; + } else if (semitones < 0.0) { + return 0.0; + } + return semitones; +} + + + +////////////////////////////// +// +// MidiMessage::makeMts2_KeyTuningsByFrequency -- Map a list of key numbers to specific pitches by frequency. +// + +void MidiMessage::makeMts2_KeyTuningsByFrequency(int key, double frequency, int program) { + std::vector> mapping; + mapping.push_back(std::make_pair(key, frequency)); + this->makeMts2_KeyTuningsByFrequency(mapping, program); +} + + +void MidiMessage::makeMts2_KeyTuningByFrequency(int key, double frequency, int program) { + this->makeMts2_KeyTuningsByFrequency(key, frequency, program); +} + + +void MidiMessage::makeMts2_KeyTuningsByFrequency(std::vector>& mapping, int program) { + std::vector> semimap(mapping.size()); + for (int i=0; i<(int)mapping.size(); i++) { + semimap[i].first = mapping[i].first; + semimap[i].second = MidiMessage::frequencyToSemitones(mapping[i].second); + } + this->makeMts2_KeyTuningsBySemitone(semimap, program); +} + + + +////////////////////////////// +// +// MidiMessage::makeMts2_KeyTuningsBySemitone -- Map a list of key numbers to specific pitches by absolute +// semitones (MIDI key numbers with fractional values). +// + +void MidiMessage::makeMts2_KeyTuningsBySemitone(int key, double semitone, int program) { + std::vector> semimap; + semimap.push_back(std::make_pair(key, semitone)); + this->makeMts2_KeyTuningsBySemitone(semimap, program); +} + + +void MidiMessage::makeMts2_KeyTuningBySemitone(int key, double semitone, int program) { + this->makeMts2_KeyTuningsBySemitone(key, semitone, program); +} + + +void MidiMessage::makeMts2_KeyTuningsBySemitone(std::vector>& mapping, int program) { + if (program < 0) { + program = 0; + } else if (program > 127) { + program = 127; + } + std::vector data; + data.reserve(mapping.size() * 4 + 10); + data.push_back((uchar)0x7f); // real-time sysex + data.push_back((uchar)0x7f); // all devices + data.push_back((uchar)0x08); // sub-ID#1 (MIDI Tuning) + data.push_back((uchar)0x02); // sub-ID#2 (note change) + data.push_back((uchar)program); // tuning program number (0 - 127) + std::vector vlv = intToVlv((int)mapping.size()); + for (uchar item : vlv) { + data.push_back(item); + } + for (auto &item : mapping) { + int keynum = item.first; + if (keynum < 0) { + keynum = 0; + } else if (keynum > 127) { + keynum = 127; + } + data.push_back((uchar)keynum); + double semitones = item.second; + int sint = (int)semitones; + if (sint < 0) { + sint = 0; + } else if (sint > 127) { + sint = 127; + } + data.push_back((uchar)sint); + double fraction = semitones - sint; + int value = int(fraction * (1 << 14)); + uchar lsb = value & 0x7f; + uchar msb = (value >> 7) & 0x7f; + data.push_back(msb); + data.push_back(lsb); + } + this->makeSysExMessage(data); +} + + + +////////////////////////////// +// +// MidiMessage::makeMts9_TemperamentByCentsDeviationFromET -- +// + +void MidiMessage::makeMts9_TemperamentByCentsDeviationFromET (std::vector& mapping, int referencePitchClass, int channelMask) { + if (mapping.size() != 12) { + std::cerr << "Error: input mapping must have a size of 12." << std::endl; + return; + } + if (referencePitchClass < 0) { + std::cerr << "Error: Cannot have a negative reference pitch class" << std::endl; + return; + } + + std::vector data; + data.reserve(24 + 7); + + data.push_back((uchar)0x7f); // real-time sysex + data.push_back((uchar)0x7f); // all devices + data.push_back((uchar)0x08); // sub-ID#1 (MIDI Tuning) + data.push_back((uchar)0x09); // sub-ID#2 (note change) + + uchar MMSB = (channelMask >> 14) & 0x3; + uchar MSB = (channelMask >> 7) & 0x7f; + uchar LSB = channelMask & 0x7f; + + data.push_back(MMSB); + data.push_back(MSB); + data.push_back(LSB); + + for (int i=0; i<(int)mapping.size(); i++) { + int ii = (i - referencePitchClass + 48) % 12; + double value = mapping.at(ii) / 100.0; + + if (value > 1.0) { + value = 1.0; + } + if (value < -1.0) { + value = -1.0; + } + + int intval = (int)(((1 << 13)-0.5) * (value + 1.0) + 0.5); + uchar LSB = intval & 0x7f; + uchar MSB = (intval >> 7) & 0x7f; + data.push_back(MSB); + data.push_back(LSB); + } + this->makeSysExMessage(data); +} + + + +////////////////////////////// +// +// MidiMessage::makeEqualTemperament -- +// + +void MidiMessage::makeTemperamentEqual(int referencePitchClass, int channelMask) { + std::vector temperament(12, 0.0); + this->makeMts9_TemperamentByCentsDeviationFromET(temperament, referencePitchClass, channelMask); +} + + + +////////////////////////////// +// +// MidiMessage::makeTemperamentBad -- Detune by random amounts from equal temperament. +// + +void MidiMessage::makeTemperamentBad(double maxDeviationCents, int referencePitchClass, int channelMask) { + if (maxDeviationCents < 0.0) { + maxDeviationCents = -maxDeviationCents; + } + if (maxDeviationCents > 100.0) { + maxDeviationCents = 100.0; + } + std::vector temperament(12); + for (double &item : temperament) { + item = ((rand() / (double)RAND_MAX) * 2.0 - 1.0) * maxDeviationCents; + } + this->makeMts9_TemperamentByCentsDeviationFromET(temperament, referencePitchClass, channelMask); +} + + + +////////////////////////////// +// +// MidiMessage::makeTemperamentPythagorean -- Default reference pitch is 2 (D) +// + +void MidiMessage::makeTemperamentPythagorean(int referencePitchClass, int channelMask) { + std::vector temperament(12); + double x = 1200.0 * log2(3.0 / 2.0); + temperament[1] = x * -5 + 3500; // -9.775 cents + temperament[8] = x * -4 + 2800; // -7.820 cents + temperament[3] = x * -3 + 2100; // -5.865 cents + temperament[10] = x * -2 + 1400; // -3.910 cents + temperament[5] = x * -1 + 700; // -1.955 cents + temperament[0] = 0.0; // 0 cents + temperament[7] = x * 1 - 700; // 1.955 cents + temperament[2] = x * 2 - 1400; // 3.910 cents + temperament[9] = x * 3 - 2100; // 5.865 cents + temperament[4] = x * 4 - 2800; // 7.820 cents + temperament[11] = x * 5 - 3500; // 9.775 cents + temperament[6] = x * 6 - 4200; // 11.730 cents + this->makeMts9_TemperamentByCentsDeviationFromET(temperament, referencePitchClass, channelMask); +} + + + +////////////////////////////// +// +// MidiMessage::makeTemperamentMeantone -- Default type is 1/4-comma meantone. +// + +void MidiMessage::makeTemperamentMeantone(double fraction, int referencePitchClass, int channelMask) { + std::vector temperament(12); + double x = 1200.0 * log2((3.0/2.0)*pow(81.0/80.0, -fraction)); + temperament[1] = x * -5 + 3500; // 17.107 cents (for fraction = 0.25) + temperament[8] = x * -4 + 2800; // 13.686 cents (for fraction = 0.25) + temperament[3] = x * -3 + 2100; // 10.265 cents (for fraction = 0.25) + temperament[10] = x * -2 + 1400; // 6.843 cents (for fraction = 0.25) + temperament[5] = x * -1 + 700; // 3.422 cents (for fraction = 0.25) + temperament[0] = 0.0; // 0 cents + temperament[7] = x * 1 - 700; // -3.422 cents (for fraction = 0.25) + temperament[2] = x * 2 - 1400; // -6.843 cents (for fraction = 0.25) + temperament[9] = x * 3 - 2100; // -10.265 cents (for fraction = 0.25) + temperament[4] = x * 4 - 2800; // -13.686 cents (for fraction = 0.25) + temperament[11] = x * 5 - 3500; // -17.107 cents (for fraction = 0.25) + temperament[6] = x * 6 - 4200; // -20.529 cents (for fraction = 0.25) + this->makeMts9_TemperamentByCentsDeviationFromET(temperament, referencePitchClass, channelMask); +} + + + +////////////////////////////// +// +// MidiMessage::makeTemperamentMeantoneCommaQuarter -- 1/4-comma meantone +// + +void MidiMessage::makeTemperamentMeantoneCommaQuarter(int referencePitchClass, int channelMask) { + this->makeTemperamentMeantone(1.0 / 4.0, referencePitchClass, channelMask); +} + + + +////////////////////////////// +// +// MidiMessage::makeTemperamentMeantoneCommaThird -- 1/3-comma meantone +// + +void MidiMessage::makeTemperamentMeantoneCommaThird(int referencePitchClass, int channelMask) { + this->makeTemperamentMeantone(1.0 / 3.0, referencePitchClass, channelMask); +} + + + +////////////////////////////// +// +// MidiMessage::makeTemperamentMeantoneCommaHalf -- 1/2-comma meantone +// + +void MidiMessage::makeTemperamentMeantoneCommaHalf(int referencePitchClass, int channelMask) { + this->makeTemperamentMeantone(1.0 / 2.0, referencePitchClass, channelMask); +} + + + +////////////////////////////// +// +// operator<<(MidiMessage) -- Print MIDI messages as text. 0x80 and above +// are printed as hex, below as dec (will look strange for meta messages +// and system exclusives which could be dealt with later). +// + +std::ostream& operator<<(std::ostream& out, MidiMessage& message) { + for (int i=0; i<(int)message.size(); i++) { + if (message[i] >= 0x80) { + out << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)message[i]; + out << std::dec << std::setw(0) << std::setfill(' '); + } else { + out << (int)message[i]; + } + if (i<(int)message.size() - 1) { + out << ' '; + } + } + return out; +} + + +} // end namespace smf + + + diff --git a/src/3rdparty/midifile/MidiMessage.h b/src/3rdparty/midifile/MidiMessage.h new file mode 100644 index 0000000000..5e31a95e06 --- /dev/null +++ b/src/3rdparty/midifile/MidiMessage.h @@ -0,0 +1,219 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 20:36:32 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/MidiMessage.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: Storage for bytes of a MIDI message for use in MidiFile +// class. +// + +#ifndef _MIDIMESSAGE_H_INCLUDED +#define _MIDIMESSAGE_H_INCLUDED + +#include +#include +#include +#include + + +namespace smf { + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned long ulong; + +class MidiMessage : public std::vector { + + public: + MidiMessage (void); + MidiMessage (int command); + MidiMessage (int command, int p1); + MidiMessage (int command, int p1, int p2); + MidiMessage (const MidiMessage& message); + MidiMessage (const std::vector& message); + MidiMessage (const std::vector& message); + MidiMessage (const std::vector& message); + + ~MidiMessage (); + + MidiMessage& operator= (const MidiMessage& message); + MidiMessage& operator= (const std::vector& bytes); + MidiMessage& operator= (const std::vector& bytes); + MidiMessage& operator= (const std::vector& bytes); + + void sortTrack (void); + void sortTrackWithSequence(void); + + static std::vector intToVlv (int value); + static double frequencyToSemitones (double frequency, double a4frequency = 440.0); + + // data access convenience functions (returns -1 if not present): + int getP0 (void) const; + int getP1 (void) const; + int getP2 (void) const; + int getP3 (void) const; + void setP0 (int value); + void setP1 (int value); + void setP2 (int value); + void setP3 (int value); + + int getSize (void) const; + void setSize (int asize); + int setSizeToCommand (void); + int resizeToCommand (void); + + // note-message convenience functions: + int getKeyNumber (void) const; + int getVelocity (void) const; + void setKeyNumber (int value); + void setVelocity (int value); + void setSpelling (int base7, int accidental); + void getSpelling (int& base7, int& accidental); + + // controller-message convenience functions: + int getControllerNumber (void) const; + int getControllerValue (void) const; + + int getCommandNibble (void) const; + int getCommandByte (void) const; + int getChannelNibble (void) const; + int getChannel (void) const; + + void setCommandByte (int value); + void setCommand (int value); + void setCommand (int value, int p1); + void setCommand (int value, int p1, int p2); + void setCommandNibble (int value); + void setChannelNibble (int value); + void setChannel (int value); + void setParameters (int p1, int p2); + void setParameters (int p1); + void setMessage (const std::vector& message); + void setMessage (const std::vector& message); + void setMessage (const std::vector& message); + + // message-type convenience functions: + bool isMetaMessage (void) const; + bool isMeta (void) const; + bool isNote (void) const; + bool isNoteOff (void) const; + bool isNoteOn (void) const; + bool isAftertouch (void) const; + bool isController (void) const; + bool isSustain (void) const; // controller 64 + bool isSustainOn (void) const; + bool isSustainOff (void) const; + bool isSoft (void) const; // controller 67 + bool isSoftOn (void) const; + bool isSoftOff (void) const; + bool isPatchChange (void) const; + bool isTimbre (void) const; + bool isPressure (void) const; + bool isPitchbend (void) const; + bool isEmpty (void) const; // see MidiFile::removeEmpties() + + // helper functions to create various MidiMessages: + void makeNoteOn (int channel, int key, int velocity); + void makeNoteOff (int channel, int key, int velocity); + void makeNoteOff (int channel, int key); + void makeNoteOff (void); + void makePatchChange (int channel, int patchnum); + void makeTimbre (int channel, int patchnum); + void makeController (int channel, int num, int value); + void makePitchBend (int channel, int lsb, int msb); + void makePitchBend (int channel, int value); + void makePitchBendDouble (int channel, double value); + void makePitchbend (int channel, int lsb, int msb) { makePitchBend(channel, lsb, msb); } + void makePitchbend (int channel, int value) { makePitchBend(channel, value); } + void makePitchbendDouble (int channel, double value) { makePitchBendDouble(channel, value); } + + // helper functions to create various continuous controller messages: + void makeSustain (int channel, int value); + void makeSustainPedal (int channel, int value); + void makeSustainOn (int channel); + void makeSustainPedalOn (int channel); + void makeSustainOff (int channel); + void makeSustainPedalOff (int channel); + + // meta-message creation and helper functions: + void makeMetaMessage (int mnum, const std::string& data); + void makeText (const std::string& name); + void makeCopyright (const std::string& text); + void makeTrackName (const std::string& name); + void makeInstrumentName (const std::string& name); + void makeLyric (const std::string& text); + void makeMarker (const std::string& text); + void makeCue (const std::string& text); + void makeKeySignature (int fifths, bool mode = 0); + void makeTimeSignature (int top, int bottom, + int clocksPerClick = 24, + int num32dsPerQuarter = 8); + + void makeTempo (double tempo) { setTempo(tempo); } + int getTempoMicro (void) const; + int getTempoMicroseconds (void) const; + double getTempoSeconds (void) const; + double getTempoBPM (void) const; + double getTempoTPS (int tpq) const; + double getTempoSPT (int tpq) const; + + int getMetaType (void) const; + bool isText (void) const; + bool isCopyright (void) const; + bool isTrackName (void) const; + bool isInstrumentName (void) const; + bool isLyricText (void) const; + bool isMarkerText (void) const; + bool isTempo (void) const; + bool isTimeSignature (void) const; + bool isKeySignature (void) const; + bool isEndOfTrack (void) const; + + std::string getMetaContent (void) const; + void setMetaContent (const std::string& content); + void setTempo (double tempo); + void setTempoMicroseconds (int microseconds); + void setMetaTempo (double tempo); + + + void makeSysExMessage (const std::vector& data); + + // helper functions to create MTS tunings by key (real-time sysex) + + // MTS type 2: Real-time frequency assignment to a arbitrary list of MIDI key numbers. + // See page 2 of: https://docs.google.com/viewer?url=https://www.midi.org/component/edocman/midi-tuning-updated/fdocument?Itemid=9999 + void makeMts2_KeyTuningByFrequency (int key, double frequency, int program = 0); + void makeMts2_KeyTuningsByFrequency (int key, double frequency, int program = 0); + void makeMts2_KeyTuningsByFrequency (std::vector>& mapping, int program = 0); + void makeMts2_KeyTuningBySemitone (int key, double semitone, int program = 0); + void makeMts2_KeyTuningsBySemitone (int key, double semitone, int program = 0); + void makeMts2_KeyTuningsBySemitone (std::vector>& mapping, int program = 0); + + // MTS type 9: Real-time octave temperaments by +/- 100 cents deviation from ET + // See page 7 of: https://docs.google.com/viewer?url=https://www.midi.org/component/edocman/midi-tuning-updated/fdocument?Itemid=9999 + void makeMts9_TemperamentByCentsDeviationFromET (std::vector& mapping, int referencePitchClass = 0, int channelMask = 0b1111111111111111); + void makeTemperamentEqual(int referencePitchClass = 0, int channelMask = 0b1111111111111111); + void makeTemperamentBad(double maxDeviationCents = 100.0, int referencePitchClass = 0, int channelMask = 0b1111111111111111); + void makeTemperamentPythagorean(int referencePitchClass = 2, int channelMask = 0b1111111111111111); + void makeTemperamentMeantone(double fraction = 0.25, int referencePitchClass = 2, int channelMask = 0b1111111111111111); + void makeTemperamentMeantoneCommaQuarter(int referencePitchClass = 2, int channelMask = 0b1111111111111111); + void makeTemperamentMeantoneCommaThird(int referencePitchClass = 2, int channelMask = 0b1111111111111111); + void makeTemperamentMeantoneCommaHalf(int referencePitchClass = 2, int channelMask = 0b1111111111111111); + +}; + + +std::ostream& operator<<(std::ostream& out, MidiMessage& event); + + +} // end of namespace smf + + +#endif /* _MIDIMESSAGE_H_INCLUDED */ + + + diff --git a/src/music/CMakeLists.txt b/src/music/CMakeLists.txt index cf60e78669..e24cc1d29c 100644 --- a/src/music/CMakeLists.txt +++ b/src/music/CMakeLists.txt @@ -37,6 +37,12 @@ if(NOT OPTION_DEDICATED) CONDITION HAIKU ) + add_files( + alsamidi.cpp + alsamidi.h + CONDITION ALSA_FOUND + ) + add_files( midi.h midifile.cpp diff --git a/src/music/alsamidi.cpp b/src/music/alsamidi.cpp new file mode 100644 index 0000000000..1497ffac31 --- /dev/null +++ b/src/music/alsamidi.cpp @@ -0,0 +1,604 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** @file alsamidi.cpp Support for ALSA Linux MIDI. */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "alsamidi.h" +#include "../base_media_base.h" +#include "midifile.hpp" +#include "../debug.h" +#include "midi.h" +#include +#include + +#include "../safeguards.h" + +/** Factory for ALSA MIDI player. */ +static FMusicDriver_AlsaMidi iFMusicDriver_AlsaMidi; + +std::optional MusicDriver_AlsaMidi::Start(const StringList &parm) +{ + this->playing.store(false); + Debug(driver, 2, "ALSA MIDI: Start"); + this->dev_port = (uint)GetDriverParamInt(parm, "port", UINT_MAX); + Debug(driver, 2, "ALSA MIDI: using MIDI device at port {}", dev_port); + + // Open sequencer + if (snd_seq_open(&this->seq, "default", SND_SEQ_OPEN_OUTPUT, 0) < 0) { + return "Failed to open ALSA sequencer"; + } + + snd_seq_set_client_name(this->seq, "OpenTTD MIDI Out"); + + // Create port + this->seq_port = snd_seq_create_simple_port(this->seq, "MIDI Out", + SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, + SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION); + + if (this->seq_port < 0) { + return "Failed to create ALSA sequencer port"; + } + + // Create event queue + Debug(driver, 2, "ALSA MIDI: Creating sequencer event queue"); + this->queue_id = snd_seq_alloc_named_queue(seq, "OpenTTD Sequencer Queue"); + if (this->queue_id < 0) { + return "Failed to create ALSA sequencer event queue"; + } + + // Set a slightly larger event output buffer than normal + snd_seq_set_client_pool_output(this->seq, 1000); + snd_seq_set_client_pool_output_room(this->seq, 1000); + snd_seq_set_output_buffer_size(this->seq, 1000); + + // Turn on nonblocking mode + snd_seq_nonblock(this->seq, 1); + + snd_seq_addr_t sender, dest; + + // Setup event port + sender.client = snd_seq_client_id(this->seq); + sender.port = this->seq_port; + + dest.client = this->dev_port; + dest.port = this->seq_port; + + snd_seq_port_subscribe_t* subs; + snd_seq_port_subscribe_alloca(&subs); + snd_seq_port_subscribe_set_sender(subs, &sender); + snd_seq_port_subscribe_set_dest(subs, &dest); + + if (snd_seq_subscribe_port(this->seq, subs) < 0) { + return "Failed to connect to port"; + } + + Debug(driver, 2, "ALSA MIDI: opened sequencer port {}", this->seq_port); + + this->InitMidiVolume(); + + return std::nullopt; +} + +void MusicDriver_AlsaMidi::Stop() +{ + Debug(driver, 2, "ALSA MIDI: stopping"); + this->StopSong(); + + if (this->queue_id) { + Debug(driver, 2, "ALSA MIDI: freeing sequencer event queue"); + snd_seq_free_queue(this->seq, this->queue_id); + } + + if (this->seq) { + Debug(driver, 2, "ALSA MIDI: closing sequencer handle"); + snd_seq_close(this->seq); + } +} + +void MusicDriver_AlsaMidi::PlaySong(const MusicSongInfo &song) +{ + Debug(driver, 2, "ALSA MIDI: PlaySong"); + std::string filename = MidiFile::GetSMFFile(song); + smf::MidiFile midifile; + + Debug(driver, 2, "ALSA MIDI: reading SMFFile"); + if (!filename.empty()) { + if (!midifile.read(filename)) { + Debug(driver, 2, "ALSA MIDI: error reading SMFFile"); + } + } + + // Sort + midifile.sortTracks(); + // Convert MIDI ticks to absolute seconds + midifile.doTimeAnalysis(); + + // Merge > 1 tracks into single track for easier queueing. + // (WriteSMF only creates single-track MIDIs, other packs may be multitrack) + midifile.joinTracks(); + + if (this->playing.load() == true) { + this->StopSong(); + } + + Debug(driver, 2, "ALSA MIDI: starting playback of {}", song.songname); + Debug(driver, 2, "ALSA MIDI: SMF filename {}", filename); + + // ALSA does not allow setting PPQ on started queues, so do this first. + // Tempo may be adjusted later, on a started/running queue. + int ppq = midifile.getTPQ(); + this->SetPPQ(ppq); + + this->SetupPolling(); + + snd_seq_start_queue(this->seq, this->queue_id, nullptr); + snd_seq_drain_output(this->seq); + + this->playing.store(true); + + StartNewThread(&this->_queue_thread, "ottd:alsamidi", &StartQueue, this, std::move(midifile)); +} + +void MusicDriver_AlsaMidi::SetupPolling() +{ + int poll_fd_cnt = snd_seq_poll_descriptors_count(this->seq, POLLOUT); + this->poll_fds.resize(poll_fd_cnt); + snd_seq_poll_descriptors(this->seq, this->poll_fds.data(), poll_fd_cnt, POLLOUT); +} + +/** + * Starts the ALSA sequencer queue, iterates through the MIDI events in the file, + * converts them to ALSA sequencer events, and pushes them onto the queue. + * + * This function is blocking and expects to be run in a thread. It will block + * until either it is signaled to stop (in which case it will purge the ALSA queue, + * send a GM RESET, and terminate), or it has enqueued all events in the MIDI file, + * and waited for the queue to finish processing them all. + * + * @param drv Pointer to `this` instance of the class + * @param midifile The previously-loaded, sorted, and time-corrected STD MIDI file. + * + * @see Stopping() + * @see StopSong() + * @see WaitForFinish() + */ +static void StartQueue(MusicDriver_AlsaMidi *drv, const smf::MidiFile midifile) +{ + Debug(driver, 2, "ALSA MIDI: queue thread started"); + + unsigned int last_tick; + + // Push all events for all tracks to the sequencer queue + for (int track = 0; track < midifile.getNumTracks(); track++) { + + std::vector sysex_buffer; + + for (int event = 0; event < midifile[track].size(); event++) { + + auto& ev = midifile[track][event]; + + last_tick = static_cast(ev.tick); + + if (drv->Stopping()) { + Debug(driver, 2, "ALSA MIDI: Looks like we are stopping, bailing out of queue thread"); + drv->SendResetEvent(); + drv->StopQueue(); + sysex_buffer.clear(); + return; + } + if (ev.isTempo()) { + // Handle tempo change here, as we have to change it for the whole queue + Debug(driver, 2, "ALSA MIDI: Got tempo change event in queue thread"); + int tempo_uspq = ev.getTempoMicroseconds(); + drv->UpdateTempo(tempo_uspq); + continue; + } + // Handle SYSEX events + // SYSEX events may + // 1. Be a complete SYSEX event (begin with F0 and end with F7) + // 2. Be a "middle" SYSEX event (a previous message began with F0) + // 3. Be an "end" SYSEX event (a previous message began with F0, and this one ends with F7) + // This basically means you need an accumulator. Split SYSEX messages are *rare* but exist. + if (ev.getCommandByte() == MIDIST_SYSEX) { + Debug(driver, 2, "ALSA MIDI: got SYSEX message"); + sysex_buffer.clear(); + + // If this is is a complete (not partial) SYSEX message, send it + // Otherwise, accumulate it as a partial and continue to the next + if (ev.back() == MIDIST_ENDSYSEX) { + Debug(driver, 2, "ALSA MIDI: complete SYSEX, sending"); + sysex_buffer.insert(sysex_buffer.end(), ev.begin() + 1, ev.end() -1); + drv->SendSysexEvent(sysex_buffer); + sysex_buffer.clear(); + } else { + sysex_buffer.insert(sysex_buffer.end(), ev.begin() + 1, ev.end()); + } + continue; + } + if (!sysex_buffer.empty() && ev.back() == MIDIST_ENDSYSEX) { + Debug(driver, 2, "ALSA MIDI: partial SYSEX completed, sending"); + sysex_buffer.insert(sysex_buffer.end(), ev.begin(), ev.end() -1); + drv->SendSysexEvent(sysex_buffer); + sysex_buffer.clear(); + continue; + } + if (!sysex_buffer.empty()) { + Debug(driver, 2, "ALSA MIDI: partial SYSEX continuing"); + sysex_buffer.insert(sysex_buffer.end(), ev.begin(), ev.end()); + continue; + } + + // At this point, it's just a regular event - handle it. + drv->SendEvent(ev); + } + } + + Debug(driver, 2, "ALSA MIDI: queue thread finished, waiting for events"); + drv->WaitForFinish(last_tick); + drv->StopQueue(); +} + +/** + * Stops the ALSA sequencer queue, and sets `playing` to false. + * + * Note that this does not clear or drop any pending events in the queue + * before stopping it. + * + * @see SendResetEvent() + */ +void MusicDriver_AlsaMidi::StopQueue() +{ + Debug(driver, 2, "ALSA MIDI: stopping current queue!"); + snd_seq_stop_queue(this->seq, this->queue_id, nullptr); + snd_seq_drain_output(this->seq); + + this->poll_fds.clear(); + + this->playing.store(false); +} + +/** + * Sends a SYSEX GM reset message, after dropping all pending events in the + * queue. + * + * Does not stop the queue. + * + * @see StopQueue() + */ +void MusicDriver_AlsaMidi::SendResetEvent() +{ + // Drop anything still in the queue, this is a disruptive reset. + snd_seq_drop_output(this->seq); + + std::vector sysex_rst_msg = {0x7E, 0x7F, 0x09, 0x01}; + this->SendSysexEvent(sysex_rst_msg); +} + +/** + * Generic helper for sending SYSEX messages. + * + * Note that this sends all SYSEX messages as "direct"/unscheduled events + * (skips tick queue). + * + * @param data The SYSEX message data (excluding 0xF0/0xF7 begin/end markers). + * + * @see SendEvent() + */ +void MusicDriver_AlsaMidi::SendSysexEvent(const std::vector data) +{ + snd_seq_event_t seqev; + snd_seq_ev_clear(&seqev); + snd_seq_ev_set_source(&seqev, this->seq_port); + snd_seq_ev_set_subs(&seqev); + + std::vector complete_message; + complete_message.reserve(data.size() + 2); + complete_message.push_back(0xF0); // Start of SysEx + complete_message.insert(complete_message.end(), data.begin(), data.end()); + complete_message.push_back(0xF7); // End of SysEx + snd_seq_ev_set_sysex(&seqev, complete_message.size(), complete_message.data()); + + // TODO this assumes all SYSEX msgs are immediate, and not queued with a tick + // this is correct for SYSEX GM RESET (all this is currently used for) but + // might not be globally correct. + snd_seq_ev_set_direct(&seqev); + + this->PushEvent(seqev); +} + +/** + * Generic helper for sending non-SYSEX messages. + * + * Converts MIDI events from the file into ALSA-specific sequencer queue events, + * and schedules them on the tick-based sequencer queue. + * + * @param ev The raw MIDI event from the file. + * + * @see SendSysexEvent() + */ +void MusicDriver_AlsaMidi::SendEvent(const smf::MidiEvent& ev) +{ + snd_seq_event_t seqev; + snd_seq_ev_clear(&seqev); + + unsigned int ticks = static_cast(ev.tick); + + if (ev.isNoteOn()) { + snd_seq_ev_set_noteon(&seqev, ev.getChannel(), ev[1], ev[2]); + } else if (ev.isNoteOff()) { + snd_seq_ev_set_noteoff(&seqev, ev.getChannel(), ev[1], ev[2]); + } else if (ev.isController()) { + if (ev[1] == MIDI_CTL_MSB_MAIN_VOLUME) { + this->UpdateChannelVolume(ev.getChannel(), ev[2]); + snd_seq_ev_set_controller(&seqev, ev.getChannel(), ev[1], this->vol_state.current_volume[ev.getChannel()]); + } + snd_seq_ev_set_controller(&seqev, ev.getChannel(), ev[1], ev[2]); + } else if (ev.isPatchChange()) { + snd_seq_ev_set_pgmchange(&seqev, ev.getChannel(), ev[1]); + } else if (ev.isPitchbend()) { + snd_seq_ev_set_pitchbend(&seqev, ev.getChannel(), ((ev[2] << 7) | ev[1]) - 8192); + } else if (ev.isPressure()) { + snd_seq_ev_set_chanpress(&seqev, ev.getChannel(), ev[1]); + } else if (ev.isAftertouch()) { + snd_seq_ev_set_keypress(&seqev, ev.getChannel(), ev[1], ev[2]); + } else if (ev.getCommandNibble() == 0xF0 && ev.getCommandByte() == MIDIST_SYSRESET) { + Debug(driver, 2, "ALSA MIDI: reset event"); + snd_seq_ev_set_fixed(&seqev); + seqev.type = SND_SEQ_EVENT_RESET; + } else if (ev.isMeta()) { + Debug(driver, 2, "ALSA MIDI: ignoring meta message"); + return; + } else { + Debug(driver, 2, "ALSA MIDI: unknown message: {}", ev.getCommandNibble()); + return; + } + + // Schedule event + snd_seq_ev_schedule_tick(&seqev, this->queue_id, 0, ticks); + snd_seq_ev_set_source(&seqev, this->seq_port); + snd_seq_ev_set_subs(&seqev); + + this->PushEvent(seqev); +} + +/** + * Waits until either: + * + * 1. The ALSA sequencer finishes processing up to the last event + * that was enqueued, as measured by comparing the tick value of the + * last event against the current tick value of the ALSA queue state. + * + * 2. `Stopping()` returns true, signaling early exit (don't wait for playback to finish). + * + * @param last_event_tick Tick value of the last event that was added to the queue. + * + * @see Stopping() + */ +void MusicDriver_AlsaMidi::WaitForFinish(const unsigned int last_event_tick) +{ + Debug(driver, 2, "ALSA MIDI: waiting for events finish"); + + // First wait for queue to drain + int res = 0; + do { + res = snd_seq_drain_output(this->seq); + if (res != 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } while (res != 0); + + // Now get queue status and wait until we've passed the last scheduled tick + snd_seq_queue_status_t *status; + snd_seq_queue_status_alloca(&status); + + do { + snd_seq_get_queue_status(this->seq, this->queue_id, status); + const snd_seq_tick_time_t current_tick = snd_seq_queue_status_get_tick_time(status); + + if (this->Stopping()) { + Debug(driver, 2, "ALSA MIDI: got stop signal, not waiting for events to finish"); + this->SendResetEvent(); + break; + } + + if (current_tick >= last_event_tick) { + // This is necessarily imprecise, just because the queue has processed the last + // tick event doesn't mean whatever output device in use has played it yet, + // but in practice this is good enough to not cut off the last few notes. + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } while(true); + + Debug(driver, 2, "ALSA MIDI: events finished"); +} + +/** + * Pushes an ALSA sequencer event onto the ALSA sequencer queue. + * + * If the push fails because the output buffer is full, uses `poll()` + * to wait until there's space/device is ready. + * + * @param seqev ALSA sequencer event to push. + */ +void MusicDriver_AlsaMidi::PushEvent(snd_seq_event_t seqev) +{ + // Wait for space in the queue via `poll()` + while (snd_seq_event_output_direct(this->seq, &seqev) < 0) { + poll(this->poll_fds.data(), this->poll_fds.size(), 100); // 100ms timeout + } +} + +/** + * Signals the queue thread to terminate and joins it. + */ +void MusicDriver_AlsaMidi::StopSong() +{ + this->stopping.store(true); + + Debug(driver, 2, "ALSA MIDI: StopSong waiting for queue thread"); + if (this->_queue_thread.joinable()) { + this->_queue_thread.join(); + } + + Debug(driver, 2, "ALSA MIDI: stopping current queue"); + + this->stopping.store(false); + + Debug(driver, 2, "ALSA MIDI: stopped song"); +} + +bool MusicDriver_AlsaMidi::Stopping() +{ + return this->stopping.load(); +} + +bool MusicDriver_AlsaMidi::IsSongPlaying() +{ + return this->playing.load(); +} + +/** + * Sets the desired volume for the MIDI sequencer (from the UI thread) + * + * Note that this implementation will internally debounce rapid subsequent calls to + * SetVolume(), to avoid overwhelming the sequencer and its queues and buffers with + * incremental volume updates. The magnitude of the volume change is taken into account + * for this. + * + */ +void MusicDriver_AlsaMidi::SetVolume(uint8_t vol) +{ + Debug(driver, 2, "ALSA MIDI: got volume level update {}", vol); + + // Adaptive debounce: small changes need more time between updates + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - this->last_volume_update); + + int current_vol = this->current_vol.load(); + int change_magnitude = std::abs(vol - current_vol); + + int required_ms = change_magnitude < 5 ? SMALL_VOL_DEBOUNCE : + change_magnitude < 15 ? MED_VOL_DEBOUNCE : + LARGE_VOL_DEBOUNCE; + + + if (elapsed.count() >= required_ms || current_vol == 127) { + Debug(driver, 2, "ALSA MIDI: got volume level update {}", vol); + if (vol != current_vol) { + this->SetScaledVolume(vol); + this->last_volume_update = now; + } + } +} + +/** + * Initializes the volume state for the player. + * + * @see UpdateChannelVolume() + * @see SetScaledVolume() + */ +void MusicDriver_AlsaMidi::InitMidiVolume() +{ + this->last_volume_update = std::chrono::steady_clock::now(); + + this->vol_state.master_scale = 127; + for (int i = 0; i < MIDI_CHANNELS; i++) { + this->vol_state.base_volume[i] = 127; + this->vol_state.current_volume[i] = 127; + } +} + +/** + * Scales current volume level for each channel according to the scale + * factor provided. This is to maintain the relative volume levels between + * channels as set by the midi file, while scaling up or down. + * + * @param channel to update the volume for. + * @param value requested volume level to scale selected channel volume against. + */ +void MusicDriver_AlsaMidi::UpdateChannelVolume(int channel, int value) +{ + std::lock_guard lock(this->vol_state.mutex); + this->vol_state.base_volume[channel] = value; + this->vol_state.current_volume[channel] = (value * this->vol_state.master_scale) / 127; + Debug(driver, 2, "ALSA MIDI: upading volume for channel {} to {}, base: {}, scale {}", channel, this->vol_state.current_volume[channel], this->vol_state.base_volume[channel], this->vol_state.master_scale); +} + +/** + * Scales current volume level for each channel according to the scale + * factor provided. This is to maintain the relative volume levels between + * channels as set by the midi file, while scaling up or down. + * + * @param value requested volume level (0-127) to scale all channel volume levels against. + */ +void MusicDriver_AlsaMidi::SetScaledVolume(int value) +{ + std::lock_guard lock(this->vol_state.mutex); + this->vol_state.master_scale = (value > 127) ? 127 : (value < 0) ? 0 : value; + + for (int i = 0; i < MIDI_CHANNELS; i++) { + this->vol_state.current_volume[i] = ( this->vol_state.base_volume[i] * this->vol_state.master_scale) / 127; + snd_seq_event_t vol_ev; + snd_seq_ev_clear(&vol_ev); + Debug(driver, 2, "ALSA MIDI: setting volume for channel {} to {}, master: {} base: {}", i, this->vol_state.current_volume[i], this->vol_state.master_scale, this->vol_state.base_volume[i]); + snd_seq_ev_set_controller(&vol_ev, i, MIDI_CTL_MSB_MAIN_VOLUME, this->vol_state.current_volume[i]); + snd_seq_ev_set_source(&vol_ev, this->seq_port); + snd_seq_ev_set_subs(&vol_ev); + snd_seq_ev_set_direct(&vol_ev); + this->PushEvent(vol_ev); + } + + this->current_vol.store(value); +} + +/** + * Updates the tempo of the current (started) ALSA sequencer queue. + * + * @param tempo_uspq Tempo value in units per quarter note. + */ +void MusicDriver_AlsaMidi::UpdateTempo(const int tempo_uspq) +{ + if (snd_seq_change_queue_tempo(this->seq, this->queue_id, tempo_uspq, nullptr) < 0) { + throw std::runtime_error("Failed to update queue tempo"); + } + snd_seq_drain_output(this->seq); +} + +/** + * Updates the Pulses Per Quarternote (PPQ) of the current ALSA sequencer queue. + * + * Note that the PPQ of an ALSA sequencer queue cannot be changed after it is started. + * + * @param ppq Pulse per quarter note value. + * + * @see StopQueue() + */ +void MusicDriver_AlsaMidi::SetPPQ(const int ppq) +{ + Debug(driver, 2, "ALSA MIDI: setting PPQ to {}", ppq); + snd_seq_queue_tempo_t* tempo; + snd_seq_queue_tempo_alloca(&tempo); + snd_seq_queue_tempo_set_ppq(tempo, ppq); + snd_seq_queue_tempo_set_tempo(tempo, 1000000); // 60 BPM + + snd_seq_queue_status_t *status; + snd_seq_queue_status_alloca(&status); + snd_seq_get_queue_status(seq, queue_id, status); + + if (snd_seq_queue_status_get_status(status) == 0) { + if (snd_seq_set_queue_tempo(this->seq, this->queue_id, tempo) < 0) { + throw std::runtime_error("Failed to set queue PPQ"); + } + snd_seq_drain_output(this->seq); + } else { + Debug(driver, 2, "ALSA MIDI: tried to set PPQ on non-stopped queue!"); + } +} diff --git a/src/music/alsamidi.h b/src/music/alsamidi.h new file mode 100644 index 0000000000..5480ed8803 --- /dev/null +++ b/src/music/alsamidi.h @@ -0,0 +1,93 @@ +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** @file alsamidi.h Base of Alsa MIDI support. */ + +#ifndef MUSIC_ALSAMIDI_H +#define MUSIC_ALSAMIDI_H + +#include "music_driver.hpp" +#include +#include "../3rdparty/midifile/MidiFile.h" +#include "../../thread.h" +#include + +const int SMALL_VOL_DEBOUNCE = 200; // Tiny changes: 200ms +const int MED_VOL_DEBOUNCE = 50; // Small changes: 50ms +const int LARGE_VOL_DEBOUNCE = 10; // Large changes: 10ms + +/** The midi player for ALSA on Linux. */ +class MusicDriver_AlsaMidi : public MusicDriver { +public: + std::optional Start(const StringList ¶m) override; + + void Stop() override; + + void PlaySong(const MusicSongInfo &song) override; + + void StopSong() override; + + bool IsSongPlaying() override; + + void SetVolume(uint8_t vol) override; + + std::string_view GetName() const override { return "alsamidi"; } + + void WaitForFinish(const unsigned int last_tick); + + void StopQueue(); + + void SendEvent(const smf::MidiEvent& event); + + void SendSysexEvent(const std::vector data); + + void SendResetEvent(); + + void UpdateTempo(const int tempo_uspq); + + bool Stopping(); + + +private: + typedef struct + { + int base_volume[MIDI_CHANNELS]; + int master_scale; + int current_volume[MIDI_CHANNELS]; + std::mutex mutex; + }MidiVolume; + + MidiVolume vol_state; + snd_seq_t* seq; + int queue_id; + int seq_port; + int dev_port; + std::vector poll_fds; + std::thread _queue_thread; + std::atomic playing{false}; + std::atomic stopping{false}; + std::atomic current_vol{127}; + std::chrono::time_point last_volume_update; + void SetPPQ(const int ppq); + void SetScaledVolume(int value); + void SetupPolling(); + void PushEvent(snd_seq_event_t seqev); + void InitMidiVolume(); + void UpdateChannelVolume(int channel, int value); + void VolumeAdjust (const uint8_t new_vol); +}; + +/** Factory for the Linux ALSA midi player. */ +class FMusicDriver_AlsaMidi : public DriverFactoryBase { +public: + FMusicDriver_AlsaMidi() : DriverFactoryBase(Driver::DT_MUSIC, 10, "alsamidi", "ALSA Linux MIDI Driver") {} + Driver *CreateInstance() const override { return new MusicDriver_AlsaMidi(); } +}; + +static void StartQueue(MusicDriver_AlsaMidi *drv, const smf::MidiFile midifile); + +#endif /* MUSIC_ALSAMIDI_H */