1
0
Fork 0
drelbszoomer 2025-06-26 07:33:45 +00:00 committed by GitHub
commit 562e065d20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 10467 additions and 0 deletions

View File

@ -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

View File

@ -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)

View File

@ -7,3 +7,4 @@ add_subdirectory(squirrel)
add_subdirectory(nlohmann)
add_subdirectory(opengl)
add_subdirectory(openttd_social_integration_api)
add_subdirectory(midifile)

2012
src/3rdparty/midifile/Binasc.cpp vendored 100644

File diff suppressed because it is too large Load Diff

161
src/3rdparty/midifile/Binasc.h vendored 100644
View File

@ -0,0 +1,161 @@
//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// 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 <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
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 */

View File

@ -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
)

View File

@ -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.

View File

@ -0,0 +1,298 @@
//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// 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 <cstdlib>
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<uchar>& 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<uchar>& 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<char>& bytes) {
clearVariables();
setMessage(bytes);
return *this;
}
MidiEvent& MidiEvent::operator=(const vector<int>& 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<MidiMessage&>(event) << ')';
return out;
}
} // end namespace smf

View File

@ -0,0 +1,78 @@
//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// 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 <ostream>
#include <vector>
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<uchar>& message);
~MidiEvent ();
MidiEvent& operator= (const MidiEvent& mfevent);
MidiEvent& operator= (const MidiMessage& message);
MidiEvent& operator= (const std::vector<uchar>& bytes);
MidiEvent& operator= (const std::vector<char>& bytes);
MidiEvent& operator= (const std::vector<int>& 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 */

View File

@ -0,0 +1,623 @@
//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// 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 <algorithm>
#include <cstdlib>
#include <iterator>
#include <utility>
#include <vector>
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<MidiEvent*> 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<std::vector<std::vector<MidiEvent*>>> 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<std::pair<int, int>> contmap;
contmap.resize(128);
std::pair<int, int> 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<std::vector<MidiEvent*>> contevents;
contevents.resize(18);
std::vector<std::vector<int>> 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; i<getSize(); i++) {
mev = &getEvent(i);
mev->unlinkEvent();
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<getEventCount(); i++) {
getEvent(i).seq = 0;
}
}
//////////////////////////////
//
// MidiEventList::markSequence -- Assign a sequence serial number to
// every MidiEvent in the event list. 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 events occurring at
// the same time is important. Use clearSequence() to use the
// default sorting behavior of sortTracks() when events occur at the
// same time. Returns the next serial number that has not yet been
// used.
// default value: sequence = 1.
//
int MidiEventList::markSequence(int sequence) {
for (int i=0; i<getEventCount(); i++) {
getEvent(i).seq = sequence++;
}
return sequence;
}
///////////////////////////////////////////////////////////////////////////
//
// protected functions --
//
//////////////////////////////
//
// MidiEventList::detach -- De-allocate any MidiEvents present in the list
// and set the size of the list to 0.
//
void MidiEventList::detach(void) {
list.resize(0);
}
//////////////////////////////
//
// MidiEventList::push_back_no_copy -- add a MidiEvent at the end of
// the list. The event is not copied, but memory from the
// remote location is used. Returns the index of the appended event.
//
int MidiEventList::push_back_no_copy(MidiEvent* event) {
list.push_back(event);
return (int)list.size()-1;
}
//////////////////////////////
//
// MidiEventList::operator=(MidiEventList) -- Assignment.
//
MidiEventList& MidiEventList::operator=(MidiEventList& other) {
list.swap(other.list);
return *this;
}
///////////////////////////////////////////////////////////////////////////
//
// private functions
//
//////////////////////////////
//
// MidiEventList::sort -- Private because the MidiFile class keeps
// track of delta versus absolute tick states of the MidiEventList,
// and sorting is only allowed in absolute tick state (The MidiEventList
// does not know about delta/absolute tick states of its contents).
//
void MidiEventList::sort(void) {
qsort(data(), getEventCount(), sizeof(MidiEvent*), eventcompare);
}
///////////////////////////////////////////////////////////////////////////
//
// external functions
//
//////////////////////////////
//
// eventcompare -- Event comparison function for sorting tracks.
//
// Sorting rules:
// (1) sort by (absolute) tick value; otherwise, if tick values are the same:
// (2) end-of-track meta message is always last.
// (3) other meta-messages come before regular MIDI messages.
// (4) note-offs come after all other regular MIDI messages except note-ons.
// (5) note-ons come after all other regular MIDI messages.
//
int eventcompare(const void* a, const void* b) {
MidiEvent& aevent = **((MidiEvent**)a);
MidiEvent& bevent = **((MidiEvent**)b);
if (aevent.tick > 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

View File

@ -0,0 +1,82 @@
//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// 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 <vector>
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<MidiEvent*> 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 */

3465
src/3rdparty/midifile/MidiFile.cpp vendored 100644

File diff suppressed because it is too large Load Diff

332
src/3rdparty/midifile/MidiFile.h vendored 100644
View File

@ -0,0 +1,332 @@
//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// 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 <fstream>
#include <istream>
#include <string>
#include <vector>
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<uchar>& 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<uchar>& 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<MidiEventList*> 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<uchar>& 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<uchar>& 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<int> decodeLookup;
static const char *GMinstrument[128];
};
} // end of namespace smf
std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile);
#endif /* _MIDIFILE_H_INCLUDED */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,219 @@
//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// 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 <iostream>
#include <string>
#include <utility>
#include <vector>
namespace smf {
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned long ulong;
class MidiMessage : public std::vector<uchar> {
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<uchar>& message);
MidiMessage (const std::vector<char>& message);
MidiMessage (const std::vector<int>& message);
~MidiMessage ();
MidiMessage& operator= (const MidiMessage& message);
MidiMessage& operator= (const std::vector<uchar>& bytes);
MidiMessage& operator= (const std::vector<char>& bytes);
MidiMessage& operator= (const std::vector<int>& bytes);
void sortTrack (void);
void sortTrackWithSequence(void);
static std::vector<uchar> 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<uchar>& message);
void setMessage (const std::vector<char>& message);
void setMessage (const std::vector<int>& 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<uchar>& 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<std::pair<int, double>>& 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<std::pair<int, double>>& 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<double>& 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 */

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @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 <chrono>
#include <thread>
#include "../safeguards.h"
/** Factory for ALSA MIDI player. */
static FMusicDriver_AlsaMidi iFMusicDriver_AlsaMidi;
std::optional<std::string_view> 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<uint8_t> sysex_buffer;
for (int event = 0; event < midifile[track].size(); event++) {
auto& ev = midifile[track][event];
last_tick = static_cast<unsigned int>(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<unsigned char> 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<uint8_t> 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<uint8_t> 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<unsigned int>(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<std::chrono::milliseconds>(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<std::mutex> 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<std::mutex> 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!");
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file alsamidi.h Base of Alsa MIDI support. */
#ifndef MUSIC_ALSAMIDI_H
#define MUSIC_ALSAMIDI_H
#include "music_driver.hpp"
#include <alsa/asoundlib.h>
#include "../3rdparty/midifile/MidiFile.h"
#include "../../thread.h"
#include <atomic>
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<std::string_view> Start(const StringList &param) 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<uint8_t> 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<struct pollfd> poll_fds;
std::thread _queue_thread;
std::atomic<bool> playing{false};
std::atomic<bool> stopping{false};
std::atomic<uint8_t> current_vol{127};
std::chrono::time_point<std::chrono::steady_clock> 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 */