diff --git a/SConstruct b/SConstruct index f738fad..11d45bd 100644 --- a/SConstruct +++ b/SConstruct @@ -22,10 +22,12 @@ opts.Add(EnumVariable("platform","Platform to build",detected_platform,("windows opts.Add(BoolVariable("enable_rtaudio","Use RtAudio as Sound Driver",True)) opts.Add(BoolVariable("use_jack","Use Jack with RtAudio",False)) opts.Add(BoolVariable("use_pulseaudio","Use Pulseaudio with RtAudio",True)) -opts.Add(BoolVariable("use_alsa","Use Alsa with RtAudio",True)) +opts.Add(BoolVariable("use_alsa","Use Alsa with RtAudio and RtMidi",True)) opts.Add(BoolVariable("enable_vst2","Enable VST2",True)) opts.Add(BoolVariable("use_wasapi","Enable Wasapi",True)) opts.Add(BoolVariable("use_directsound","Enable Wasapi",True)) +opts.Add(BoolVariable("enable_rtmidi","Use RtMidi as MIDI Driver",True)) +opts.Add(BoolVariable("use_winmm","Enable WinMM for RtMidi",True)) opts.Update(env) # update environment Help(opts.GenerateHelpText(env)) # generate help @@ -37,36 +39,40 @@ if (detected_platform==""): if (env["enable_rtaudio"]): env.Append(CXXFLAGS=["-DRTAUDIO_ENABLED"]) - if (env["platform"]=="windows"): - if (env["use_wasapi"]): - env.Append(CXXFLAGS=["-D__WINDOWS_WASAPI__"]) - if (env["use_directsound"]): - env.Append(CXXFLAGS=["-D__WINDOWS_DS__"]) - #env.Append(CXXFLAGS=["-D__WINDOWS_ASIO__"]) - env.Append(LIBS=["dsound","mfplat","mfuuid","wmcodecdspuuid","ksuser"]) - - if (env["platform"]=="freedesktop"): - - if (env["use_pulseaudio"]): - env.Append(CXXFLAGS=["-D__LINUX_PULSE__"]) - env.ParseConfig("pkg-config libpulse --libs --cflags") - env.ParseConfig("pkg-config libpulse-simple --libs --cflags") - if (env["use_alsa"]): - env.Append(CXXFLAGS=["-D__LINUX_ALSA__"]) - env.ParseConfig("pkg-config alsa --libs --cflags") - if (env["use_jack"]): - env.Append(CXXFLAGS=["-D__LINUX_JACK__"]) - env.ParseConfig("pkg-config jack --libs --cflags") + +if (env["enable_rtmidi"]): + + env.Append(CXXFLAGS=["-DRTMIDI_ENABLED"]) + if (env["platform"]=="windows"): env.Append(CXXFLAGS=["-DWINDOWS_ENABLED"]) if (env["enable_vst2"]): env.Append(CXXFLAGS=["-DVST2_ENABLED"]) + if (env["use_wasapi"]): + env.Append(CXXFLAGS=["-D__WINDOWS_WASAPI__"]) + if (env["use_directsound"]): + env.Append(CXXFLAGS=["-D__WINDOWS_DS__"]) + if (env["use_winmm"]): + env.Append(CXXFLAGS=["-D__WINDOWS_MM__"]) + + #env.Append(CXXFLAGS=["-D__WINDOWS_ASIO__"]) + env.Append(LIBS=["dsound","mfplat","mfuuid","wmcodecdspuuid","ksuser"]) if (env["platform"]=="freedesktop"): env["enable_vst2"]=False # not supported env.Append(CXXFLAGS=["-DFREEDESKTOP_ENABLED"]) + if (env["use_pulseaudio"]): + env.Append(CXXFLAGS=["-D__LINUX_PULSE__"]) + env.ParseConfig("pkg-config libpulse --libs --cflags") + env.ParseConfig("pkg-config libpulse-simple --libs --cflags") + if (env["use_alsa"]): + env.Append(CXXFLAGS=["-D__LINUX_ALSA__"]) + env.ParseConfig("pkg-config alsa --libs --cflags") + if (env["use_jack"]): + env.Append(CXXFLAGS=["-D__LINUX_JACK__"]) + env.ParseConfig("pkg-config jack --libs --cflags") def add_sources(self, sources, filetype, lib_env = None, shared = False): import glob; diff --git a/bin/zytrax.cpp b/bin/zytrax.cpp index 64326c0..0261ec8 100644 --- a/bin/zytrax.cpp +++ b/bin/zytrax.cpp @@ -13,6 +13,9 @@ #include "drivers/rtaudio/sound_driver_rtaudio.h" #endif +#ifdef RTMIDI_ENABLED +#include "drivers/rtmidi/midi_driver_rtmidi.h" +#endif int main(int argc, char *argv[]) { AudioEffectFactory effect_factory; @@ -26,6 +29,9 @@ int main(int argc, char *argv[]) { register_rtaudio_driver(); #endif +#ifdef RTMIDI_ENABLED + register_rtmidi_driver(); +#endif auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base"); Theme theme; @@ -35,19 +41,21 @@ int main(int argc, char *argv[]) { { String path = SettingsDialog::get_settings_path() + "/settings.json"; JSON::Node node; - int use_driver_index = 0; + int use_driver_index = SoundDriverManager::get_driver_count() ? 0 : -1; + int use_midi_in_driver_index = MIDIDriverManager::get_input_driver_count() ? 0 : -1; if (load_json(path, node) == OK) { if (node.has("audio")) { //audio JSON::Node audio_node = node.get("audio"); std::string driver_id = audio_node.get("id").toString(); - int use_driver_index = -1; + for (int i = 0; i < SoundDriverManager::get_driver_count(); i++) { SoundDriver *driver = SoundDriverManager::get_driver(i); if (driver->get_id() == driver_id.c_str()) { + use_driver_index = i; } - use_driver_index = i; + break; } @@ -65,6 +73,17 @@ int main(int argc, char *argv[]) { if (block_size >= 0 && block_size < SoundDriverManager::BUFFER_SIZE_MAX) { SoundDriverManager::set_step_buffer_size(SoundDriverManager::BufferSize(block_size)); } + + std::string midi_driver_id = audio_node.get("midi_in_id").toString(); + + for (int i = 0; i < MIDIDriverManager::get_input_driver_count(); i++) { + MIDIInputDriver *driver = MIDIDriverManager::get_input_driver(i); + if (driver->get_id() == midi_driver_id.c_str()) { + use_midi_in_driver_index = i; + } + + break; + } } if (node.has("plugins")) { //plugins @@ -157,6 +176,7 @@ int main(int argc, char *argv[]) { } } SoundDriverManager::init_driver(use_driver_index); + MIDIDriverManager::init_input_driver(use_midi_in_driver_index); } register_effects(&effect_factory); diff --git a/drivers/SCsub b/drivers/SCsub index 6708408..cf27141 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -9,4 +9,8 @@ if (env["enable_rtaudio"]): env.add_sources(targets,"rtaudio/*.cpp") env.add_sources(targets,"rtaudio/rtaudio/*.cpp") +if (env["enable_rtmidi"]): + env.add_sources(targets,"rtmidi/*.cpp") + env.add_sources(targets,"rtmidi/rtmidi/*.cpp") + env.libs+=env.Library('drivers', targets); diff --git a/drivers/rtmidi/midi_driver_rtmidi.cpp b/drivers/rtmidi/midi_driver_rtmidi.cpp new file mode 100644 index 0000000..fe63f57 --- /dev/null +++ b/drivers/rtmidi/midi_driver_rtmidi.cpp @@ -0,0 +1,86 @@ +#include "midi_driver_rtmidi.h" + +#include "engine/midi_driver_manager.h" +#include "globals/vector.h" +// +#include "drivers/rtmidi/rtmidi/RtMidi.h" + +static RtMidiIn *midiin = NULL; + +class MIDIInputDriverRtMidi : public MIDIInputDriver { +public: + static void midi_callback(double deltatime, std::vector *message, void *userData) { + MIDIInputDriverRtMidi *driver = (MIDIInputDriverRtMidi *)userData; + MIDIEvent ev; + if (ev.parse(&(*message)[0]) == OK) { + driver->event(deltatime, ev); + } + } + + virtual void lock() { + } + virtual void unlock() { + } + + String name; + String id; + int index; + bool active; + + virtual String get_name() const { + return name; + } + virtual String get_id() const { + return id; + } + + virtual bool is_active() { + return active; + } + virtual bool init() { + ERR_FAIL_COND_V(active, false); + midiin->openPort(index); + // Set our callback function. This should be done immediately after + // opening the port to avoid having incoming messages written to the + // queue. + midiin->setCallback(&midi_callback, this); + // Don't ignore sysex, timing, or active sensing messages. + midiin->ignoreTypes(false, false, false); + active = true; + } + virtual void finish() { + midiin->closePort(); + active = false; + } + + MIDIInputDriverRtMidi() { + index = -1; + active = false; + } + ~MIDIInputDriverRtMidi() { + } +}; + +static Vector midi_drivers; + +void register_rtmidi_driver() { + + midiin = new RtMidiIn(); + + unsigned int nPorts = midiin->getPortCount(); + for (int i = 0; i < nPorts; i++) { + MIDIInputDriverRtMidi *driver = new MIDIInputDriverRtMidi; + driver->name.parse_utf8(midiin->getPortName(i).c_str()); + driver->id = "RtMidi:" + driver->name; + driver->index = i; + midi_drivers.push_back(driver); + MIDIDriverManager::add_input_driver(driver); + } +} + +void unregister_rtmidi_driver() { + for (int i = 0; i < midi_drivers.size(); i++) { + delete midi_drivers[i]; + } + delete midiin; +} diff --git a/drivers/rtmidi/midi_driver_rtmidi.h b/drivers/rtmidi/midi_driver_rtmidi.h new file mode 100644 index 0000000..9799141 --- /dev/null +++ b/drivers/rtmidi/midi_driver_rtmidi.h @@ -0,0 +1,9 @@ +#ifndef MIDI_DRIVER_RTMIDI_H +#define MIDI_DRIVER_RTMIDI_H + +// + +void register_rtmidi_driver(); +void unregister_rtmidi_driver(); + +#endif // MIDI_DRIVER_RTMIDI_H diff --git a/drivers/rtmidi/rtmidi/RtMidi.cpp b/drivers/rtmidi/rtmidi/RtMidi.cpp new file mode 100644 index 0000000..e2b4d6b --- /dev/null +++ b/drivers/rtmidi/rtmidi/RtMidi.cpp @@ -0,0 +1,3319 @@ +/**********************************************************************/ +/*! \class RtMidi + \brief An abstract base class for realtime MIDI input/output. + + This class implements some common functionality for the realtime + MIDI input/output subclasses RtMidiIn and RtMidiOut. + + RtMidi GitHub site: https://github.com/thestk/rtmidi + RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ + + RtMidi: realtime MIDI i/o C++ classes + Copyright (c) 2003-2019 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/**********************************************************************/ + +#include "RtMidi.h" +#include + +#if defined(__MACOSX_CORE__) +#if TARGET_OS_IPHONE +#define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime +#define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos +#endif +#endif + +// Default for Windows is to add an identifier to the port names; this +// flag can be defined (e.g. in your project file) to disable this behaviour. +//#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES + +// **************************************************************** // +// +// MidiInApi and MidiOutApi subclass prototypes. +// +// **************************************************************** // + +#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) +#define __RTMIDI_DUMMY__ +#endif + +#if defined(__MACOSX_CORE__) + +class MidiInCore : public MidiInApi { +public: + MidiInCore(const std::string &clientName, unsigned int queueSizeLimit); + ~MidiInCore(void); + RtMidi::Api getCurrentApi(void) { return RtMidi::MACOSX_CORE; }; + void openPort(unsigned int portNumber, const std::string &portName); + void openVirtualPort(const std::string &portName); + void closePort(void); + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + unsigned int getPortCount(void); + std::string getPortName(unsigned int portNumber); + +protected: + void initialize(const std::string &clientName); +}; + +class MidiOutCore : public MidiOutApi { +public: + MidiOutCore(const std::string &clientName); + ~MidiOutCore(void); + RtMidi::Api getCurrentApi(void) { return RtMidi::MACOSX_CORE; }; + void openPort(unsigned int portNumber, const std::string &portName); + void openVirtualPort(const std::string &portName); + void closePort(void); + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + unsigned int getPortCount(void); + std::string getPortName(unsigned int portNumber); + void sendMessage(const unsigned char *message, size_t size); + +protected: + void initialize(const std::string &clientName); +}; + +#endif + +#if defined(__UNIX_JACK__) + +class MidiInJack : public MidiInApi { +public: + MidiInJack(const std::string &clientName, unsigned int queueSizeLimit); + ~MidiInJack(void); + RtMidi::Api getCurrentApi(void) { return RtMidi::UNIX_JACK; }; + void openPort(unsigned int portNumber, const std::string &portName); + void openVirtualPort(const std::string &portName); + void closePort(void); + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + unsigned int getPortCount(void); + std::string getPortName(unsigned int portNumber); + +protected: + std::string clientName; + + void connect(void); + void initialize(const std::string &clientName); +}; + +class MidiOutJack : public MidiOutApi { +public: + MidiOutJack(const std::string &clientName); + ~MidiOutJack(void); + RtMidi::Api getCurrentApi(void) { return RtMidi::UNIX_JACK; }; + void openPort(unsigned int portNumber, const std::string &portName); + void openVirtualPort(const std::string &portName); + void closePort(void); + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + unsigned int getPortCount(void); + std::string getPortName(unsigned int portNumber); + void sendMessage(const unsigned char *message, size_t size); + +protected: + std::string clientName; + + void connect(void); + void initialize(const std::string &clientName); +}; + +#endif + +#if defined(__LINUX_ALSA__) + +class MidiInAlsa : public MidiInApi { +public: + MidiInAlsa(const std::string &clientName, unsigned int queueSizeLimit); + ~MidiInAlsa(void); + RtMidi::Api getCurrentApi(void) { return RtMidi::LINUX_ALSA; }; + void openPort(unsigned int portNumber, const std::string &portName); + void openVirtualPort(const std::string &portName); + void closePort(void); + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + unsigned int getPortCount(void); + std::string getPortName(unsigned int portNumber); + +protected: + void initialize(const std::string &clientName); +}; + +class MidiOutAlsa : public MidiOutApi { +public: + MidiOutAlsa(const std::string &clientName); + ~MidiOutAlsa(void); + RtMidi::Api getCurrentApi(void) { return RtMidi::LINUX_ALSA; }; + void openPort(unsigned int portNumber, const std::string &portName); + void openVirtualPort(const std::string &portName); + void closePort(void); + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + unsigned int getPortCount(void); + std::string getPortName(unsigned int portNumber); + void sendMessage(const unsigned char *message, size_t size); + +protected: + void initialize(const std::string &clientName); +}; + +#endif + +#if defined(__WINDOWS_MM__) + +class MidiInWinMM : public MidiInApi { +public: + MidiInWinMM(const std::string &clientName, unsigned int queueSizeLimit); + ~MidiInWinMM(void); + RtMidi::Api getCurrentApi(void) { return RtMidi::WINDOWS_MM; }; + void openPort(unsigned int portNumber, const std::string &portName); + void openVirtualPort(const std::string &portName); + void closePort(void); + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + unsigned int getPortCount(void); + std::string getPortName(unsigned int portNumber); + +protected: + void initialize(const std::string &clientName); +}; + +class MidiOutWinMM : public MidiOutApi { +public: + MidiOutWinMM(const std::string &clientName); + ~MidiOutWinMM(void); + RtMidi::Api getCurrentApi(void) { return RtMidi::WINDOWS_MM; }; + void openPort(unsigned int portNumber, const std::string &portName); + void openVirtualPort(const std::string &portName); + void closePort(void); + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + unsigned int getPortCount(void); + std::string getPortName(unsigned int portNumber); + void sendMessage(const unsigned char *message, size_t size); + +protected: + void initialize(const std::string &clientName); +}; + +#endif + +#if defined(__RTMIDI_DUMMY__) + +class MidiInDummy : public MidiInApi { +public: + MidiInDummy(const std::string & /*clientName*/, unsigned int queueSizeLimit) : + MidiInApi(queueSizeLimit) { + errorString_ = "MidiInDummy: This class provides no functionality."; + error(RtMidiError::WARNING, errorString_); + } + RtMidi::Api getCurrentApi(void) { return RtMidi::RTMIDI_DUMMY; } + void openPort(unsigned int /*portNumber*/, const std::string & /*portName*/) {} + void openVirtualPort(const std::string & /*portName*/) {} + void closePort(void) {} + void setClientName(const std::string & /*clientName*/){}; + void setPortName(const std::string & /*portName*/){}; + unsigned int getPortCount(void) { return 0; } + std::string getPortName(unsigned int /*portNumber*/) { return ""; } + +protected: + void initialize(const std::string & /*clientName*/) {} +}; + +class MidiOutDummy : public MidiOutApi { +public: + MidiOutDummy(const std::string & /*clientName*/) { + errorString_ = "MidiOutDummy: This class provides no functionality."; + error(RtMidiError::WARNING, errorString_); + } + RtMidi::Api getCurrentApi(void) { return RtMidi::RTMIDI_DUMMY; } + void openPort(unsigned int /*portNumber*/, const std::string & /*portName*/) {} + void openVirtualPort(const std::string & /*portName*/) {} + void closePort(void) {} + void setClientName(const std::string & /*clientName*/){}; + void setPortName(const std::string & /*portName*/){}; + unsigned int getPortCount(void) { return 0; } + std::string getPortName(unsigned int /*portNumber*/) { return ""; } + void sendMessage(const unsigned char * /*message*/, size_t /*size*/) {} + +protected: + void initialize(const std::string & /*clientName*/) {} +}; + +#endif + +//*********************************************************************// +// RtMidi Definitions +//*********************************************************************// + +RtMidi ::RtMidi() : + rtapi_(0) { +} + +RtMidi ::~RtMidi() { + delete rtapi_; + rtapi_ = 0; +} + +std::string RtMidi ::getVersion(void) throw() { + return std::string(RTMIDI_VERSION); +} + +// Define API names and display names. +// Must be in same order as API enum. +extern "C" { +const char *rtmidi_api_names[][2] = { + { "unspecified", "Unknown" }, + { "core", "CoreMidi" }, + { "alsa", "ALSA" }, + { "jack", "Jack" }, + { "winmm", "Windows MultiMedia" }, + { "dummy", "Dummy" }, +}; +const unsigned int rtmidi_num_api_names = + sizeof(rtmidi_api_names) / sizeof(rtmidi_api_names[0]); + +// The order here will control the order of RtMidi's API search in +// the constructor. +extern "C" const RtMidi::Api rtmidi_compiled_apis[] = { +#if defined(__MACOSX_CORE__) + RtMidi::MACOSX_CORE, +#endif +#if defined(__LINUX_ALSA__) + RtMidi::LINUX_ALSA, +#endif +#if defined(__UNIX_JACK__) + RtMidi::UNIX_JACK, +#endif +#if defined(__WINDOWS_MM__) + RtMidi::WINDOWS_MM, +#endif +#if defined(__RTMIDI_DUMMY__) + RtMidi::RTMIDI_DUMMY, +#endif + RtMidi::UNSPECIFIED, +}; +extern "C" const unsigned int rtmidi_num_compiled_apis = + sizeof(rtmidi_compiled_apis) / sizeof(rtmidi_compiled_apis[0]) - 1; +} + +// This is a compile-time check that rtmidi_num_api_names == RtMidi::NUM_APIS. +// If the build breaks here, check that they match. +template +class StaticAssert { +private: + StaticAssert() {} +}; +template <> +class StaticAssert { +public: + StaticAssert() {} +}; +class StaticAssertions { + StaticAssertions() { + StaticAssert(); + } +}; + +void RtMidi ::getCompiledApi(std::vector &apis) throw() { + apis = std::vector(rtmidi_compiled_apis, + rtmidi_compiled_apis + rtmidi_num_compiled_apis); +} + +std::string RtMidi ::getApiName(RtMidi::Api api) { + if (api < 0 || api >= RtMidi::NUM_APIS) + return ""; + return rtmidi_api_names[api][0]; +} + +std::string RtMidi ::getApiDisplayName(RtMidi::Api api) { + if (api < 0 || api >= RtMidi::NUM_APIS) + return "Unknown"; + return rtmidi_api_names[api][1]; +} + +RtMidi::Api RtMidi ::getCompiledApiByName(const std::string &name) { + unsigned int i = 0; + for (i = 0; i < rtmidi_num_compiled_apis; ++i) + if (name == rtmidi_api_names[rtmidi_compiled_apis[i]][0]) + return rtmidi_compiled_apis[i]; + return RtMidi::UNSPECIFIED; +} + +void RtMidi ::setClientName(const std::string &clientName) { + rtapi_->setClientName(clientName); +} + +void RtMidi ::setPortName(const std::string &portName) { + rtapi_->setPortName(portName); +} + +//*********************************************************************// +// RtMidiIn Definitions +//*********************************************************************// + +void RtMidiIn ::openMidiApi(RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit) { + delete rtapi_; + rtapi_ = 0; + +#if defined(__UNIX_JACK__) + if (api == UNIX_JACK) + rtapi_ = new MidiInJack(clientName, queueSizeLimit); +#endif +#if defined(__LINUX_ALSA__) + if (api == LINUX_ALSA) + rtapi_ = new MidiInAlsa(clientName, queueSizeLimit); +#endif +#if defined(__WINDOWS_MM__) + if (api == WINDOWS_MM) + rtapi_ = new MidiInWinMM(clientName, queueSizeLimit); +#endif +#if defined(__MACOSX_CORE__) + if (api == MACOSX_CORE) + rtapi_ = new MidiInCore(clientName, queueSizeLimit); +#endif +#if defined(__RTMIDI_DUMMY__) + if (api == RTMIDI_DUMMY) + rtapi_ = new MidiInDummy(clientName, queueSizeLimit); +#endif +} + +RTMIDI_DLL_PUBLIC RtMidiIn ::RtMidiIn(RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit) : + RtMidi() { + if (api != UNSPECIFIED) { + // Attempt to open the specified API. + openMidiApi(api, clientName, queueSizeLimit); + if (rtapi_) return; + + // No compiled support for specified API value. Issue a warning + // and continue as if no API was specified. + std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" + << std::endl; + } + + // Iterate through the compiled APIs and return as soon as we find + // one with at least one port or we reach the end of the list. + std::vector apis; + getCompiledApi(apis); + for (unsigned int i = 0; i < apis.size(); i++) { + openMidiApi(apis[i], clientName, queueSizeLimit); + if (rtapi_ && rtapi_->getPortCount()) break; + } + + if (rtapi_) return; + + // It should not be possible to get here because the preprocessor + // definition __RTMIDI_DUMMY__ is automatically defined if no + // API-specific definitions are passed to the compiler. But just in + // case something weird happens, we'll throw an error. + std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; + throw(RtMidiError(errorText, RtMidiError::UNSPECIFIED)); +} + +RtMidiIn ::~RtMidiIn() throw() { +} + +//*********************************************************************// +// RtMidiOut Definitions +//*********************************************************************// + +void RtMidiOut ::openMidiApi(RtMidi::Api api, const std::string &clientName) { + delete rtapi_; + rtapi_ = 0; + +#if defined(__UNIX_JACK__) + if (api == UNIX_JACK) + rtapi_ = new MidiOutJack(clientName); +#endif +#if defined(__LINUX_ALSA__) + if (api == LINUX_ALSA) + rtapi_ = new MidiOutAlsa(clientName); +#endif +#if defined(__WINDOWS_MM__) + if (api == WINDOWS_MM) + rtapi_ = new MidiOutWinMM(clientName); +#endif +#if defined(__MACOSX_CORE__) + if (api == MACOSX_CORE) + rtapi_ = new MidiOutCore(clientName); +#endif +#if defined(__RTMIDI_DUMMY__) + if (api == RTMIDI_DUMMY) + rtapi_ = new MidiOutDummy(clientName); +#endif +} + +RTMIDI_DLL_PUBLIC RtMidiOut ::RtMidiOut(RtMidi::Api api, const std::string &clientName) { + if (api != UNSPECIFIED) { + // Attempt to open the specified API. + openMidiApi(api, clientName); + if (rtapi_) return; + + // No compiled support for specified API value. Issue a warning + // and continue as if no API was specified. + std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" + << std::endl; + } + + // Iterate through the compiled APIs and return as soon as we find + // one with at least one port or we reach the end of the list. + std::vector apis; + getCompiledApi(apis); + for (unsigned int i = 0; i < apis.size(); i++) { + openMidiApi(apis[i], clientName); + if (rtapi_ && rtapi_->getPortCount()) break; + } + + if (rtapi_) return; + + // It should not be possible to get here because the preprocessor + // definition __RTMIDI_DUMMY__ is automatically defined if no + // API-specific definitions are passed to the compiler. But just in + // case something weird happens, we'll thrown an error. + std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; + throw(RtMidiError(errorText, RtMidiError::UNSPECIFIED)); +} + +RtMidiOut ::~RtMidiOut() throw() { +} + +//*********************************************************************// +// Common MidiApi Definitions +//*********************************************************************// + +MidiApi ::MidiApi(void) : + apiData_(0), + connected_(false), + errorCallback_(0), + firstErrorOccurred_(false), + errorCallbackUserData_(0) { +} + +MidiApi ::~MidiApi(void) { +} + +void MidiApi ::setErrorCallback(RtMidiErrorCallback errorCallback, void *userData = 0) { + errorCallback_ = errorCallback; + errorCallbackUserData_ = userData; +} + +void MidiApi ::error(RtMidiError::Type type, std::string errorString) { + if (errorCallback_) { + + if (firstErrorOccurred_) + return; + + firstErrorOccurred_ = true; + const std::string errorMessage = errorString; + + errorCallback_(type, errorMessage, errorCallbackUserData_); + firstErrorOccurred_ = false; + return; + } + + if (type == RtMidiError::WARNING) { + std::cerr << '\n' + << errorString << "\n\n"; + } else if (type == RtMidiError::DEBUG_WARNING) { +#if defined(__RTMIDI_DEBUG__) + std::cerr << '\n' + << errorString << "\n\n"; +#endif + } else { + std::cerr << '\n' + << errorString << "\n\n"; + throw RtMidiError(errorString, type); + } +} + +//*********************************************************************// +// Common MidiInApi Definitions +//*********************************************************************// + +MidiInApi ::MidiInApi(unsigned int queueSizeLimit) : + MidiApi() { + // Allocate the MIDI queue. + inputData_.queue.ringSize = queueSizeLimit; + if (inputData_.queue.ringSize > 0) + inputData_.queue.ring = new MidiMessage[inputData_.queue.ringSize]; +} + +MidiInApi ::~MidiInApi(void) { + // Delete the MIDI queue. + if (inputData_.queue.ringSize > 0) delete[] inputData_.queue.ring; +} + +void MidiInApi ::setCallback(RtMidiIn::RtMidiCallback callback, void *userData) { + if (inputData_.usingCallback) { + errorString_ = "MidiInApi::setCallback: a callback function is already set!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + if (!callback) { + errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + inputData_.userCallback = callback; + inputData_.userData = userData; + inputData_.usingCallback = true; +} + +void MidiInApi ::cancelCallback() { + if (!inputData_.usingCallback) { + errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + inputData_.userCallback = 0; + inputData_.userData = 0; + inputData_.usingCallback = false; +} + +void MidiInApi ::ignoreTypes(bool midiSysex, bool midiTime, bool midiSense) { + inputData_.ignoreFlags = 0; + if (midiSysex) inputData_.ignoreFlags = 0x01; + if (midiTime) inputData_.ignoreFlags |= 0x02; + if (midiSense) inputData_.ignoreFlags |= 0x04; +} + +double MidiInApi ::getMessage(std::vector *message) { + message->clear(); + + if (inputData_.usingCallback) { + errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; + error(RtMidiError::WARNING, errorString_); + return 0.0; + } + + double timeStamp; + if (!inputData_.queue.pop(message, &timeStamp)) + return 0.0; + + return timeStamp; +} + +unsigned int MidiInApi::MidiQueue::size(unsigned int *__back, + unsigned int *__front) { + // Access back/front members exactly once and make stack copies for + // size calculation + unsigned int _back = back, _front = front, _size; + if (_back >= _front) + _size = _back - _front; + else + _size = ringSize - _front + _back; + + // Return copies of back/front so no new and unsynchronized accesses + // to member variables are needed. + if (__back) *__back = _back; + if (__front) *__front = _front; + return _size; +} + +// As long as we haven't reached our queue size limit, push the message. +bool MidiInApi::MidiQueue::push(const MidiInApi::MidiMessage &msg) { + // Local stack copies of front/back + unsigned int _back, _front, _size; + + // Get back/front indexes exactly once and calculate current size + _size = size(&_back, &_front); + + if (_size < ringSize - 1) { + ring[_back] = msg; + back = (back + 1) % ringSize; + return true; + } + + return false; +} + +bool MidiInApi::MidiQueue::pop(std::vector *msg, double *timeStamp) { + // Local stack copies of front/back + unsigned int _back, _front, _size; + + // Get back/front indexes exactly once and calculate current size + _size = size(&_back, &_front); + + if (_size == 0) + return false; + + // Copy queued message to the vector pointer argument and then "pop" it. + msg->assign(ring[_front].bytes.begin(), ring[_front].bytes.end()); + *timeStamp = ring[_front].timeStamp; + + // Update front + front = (front + 1) % ringSize; + return true; +} + +//*********************************************************************// +// Common MidiOutApi Definitions +//*********************************************************************// + +MidiOutApi ::MidiOutApi(void) : + MidiApi() { +} + +MidiOutApi ::~MidiOutApi(void) { +} + +// *************************************************** // +// +// OS/API-specific methods. +// +// *************************************************** // + +#if defined(__MACOSX_CORE__) + +// The CoreMIDI API is based on the use of a callback function for +// MIDI input. We convert the system specific time stamps to delta +// time values. + +// OS-X CoreMIDI header files. +#include +#include +#include + +// A structure to hold variables related to the CoreMIDI API +// implementation. +struct CoreMidiData { + MIDIClientRef client; + MIDIPortRef port; + MIDIEndpointRef endpoint; + MIDIEndpointRef destinationId; + unsigned long long lastTime; + MIDISysexSendRequest sysexreq; +}; + +//*********************************************************************// +// API: OS-X +// Class Definitions: MidiInCore +//*********************************************************************// + +static void midiInputCallback(const MIDIPacketList *list, void *procRef, void * /*srcRef*/) { + MidiInApi::RtMidiInData *data = static_cast(procRef); + CoreMidiData *apiData = static_cast(data->apiData); + + unsigned char status; + unsigned short nBytes, iByte, size; + unsigned long long time; + + bool &continueSysex = data->continueSysex; + MidiInApi::MidiMessage &message = data->message; + + const MIDIPacket *packet = &list->packet[0]; + for (unsigned int i = 0; i < list->numPackets; ++i) { + + // My interpretation of the CoreMIDI documentation: all message + // types, except sysex, are complete within a packet and there may + // be several of them in a single packet. Sysex messages can be + // broken across multiple packets and PacketLists but are bundled + // alone within each packet (these packets do not contain other + // message types). If sysex messages are split across multiple + // MIDIPacketLists, they must be handled by multiple calls to this + // function. + + nBytes = packet->length; + if (nBytes == 0) { + packet = MIDIPacketNext(packet); + continue; + } + + // Calculate time stamp. + if (data->firstMessage) { + message.timeStamp = 0.0; + data->firstMessage = false; + } else { + time = packet->timeStamp; + if (time == 0) { // this happens when receiving asynchronous sysex messages + time = AudioGetCurrentHostTime(); + } + time -= apiData->lastTime; + time = AudioConvertHostTimeToNanos(time); + if (!continueSysex) + message.timeStamp = time * 0.000000001; + } + + // Track whether any non-filtered messages were found in this + // packet for timestamp calculation + bool foundNonFiltered = false; + + iByte = 0; + if (continueSysex) { + // We have a continuing, segmented sysex message. + if (!(data->ignoreFlags & 0x01)) { + // If we're not ignoring sysex messages, copy the entire packet. + for (unsigned int j = 0; j < nBytes; ++j) + message.bytes.push_back(packet->data[j]); + } + continueSysex = packet->data[nBytes - 1] != 0xF7; + + if (!(data->ignoreFlags & 0x01) && !continueSysex) { + // If not a continuing sysex message, invoke the user callback function or queue the message. + if (data->usingCallback) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback)data->userCallback; + callback(message.timeStamp, &message.bytes, data->userData); + } else { + // As long as we haven't reached our queue size limit, push the message. + if (!data->queue.push(message)) + std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; + } + message.bytes.clear(); + } + } else { + while (iByte < nBytes) { + size = 0; + // We are expecting that the next byte in the packet is a status byte. + status = packet->data[iByte]; + if (!(status & 0x80)) break; + // Determine the number of bytes in the MIDI message. + if (status < 0xC0) + size = 3; + else if (status < 0xE0) + size = 2; + else if (status < 0xF0) + size = 3; + else if (status == 0xF0) { + // A MIDI sysex + if (data->ignoreFlags & 0x01) { + size = 0; + iByte = nBytes; + } else + size = nBytes - iByte; + continueSysex = packet->data[nBytes - 1] != 0xF7; + } else if (status == 0xF1) { + // A MIDI time code message + if (data->ignoreFlags & 0x02) { + size = 0; + iByte += 2; + } else + size = 2; + } else if (status == 0xF2) + size = 3; + else if (status == 0xF3) + size = 2; + else if (status == 0xF8 && (data->ignoreFlags & 0x02)) { + // A MIDI timing tick message and we're ignoring it. + size = 0; + iByte += 1; + } else if (status == 0xFE && (data->ignoreFlags & 0x04)) { + // A MIDI active sensing message and we're ignoring it. + size = 0; + iByte += 1; + } else + size = 1; + + // Copy the MIDI data to our vector. + if (size) { + foundNonFiltered = true; + message.bytes.assign(&packet->data[iByte], &packet->data[iByte + size]); + if (!continueSysex) { + // If not a continuing sysex message, invoke the user callback function or queue the message. + if (data->usingCallback) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback)data->userCallback; + callback(message.timeStamp, &message.bytes, data->userData); + } else { + // As long as we haven't reached our queue size limit, push the message. + if (!data->queue.push(message)) + std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; + } + message.bytes.clear(); + } + iByte += size; + } + } + } + + // Save the time of the last non-filtered message + if (foundNonFiltered) { + apiData->lastTime = packet->timeStamp; + if (apiData->lastTime == 0) { // this happens when receiving asynchronous sysex messages + apiData->lastTime = AudioGetCurrentHostTime(); + } + } + + packet = MIDIPacketNext(packet); + } +} + +MidiInCore ::MidiInCore(const std::string &clientName, unsigned int queueSizeLimit) : + MidiInApi(queueSizeLimit) { + MidiInCore::initialize(clientName); +} + +MidiInCore ::~MidiInCore(void) { + // Close a connection if it exists. + MidiInCore::closePort(); + + // Cleanup. + CoreMidiData *data = static_cast(apiData_); + MIDIClientDispose(data->client); + if (data->endpoint) MIDIEndpointDispose(data->endpoint); + delete data; +} + +void MidiInCore ::initialize(const std::string &clientName) { + // Set up our client. + MIDIClientRef client; + CFStringRef name = CFStringCreateWithCString(NULL, clientName.c_str(), kCFStringEncodingASCII); + OSStatus result = MIDIClientCreate(name, NULL, NULL, &client); + if (result != noErr) { + std::ostringstream ost; + ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; + errorString_ = ost.str(); + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Save our api-specific connection information. + CoreMidiData *data = (CoreMidiData *)new CoreMidiData; + data->client = client; + data->endpoint = 0; + apiData_ = (void *)data; + inputData_.apiData = (void *)data; + CFRelease(name); +} + +void MidiInCore ::openPort(unsigned int portNumber, const std::string &portName) { + if (connected_) { + errorString_ = "MidiInCore::openPort: a valid connection already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); + unsigned int nSrc = MIDIGetNumberOfSources(); + if (nSrc < 1) { + errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; + error(RtMidiError::NO_DEVICES_FOUND, errorString_); + return; + } + + if (portNumber >= nSrc) { + std::ostringstream ost; + ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::INVALID_PARAMETER, errorString_); + return; + } + + MIDIPortRef port; + CoreMidiData *data = static_cast(apiData_); + CFStringRef portNameRef = CFStringCreateWithCString(NULL, portName.c_str(), kCFStringEncodingASCII); + OSStatus result = MIDIInputPortCreate(data->client, + portNameRef, + midiInputCallback, (void *)&inputData_, &port); + CFRelease(portNameRef); + + if (result != noErr) { + MIDIClientDispose(data->client); + errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Get the desired input source identifier. + MIDIEndpointRef endpoint = MIDIGetSource(portNumber); + if (endpoint == 0) { + MIDIPortDispose(port); + MIDIClientDispose(data->client); + errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Make the connection. + result = MIDIPortConnectSource(port, endpoint, NULL); + if (result != noErr) { + MIDIPortDispose(port); + MIDIClientDispose(data->client); + errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Save our api-specific port information. + data->port = port; + + connected_ = true; +} + +void MidiInCore ::openVirtualPort(const std::string &portName) { + CoreMidiData *data = static_cast(apiData_); + + // Create a virtual MIDI input destination. + MIDIEndpointRef endpoint; + CFStringRef portNameRef = CFStringCreateWithCString(NULL, portName.c_str(), kCFStringEncodingASCII); + OSStatus result = MIDIDestinationCreate(data->client, + portNameRef, + midiInputCallback, (void *)&inputData_, &endpoint); + CFRelease(portNameRef); + + if (result != noErr) { + errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Save our api-specific connection information. + data->endpoint = endpoint; +} + +void MidiInCore ::closePort(void) { + CoreMidiData *data = static_cast(apiData_); + + if (data->endpoint) { + MIDIEndpointDispose(data->endpoint); + data->endpoint = 0; + } + + if (data->port) { + MIDIPortDispose(data->port); + data->port = 0; + } + + connected_ = false; +} + +void MidiInCore ::setClientName(const std::string &) { + + errorString_ = "MidiInCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiInCore ::setPortName(const std::string &) { + + errorString_ = "MidiInCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; + error(RtMidiError::WARNING, errorString_); +} + +unsigned int MidiInCore ::getPortCount() { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); + return MIDIGetNumberOfSources(); +} + +// This function was submitted by Douglas Casey Tucker and apparently +// derived largely from PortMidi. +CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) { + CFMutableStringRef result = CFStringCreateMutable(NULL, 0); + CFStringRef str; + + // Begin with the endpoint's name. + str = NULL; + MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str); + if (str != NULL) { + CFStringAppend(result, str); + CFRelease(str); + } + + MIDIEntityRef entity = 0; + MIDIEndpointGetEntity(endpoint, &entity); + if (entity == 0) + // probably virtual + return result; + + if (CFStringGetLength(result) == 0) { + // endpoint name has zero length -- try the entity + str = NULL; + MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str); + if (str != NULL) { + CFStringAppend(result, str); + CFRelease(str); + } + } + // now consider the device's name + MIDIDeviceRef device = 0; + MIDIEntityGetDevice(entity, &device); + if (device == 0) + return result; + + str = NULL; + MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str); + if (CFStringGetLength(result) == 0) { + CFRelease(result); + return str; + } + if (str != NULL) { + // if an external device has only one entity, throw away + // the endpoint name and just use the device name + if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) { + CFRelease(result); + return str; + } else { + if (CFStringGetLength(str) == 0) { + CFRelease(str); + return result; + } + // does the entity name already start with the device name? + // (some drivers do this though they shouldn't) + // if so, do not prepend + if (CFStringCompareWithOptions(result, /* endpoint name */ + str /* device name */, + CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) { + // prepend the device name to the entity name + if (CFStringGetLength(result) > 0) + CFStringInsert(result, 0, CFSTR(" ")); + + CFStringInsert(result, 0, str); + } + CFRelease(str); + } + } + return result; +} + +// This function was submitted by Douglas Casey Tucker and apparently +// derived largely from PortMidi. +static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint) { + CFMutableStringRef result = CFStringCreateMutable(NULL, 0); + CFStringRef str; + OSStatus err; + int i; + + // Does the endpoint have connections? + CFDataRef connections = NULL; + int nConnected = 0; + bool anyStrings = false; + err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections); + if (connections != NULL) { + // It has connections, follow them + // Concatenate the names of all connected devices + nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID); + if (nConnected) { + const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); + for (i = 0; i < nConnected; ++i, ++pid) { + MIDIUniqueID id = EndianS32_BtoN(*pid); + MIDIObjectRef connObject; + MIDIObjectType connObjectType; + err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); + if (err == noErr) { + if (connObjectType == kMIDIObjectType_ExternalSource || + connObjectType == kMIDIObjectType_ExternalDestination) { + // Connected to an external device's endpoint (10.3 and later). + str = EndpointName((MIDIEndpointRef)(connObject), true); + } else { + // Connected to an external device (10.2) (or something else, catch- + str = NULL; + MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str); + } + if (str != NULL) { + if (anyStrings) + CFStringAppend(result, CFSTR(", ")); + else + anyStrings = true; + CFStringAppend(result, str); + CFRelease(str); + } + } + } + } + CFRelease(connections); + } + if (anyStrings) + return result; + + CFRelease(result); + + // Here, either the endpoint had no connections, or we failed to obtain names + return EndpointName(endpoint, false); +} + +std::string MidiInCore ::getPortName(unsigned int portNumber) { + CFStringRef nameRef; + MIDIEndpointRef portRef; + char name[128]; + + std::string stringName; + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); + if (portNumber >= MIDIGetNumberOfSources()) { + std::ostringstream ost; + ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::WARNING, errorString_); + return stringName; + } + + portRef = MIDIGetSource(portNumber); + nameRef = ConnectedEndpointName(portRef); + CFStringGetCString(nameRef, name, sizeof(name), kCFStringEncodingUTF8); + CFRelease(nameRef); + + return stringName = name; +} + +//*********************************************************************// +// API: OS-X +// Class Definitions: MidiOutCore +//*********************************************************************// + +MidiOutCore ::MidiOutCore(const std::string &clientName) : + MidiOutApi() { + MidiOutCore::initialize(clientName); +} + +MidiOutCore ::~MidiOutCore(void) { + // Close a connection if it exists. + MidiOutCore::closePort(); + + // Cleanup. + CoreMidiData *data = static_cast(apiData_); + MIDIClientDispose(data->client); + if (data->endpoint) MIDIEndpointDispose(data->endpoint); + delete data; +} + +void MidiOutCore ::initialize(const std::string &clientName) { + // Set up our client. + MIDIClientRef client; + CFStringRef name = CFStringCreateWithCString(NULL, clientName.c_str(), kCFStringEncodingASCII); + OSStatus result = MIDIClientCreate(name, NULL, NULL, &client); + if (result != noErr) { + std::ostringstream ost; + ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; + errorString_ = ost.str(); + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Save our api-specific connection information. + CoreMidiData *data = (CoreMidiData *)new CoreMidiData; + data->client = client; + data->endpoint = 0; + apiData_ = (void *)data; + CFRelease(name); +} + +unsigned int MidiOutCore ::getPortCount() { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); + return MIDIGetNumberOfDestinations(); +} + +std::string MidiOutCore ::getPortName(unsigned int portNumber) { + CFStringRef nameRef; + MIDIEndpointRef portRef; + char name[128]; + + std::string stringName; + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); + if (portNumber >= MIDIGetNumberOfDestinations()) { + std::ostringstream ost; + ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::WARNING, errorString_); + return stringName; + } + + portRef = MIDIGetDestination(portNumber); + nameRef = ConnectedEndpointName(portRef); + CFStringGetCString(nameRef, name, sizeof(name), kCFStringEncodingUTF8); + CFRelease(nameRef); + + return stringName = name; +} + +void MidiOutCore ::openPort(unsigned int portNumber, const std::string &portName) { + if (connected_) { + errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); + unsigned int nDest = MIDIGetNumberOfDestinations(); + if (nDest < 1) { + errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; + error(RtMidiError::NO_DEVICES_FOUND, errorString_); + return; + } + + if (portNumber >= nDest) { + std::ostringstream ost; + ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::INVALID_PARAMETER, errorString_); + return; + } + + MIDIPortRef port; + CoreMidiData *data = static_cast(apiData_); + CFStringRef portNameRef = CFStringCreateWithCString(NULL, portName.c_str(), kCFStringEncodingASCII); + OSStatus result = MIDIOutputPortCreate(data->client, portNameRef, &port); + CFRelease(portNameRef); + if (result != noErr) { + MIDIClientDispose(data->client); + errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Get the desired output port identifier. + MIDIEndpointRef destination = MIDIGetDestination(portNumber); + if (destination == 0) { + MIDIPortDispose(port); + MIDIClientDispose(data->client); + errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Save our api-specific connection information. + data->port = port; + data->destinationId = destination; + connected_ = true; +} + +void MidiOutCore ::closePort(void) { + CoreMidiData *data = static_cast(apiData_); + + if (data->endpoint) { + MIDIEndpointDispose(data->endpoint); + data->endpoint = 0; + } + + if (data->port) { + MIDIPortDispose(data->port); + data->port = 0; + } + + connected_ = false; +} + +void MidiOutCore ::setClientName(const std::string &) { + + errorString_ = "MidiOutCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutCore ::setPortName(const std::string &) { + + errorString_ = "MidiOutCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutCore ::openVirtualPort(const std::string &portName) { + CoreMidiData *data = static_cast(apiData_); + + if (data->endpoint) { + errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + // Create a virtual MIDI output source. + MIDIEndpointRef endpoint; + CFStringRef portNameRef = CFStringCreateWithCString(NULL, portName.c_str(), kCFStringEncodingASCII); + OSStatus result = MIDISourceCreate(data->client, portNameRef, &endpoint); + CFRelease(portNameRef); + + if (result != noErr) { + errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Save our api-specific connection information. + data->endpoint = endpoint; +} + +void MidiOutCore ::sendMessage(const unsigned char *message, size_t size) { + // We use the MIDISendSysex() function to asynchronously send sysex + // messages. Otherwise, we use a single CoreMidi MIDIPacket. + unsigned int nBytes = static_cast(size); + if (nBytes == 0) { + errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); + CoreMidiData *data = static_cast(apiData_); + OSStatus result; + + if (message[0] != 0xF0 && nBytes > 3) { + errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; + error(RtMidiError::WARNING, errorString_); + return; + } + + Byte buffer[nBytes + (sizeof(MIDIPacketList))]; + ByteCount listSize = sizeof(buffer); + MIDIPacketList *packetList = (MIDIPacketList *)buffer; + MIDIPacket *packet = MIDIPacketListInit(packetList); + + ByteCount remainingBytes = nBytes; + while (remainingBytes && packet) { + ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket + const Byte *dataStartPtr = (const Byte *)&message[nBytes - remainingBytes]; + packet = MIDIPacketListAdd(packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr); + remainingBytes -= bytesForPacket; + } + + if (!packet) { + errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Send to any destinations that may have connected to us. + if (data->endpoint) { + result = MIDIReceived(data->endpoint, packetList); + if (result != noErr) { + errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; + error(RtMidiError::WARNING, errorString_); + } + } + + // And send to an explicit destination port if we're connected. + if (connected_) { + result = MIDISend(data->port, data->destinationId, packetList); + if (result != noErr) { + errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; + error(RtMidiError::WARNING, errorString_); + } + } +} + +#endif // __MACOSX_CORE__ + +//*********************************************************************// +// API: LINUX ALSA SEQUENCER +//*********************************************************************// + +// API information found at: +// - http://www.alsa-project.org/documentation.php#Library + +#if defined(__LINUX_ALSA__) + +// The ALSA Sequencer API is based on the use of a callback function for +// MIDI input. +// +// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer +// time stamps and other assorted fixes!!! + +// If you don't need timestamping for incoming MIDI events, define the +// preprocessor definition AVOID_TIMESTAMPING to save resources +// associated with the ALSA sequencer queues. + +#include +#include + +// ALSA header file. +#include + +// A structure to hold variables related to the ALSA API +// implementation. +struct AlsaMidiData { + snd_seq_t *seq; + unsigned int portNum; + int vport; + snd_seq_port_subscribe_t *subscription; + snd_midi_event_t *coder; + unsigned int bufferSize; + unsigned char *buffer; + pthread_t thread; + pthread_t dummy_thread_id; + snd_seq_real_time_t lastTime; + int queue_id; // an input queue is needed to get timestamped events + int trigger_fds[2]; +}; + +#define PORT_TYPE(pinfo, bits) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) + +//*********************************************************************// +// API: LINUX ALSA +// Class Definitions: MidiInAlsa +//*********************************************************************// + +static void *alsaMidiHandler(void *ptr) { + MidiInApi::RtMidiInData *data = static_cast(ptr); + AlsaMidiData *apiData = static_cast(data->apiData); + + long nBytes; + double time; + bool continueSysex = false; + bool doDecode = false; + MidiInApi::MidiMessage message; + int poll_fd_count; + struct pollfd *poll_fds; + + snd_seq_event_t *ev; + int result; + apiData->bufferSize = 32; + result = snd_midi_event_new(0, &apiData->coder); + if (result < 0) { + data->doInput = false; + std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n"; + return 0; + } + unsigned char *buffer = (unsigned char *)malloc(apiData->bufferSize); + if (buffer == NULL) { + data->doInput = false; + snd_midi_event_free(apiData->coder); + apiData->coder = 0; + std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n"; + return 0; + } + snd_midi_event_init(apiData->coder); + snd_midi_event_no_status(apiData->coder, 1); // suppress running status messages + + poll_fd_count = snd_seq_poll_descriptors_count(apiData->seq, POLLIN) + 1; + poll_fds = (struct pollfd *)alloca(poll_fd_count * sizeof(struct pollfd)); + snd_seq_poll_descriptors(apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN); + poll_fds[0].fd = apiData->trigger_fds[0]; + poll_fds[0].events = POLLIN; + + while (data->doInput) { + + if (snd_seq_event_input_pending(apiData->seq, 1) == 0) { + // No data pending + if (poll(poll_fds, poll_fd_count, -1) >= 0) { + if (poll_fds[0].revents & POLLIN) { + bool dummy; + int res = read(poll_fds[0].fd, &dummy, sizeof(dummy)); + (void)res; + } + } + continue; + } + + // If here, there should be data. + result = snd_seq_event_input(apiData->seq, &ev); + if (result == -ENOSPC) { + std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n"; + continue; + } else if (result <= 0) { + std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; + perror("System reports"); + continue; + } + + // This is a bit weird, but we now have to decode an ALSA MIDI + // event (back) into MIDI bytes. We'll ignore non-MIDI types. + if (!continueSysex) message.bytes.clear(); + + doDecode = false; + switch (ev->type) { + + case SND_SEQ_EVENT_PORT_SUBSCRIBED: +#if defined(__RTMIDI_DEBUG__) + std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; +#endif + break; + + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: +#if defined(__RTMIDI_DEBUG__) + std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; + std::cout << "sender = " << (int)ev->data.connect.sender.client << ":" + << (int)ev->data.connect.sender.port + << ", dest = " << (int)ev->data.connect.dest.client << ":" + << (int)ev->data.connect.dest.port + << std::endl; +#endif + break; + + case SND_SEQ_EVENT_QFRAME: // MIDI time code + if (!(data->ignoreFlags & 0x02)) doDecode = true; + break; + + case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick + if (!(data->ignoreFlags & 0x02)) doDecode = true; + break; + + case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick + if (!(data->ignoreFlags & 0x02)) doDecode = true; + break; + + case SND_SEQ_EVENT_SENSING: // Active sensing + if (!(data->ignoreFlags & 0x04)) doDecode = true; + break; + + case SND_SEQ_EVENT_SYSEX: + if ((data->ignoreFlags & 0x01)) break; + if (ev->data.ext.len > apiData->bufferSize) { + apiData->bufferSize = ev->data.ext.len; + free(buffer); + buffer = (unsigned char *)malloc(apiData->bufferSize); + if (buffer == NULL) { + data->doInput = false; + std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n"; + break; + } + } + doDecode = true; + break; + + default: + doDecode = true; + } + + if (doDecode) { + + nBytes = snd_midi_event_decode(apiData->coder, buffer, apiData->bufferSize, ev); + if (nBytes > 0) { + // The ALSA sequencer has a maximum buffer size for MIDI sysex + // events of 256 bytes. If a device sends sysex messages larger + // than this, they are segmented into 256 byte chunks. So, + // we'll watch for this and concatenate sysex chunks into a + // single sysex message if necessary. + if (!continueSysex) + message.bytes.assign(buffer, &buffer[nBytes]); + else + message.bytes.insert(message.bytes.end(), buffer, &buffer[nBytes]); + + continueSysex = ((ev->type == SND_SEQ_EVENT_SYSEX) && (message.bytes.back() != 0xF7)); + if (!continueSysex) { + + // Calculate the time stamp: + message.timeStamp = 0.0; + + // Method 1: Use the system time. + //(void)gettimeofday(&tv, (struct timezone *)NULL); + //time = (tv.tv_sec * 1000000) + tv.tv_usec; + + // Method 2: Use the ALSA sequencer event time data. + // (thanks to Pedro Lopez-Cabanillas!). + + // Using method from: + // https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html + + // Perform the carry for the later subtraction by updating y. + // Temp var y is timespec because computation requires signed types, + // while snd_seq_real_time_t has unsigned types. + snd_seq_real_time_t &x(ev->time.time); + struct timespec y; + y.tv_nsec = apiData->lastTime.tv_nsec; + y.tv_sec = apiData->lastTime.tv_sec; + if (x.tv_nsec < y.tv_nsec) { + int nsec = (y.tv_nsec - (int)x.tv_nsec) / 1000000000 + 1; + y.tv_nsec -= 1000000000 * nsec; + y.tv_sec += nsec; + } + if (x.tv_nsec - y.tv_nsec > 1000000000) { + int nsec = ((int)x.tv_nsec - y.tv_nsec) / 1000000000; + y.tv_nsec += 1000000000 * nsec; + y.tv_sec -= nsec; + } + + // Compute the time difference. + time = (int)x.tv_sec - y.tv_sec + ((int)x.tv_nsec - y.tv_nsec) * 1e-9; + + apiData->lastTime = ev->time.time; + + if (data->firstMessage == true) + data->firstMessage = false; + else + message.timeStamp = time; + } else { +#if defined(__RTMIDI_DEBUG__) + std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; +#endif + } + } + } + + snd_seq_free_event(ev); + if (message.bytes.size() == 0 || continueSysex) continue; + + if (data->usingCallback) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback)data->userCallback; + callback(message.timeStamp, &message.bytes, data->userData); + } else { + // As long as we haven't reached our queue size limit, push the message. + if (!data->queue.push(message)) + std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; + } + } + + if (buffer) free(buffer); + snd_midi_event_free(apiData->coder); + apiData->coder = 0; + apiData->thread = apiData->dummy_thread_id; + return 0; +} + +MidiInAlsa ::MidiInAlsa(const std::string &clientName, unsigned int queueSizeLimit) : + MidiInApi(queueSizeLimit) { + MidiInAlsa::initialize(clientName); +} + +MidiInAlsa ::~MidiInAlsa() { + // Close a connection if it exists. + MidiInAlsa::closePort(); + + // Shutdown the input thread. + AlsaMidiData *data = static_cast(apiData_); + if (inputData_.doInput) { + inputData_.doInput = false; + int res = write(data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput)); + (void)res; + if (!pthread_equal(data->thread, data->dummy_thread_id)) + pthread_join(data->thread, NULL); + } + + // Cleanup. + close(data->trigger_fds[0]); + close(data->trigger_fds[1]); + if (data->vport >= 0) snd_seq_delete_port(data->seq, data->vport); +#ifndef AVOID_TIMESTAMPING + snd_seq_free_queue(data->seq, data->queue_id); +#endif + snd_seq_close(data->seq); + delete data; +} + +void MidiInAlsa ::initialize(const std::string &clientName) { + // Set up the ALSA sequencer client. + snd_seq_t *seq; + int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); + if (result < 0) { + errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Set client name. + snd_seq_set_client_name(seq, clientName.c_str()); + + // Save our api-specific connection information. + AlsaMidiData *data = (AlsaMidiData *)new AlsaMidiData; + data->seq = seq; + data->portNum = -1; + data->vport = -1; + data->subscription = 0; + data->dummy_thread_id = pthread_self(); + data->thread = data->dummy_thread_id; + data->trigger_fds[0] = -1; + data->trigger_fds[1] = -1; + apiData_ = (void *)data; + inputData_.apiData = (void *)data; + + if (pipe(data->trigger_fds) == -1) { + errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Create the input queue +#ifndef AVOID_TIMESTAMPING + data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue"); + // Set arbitrary tempo (mm=100) and resolution (240) + snd_seq_queue_tempo_t *qtempo; + snd_seq_queue_tempo_alloca(&qtempo); + snd_seq_queue_tempo_set_tempo(qtempo, 600000); + snd_seq_queue_tempo_set_ppq(qtempo, 240); + snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo); + snd_seq_drain_output(data->seq); +#endif +} + +// This function is used to count or get the pinfo structure for a given port number. +unsigned int portInfo(snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber) { + snd_seq_client_info_t *cinfo; + int client; + int count = 0; + snd_seq_client_info_alloca(&cinfo); + + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(seq, cinfo) >= 0) { + client = snd_seq_client_info_get_client(cinfo); + if (client == 0) continue; + // Reset query info + snd_seq_port_info_set_client(pinfo, client); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(seq, pinfo) >= 0) { + unsigned int atyp = snd_seq_port_info_get_type(pinfo); + if (((atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC) == 0) && + ((atyp & SND_SEQ_PORT_TYPE_SYNTH) == 0) && + ((atyp & SND_SEQ_PORT_TYPE_APPLICATION) == 0)) continue; + + unsigned int caps = snd_seq_port_info_get_capability(pinfo); + if ((caps & type) != type) continue; + if (count == portNumber) return 1; + ++count; + } + } + + // If a negative portNumber was used, return the port count. + if (portNumber < 0) return count; + return 0; +} + +unsigned int MidiInAlsa ::getPortCount() { + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca(&pinfo); + + AlsaMidiData *data = static_cast(apiData_); + return portInfo(data->seq, pinfo, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, -1); +} + +std::string MidiInAlsa ::getPortName(unsigned int portNumber) { + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_alloca(&pinfo); + + std::string stringName; + AlsaMidiData *data = static_cast(apiData_); + if (portInfo(data->seq, pinfo, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, (int)portNumber)) { + int cnum = snd_seq_port_info_get_client(pinfo); + snd_seq_get_any_client_info(data->seq, cnum, cinfo); + std::ostringstream os; + os << snd_seq_client_info_get_name(cinfo); + os << ":"; + os << snd_seq_port_info_get_name(pinfo); + os << " "; // These lines added to make sure devices are listed + os << snd_seq_port_info_get_client(pinfo); // with full portnames added to ensure individual device names + os << ":"; + os << snd_seq_port_info_get_port(pinfo); + stringName = os.str(); + return stringName; + } + + // If we get here, we didn't find a match. + errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; + error(RtMidiError::WARNING, errorString_); + return stringName; +} + +void MidiInAlsa ::openPort(unsigned int portNumber, const std::string &portName) { + if (connected_) { + errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + unsigned int nSrc = this->getPortCount(); + if (nSrc < 1) { + errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; + error(RtMidiError::NO_DEVICES_FOUND, errorString_); + return; + } + + snd_seq_port_info_t *src_pinfo; + snd_seq_port_info_alloca(&src_pinfo); + AlsaMidiData *data = static_cast(apiData_); + if (portInfo(data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, (int)portNumber) == 0) { + std::ostringstream ost; + ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::INVALID_PARAMETER, errorString_); + return; + } + + snd_seq_addr_t sender, receiver; + sender.client = snd_seq_port_info_get_client(src_pinfo); + sender.port = snd_seq_port_info_get_port(src_pinfo); + receiver.client = snd_seq_client_id(data->seq); + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca(&pinfo); + if (data->vport < 0) { + snd_seq_port_info_set_client(pinfo, 0); + snd_seq_port_info_set_port(pinfo, 0); + snd_seq_port_info_set_capability(pinfo, + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE); + snd_seq_port_info_set_type(pinfo, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_midi_channels(pinfo, 16); +#ifndef AVOID_TIMESTAMPING + snd_seq_port_info_set_timestamping(pinfo, 1); + snd_seq_port_info_set_timestamp_real(pinfo, 1); + snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); +#endif + snd_seq_port_info_set_name(pinfo, portName.c_str()); + data->vport = snd_seq_create_port(data->seq, pinfo); + + if (data->vport < 0) { + errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + data->vport = snd_seq_port_info_get_port(pinfo); + } + + receiver.port = data->vport; + + if (!data->subscription) { + // Make subscription + if (snd_seq_port_subscribe_malloc(&data->subscription) < 0) { + errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + snd_seq_port_subscribe_set_sender(data->subscription, &sender); + snd_seq_port_subscribe_set_dest(data->subscription, &receiver); + if (snd_seq_subscribe_port(data->seq, data->subscription)) { + snd_seq_port_subscribe_free(data->subscription); + data->subscription = 0; + errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + } + + if (inputData_.doInput == false) { + // Start the input queue +#ifndef AVOID_TIMESTAMPING + snd_seq_start_queue(data->seq, data->queue_id, NULL); + snd_seq_drain_output(data->seq); +#endif + // Start our MIDI input thread. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_attr_setschedpolicy(&attr, SCHED_OTHER); + + inputData_.doInput = true; + int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); + pthread_attr_destroy(&attr); + if (err) { + snd_seq_unsubscribe_port(data->seq, data->subscription); + snd_seq_port_subscribe_free(data->subscription); + data->subscription = 0; + inputData_.doInput = false; + errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; + error(RtMidiError::THREAD_ERROR, errorString_); + return; + } + } + + connected_ = true; +} + +void MidiInAlsa ::openVirtualPort(const std::string &portName) { + AlsaMidiData *data = static_cast(apiData_); + if (data->vport < 0) { + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_capability(pinfo, + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE); + snd_seq_port_info_set_type(pinfo, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_midi_channels(pinfo, 16); +#ifndef AVOID_TIMESTAMPING + snd_seq_port_info_set_timestamping(pinfo, 1); + snd_seq_port_info_set_timestamp_real(pinfo, 1); + snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); +#endif + snd_seq_port_info_set_name(pinfo, portName.c_str()); + data->vport = snd_seq_create_port(data->seq, pinfo); + + if (data->vport < 0) { + errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + data->vport = snd_seq_port_info_get_port(pinfo); + } + + if (inputData_.doInput == false) { + // Wait for old thread to stop, if still running + if (!pthread_equal(data->thread, data->dummy_thread_id)) + pthread_join(data->thread, NULL); + + // Start the input queue +#ifndef AVOID_TIMESTAMPING + snd_seq_start_queue(data->seq, data->queue_id, NULL); + snd_seq_drain_output(data->seq); +#endif + // Start our MIDI input thread. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_attr_setschedpolicy(&attr, SCHED_OTHER); + + inputData_.doInput = true; + int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); + pthread_attr_destroy(&attr); + if (err) { + if (data->subscription) { + snd_seq_unsubscribe_port(data->seq, data->subscription); + snd_seq_port_subscribe_free(data->subscription); + data->subscription = 0; + } + inputData_.doInput = false; + errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; + error(RtMidiError::THREAD_ERROR, errorString_); + return; + } + } +} + +void MidiInAlsa ::closePort(void) { + AlsaMidiData *data = static_cast(apiData_); + + if (connected_) { + if (data->subscription) { + snd_seq_unsubscribe_port(data->seq, data->subscription); + snd_seq_port_subscribe_free(data->subscription); + data->subscription = 0; + } + // Stop the input queue +#ifndef AVOID_TIMESTAMPING + snd_seq_stop_queue(data->seq, data->queue_id, NULL); + snd_seq_drain_output(data->seq); +#endif + connected_ = false; + } + + // Stop thread to avoid triggering the callback, while the port is intended to be closed + if (inputData_.doInput) { + inputData_.doInput = false; + int res = write(data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput)); + (void)res; + if (!pthread_equal(data->thread, data->dummy_thread_id)) + pthread_join(data->thread, NULL); + } +} + +void MidiInAlsa ::setClientName(const std::string &clientName) { + + AlsaMidiData *data = static_cast(apiData_); + snd_seq_set_client_name(data->seq, clientName.c_str()); +} + +void MidiInAlsa ::setPortName(const std::string &portName) { + AlsaMidiData *data = static_cast(apiData_); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca(&pinfo); + snd_seq_get_port_info(data->seq, data->vport, pinfo); + snd_seq_port_info_set_name(pinfo, portName.c_str()); + snd_seq_set_port_info(data->seq, data->vport, pinfo); +} + +//*********************************************************************// +// API: LINUX ALSA +// Class Definitions: MidiOutAlsa +//*********************************************************************// + +MidiOutAlsa ::MidiOutAlsa(const std::string &clientName) : + MidiOutApi() { + MidiOutAlsa::initialize(clientName); +} + +MidiOutAlsa ::~MidiOutAlsa() { + // Close a connection if it exists. + MidiOutAlsa::closePort(); + + // Cleanup. + AlsaMidiData *data = static_cast(apiData_); + if (data->vport >= 0) snd_seq_delete_port(data->seq, data->vport); + if (data->coder) snd_midi_event_free(data->coder); + if (data->buffer) free(data->buffer); + snd_seq_close(data->seq); + delete data; +} + +void MidiOutAlsa ::initialize(const std::string &clientName) { + // Set up the ALSA sequencer client. + snd_seq_t *seq; + int result1 = snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK); + if (result1 < 0) { + errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Set client name. + snd_seq_set_client_name(seq, clientName.c_str()); + + // Save our api-specific connection information. + AlsaMidiData *data = (AlsaMidiData *)new AlsaMidiData; + data->seq = seq; + data->portNum = -1; + data->vport = -1; + data->bufferSize = 32; + data->coder = 0; + data->buffer = 0; + int result = snd_midi_event_new(data->bufferSize, &data->coder); + if (result < 0) { + delete data; + errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + data->buffer = (unsigned char *)malloc(data->bufferSize); + if (data->buffer == NULL) { + delete data; + errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; + error(RtMidiError::MEMORY_ERROR, errorString_); + return; + } + snd_midi_event_init(data->coder); + apiData_ = (void *)data; +} + +unsigned int MidiOutAlsa ::getPortCount() { + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca(&pinfo); + + AlsaMidiData *data = static_cast(apiData_); + return portInfo(data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, -1); +} + +std::string MidiOutAlsa ::getPortName(unsigned int portNumber) { + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_alloca(&pinfo); + + std::string stringName; + AlsaMidiData *data = static_cast(apiData_); + if (portInfo(data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, (int)portNumber)) { + int cnum = snd_seq_port_info_get_client(pinfo); + snd_seq_get_any_client_info(data->seq, cnum, cinfo); + std::ostringstream os; + os << snd_seq_client_info_get_name(cinfo); + os << ":"; + os << snd_seq_port_info_get_name(pinfo); + os << " "; // These lines added to make sure devices are listed + os << snd_seq_port_info_get_client(pinfo); // with full portnames added to ensure individual device names + os << ":"; + os << snd_seq_port_info_get_port(pinfo); + stringName = os.str(); + return stringName; + } + + // If we get here, we didn't find a match. + errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; + error(RtMidiError::WARNING, errorString_); + return stringName; +} + +void MidiOutAlsa ::openPort(unsigned int portNumber, const std::string &portName) { + if (connected_) { + errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + unsigned int nSrc = this->getPortCount(); + if (nSrc < 1) { + errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; + error(RtMidiError::NO_DEVICES_FOUND, errorString_); + return; + } + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca(&pinfo); + AlsaMidiData *data = static_cast(apiData_); + if (portInfo(data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, (int)portNumber) == 0) { + std::ostringstream ost; + ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::INVALID_PARAMETER, errorString_); + return; + } + + snd_seq_addr_t sender, receiver; + receiver.client = snd_seq_port_info_get_client(pinfo); + receiver.port = snd_seq_port_info_get_port(pinfo); + sender.client = snd_seq_client_id(data->seq); + + if (data->vport < 0) { + data->vport = snd_seq_create_simple_port(data->seq, portName.c_str(), + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + if (data->vport < 0) { + errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + } + + sender.port = data->vport; + + // Make subscription + if (snd_seq_port_subscribe_malloc(&data->subscription) < 0) { + snd_seq_port_subscribe_free(data->subscription); + errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + snd_seq_port_subscribe_set_sender(data->subscription, &sender); + snd_seq_port_subscribe_set_dest(data->subscription, &receiver); + snd_seq_port_subscribe_set_time_update(data->subscription, 1); + snd_seq_port_subscribe_set_time_real(data->subscription, 1); + if (snd_seq_subscribe_port(data->seq, data->subscription)) { + snd_seq_port_subscribe_free(data->subscription); + errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + connected_ = true; +} + +void MidiOutAlsa ::closePort(void) { + if (connected_) { + AlsaMidiData *data = static_cast(apiData_); + snd_seq_unsubscribe_port(data->seq, data->subscription); + snd_seq_port_subscribe_free(data->subscription); + data->subscription = 0; + connected_ = false; + } +} + +void MidiOutAlsa ::setClientName(const std::string &clientName) { + + AlsaMidiData *data = static_cast(apiData_); + snd_seq_set_client_name(data->seq, clientName.c_str()); +} + +void MidiOutAlsa ::setPortName(const std::string &portName) { + AlsaMidiData *data = static_cast(apiData_); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca(&pinfo); + snd_seq_get_port_info(data->seq, data->vport, pinfo); + snd_seq_port_info_set_name(pinfo, portName.c_str()); + snd_seq_set_port_info(data->seq, data->vport, pinfo); +} + +void MidiOutAlsa ::openVirtualPort(const std::string &portName) { + AlsaMidiData *data = static_cast(apiData_); + if (data->vport < 0) { + data->vport = snd_seq_create_simple_port(data->seq, portName.c_str(), + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + + if (data->vport < 0) { + errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + } + } +} + +void MidiOutAlsa ::sendMessage(const unsigned char *message, size_t size) { + int result; + AlsaMidiData *data = static_cast(apiData_); + unsigned int nBytes = static_cast(size); + if (nBytes > data->bufferSize) { + data->bufferSize = nBytes; + result = snd_midi_event_resize_buffer(data->coder, nBytes); + if (result != 0) { + errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + free(data->buffer); + data->buffer = (unsigned char *)malloc(data->bufferSize); + if (data->buffer == NULL) { + errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; + error(RtMidiError::MEMORY_ERROR, errorString_); + return; + } + } + + snd_seq_event_t ev; + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, data->vport); + snd_seq_ev_set_subs(&ev); + snd_seq_ev_set_direct(&ev); + for (unsigned int i = 0; i < nBytes; ++i) + data->buffer[i] = message[i]; + result = snd_midi_event_encode(data->coder, data->buffer, (long)nBytes, &ev); + if (result < (int)nBytes) { + errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + // Send the event. + result = snd_seq_event_output(data->seq, &ev); + if (result < 0) { + errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; + error(RtMidiError::WARNING, errorString_); + return; + } + snd_seq_drain_output(data->seq); +} + +#endif // __LINUX_ALSA__ + +//*********************************************************************// +// API: Windows Multimedia Library (MM) +//*********************************************************************// + +// API information deciphered from: +// - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp + +// Thanks to Jean-Baptiste Berruchon for the sysex code. + +#if defined(__WINDOWS_MM__) + +// The Windows MM API is based on the use of a callback function for +// MIDI input. We convert the system specific time stamps to delta +// time values. + +// Windows MM MIDI header files. +#include +// +#include + +// Convert a null-terminated wide string or ANSI-encoded string to UTF-8. +static std::string ConvertToUTF8(const TCHAR *str) { + std::string u8str; + const WCHAR *wstr = L""; +#if defined(UNICODE) || defined(_UNICODE) + wstr = str; +#else + // Convert from ANSI encoding to wide string + int wlength = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); + std::wstring wstrtemp; + if (wlength) { + wstrtemp.assign(wlength - 1, 0); + MultiByteToWideChar(CP_ACP, 0, str, -1, &wstrtemp[0], wlength); + wstr = &wstrtemp[0]; + } +#endif + // Convert from wide string to UTF-8 + int length = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + if (length) { + u8str.assign(length - 1, 0); + length = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &u8str[0], length, NULL, NULL); + } + return u8str; +} + +#define RT_SYSEX_BUFFER_SIZE 1024 +#define RT_SYSEX_BUFFER_COUNT 4 + +// A structure to hold variables related to the CoreMIDI API +// implementation. +struct WinMidiData { + HMIDIIN inHandle; // Handle to Midi Input Device + HMIDIOUT outHandle; // Handle to Midi Output Device + DWORD lastTime; + MidiInApi::MidiMessage message; + LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT]; + CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo +}; + +//*********************************************************************// +// API: Windows MM +// Class Definitions: MidiInWinMM +//*********************************************************************// + +static void CALLBACK midiInputCallback(HMIDIIN /*hmin*/, + UINT inputStatus, + DWORD_PTR instancePtr, + DWORD_PTR midiMessage, + DWORD timestamp) { + if (inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR) return; + + //MidiInApi::RtMidiInData *data = static_cast (instancePtr); + MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; + WinMidiData *apiData = static_cast(data->apiData); + + // Calculate time stamp. + if (data->firstMessage == true) { + apiData->message.timeStamp = 0.0; + data->firstMessage = false; + } else + apiData->message.timeStamp = (double)(timestamp - apiData->lastTime) * 0.001; + + if (inputStatus == MIM_DATA) { // Channel or system message + + // Make sure the first byte is a status byte. + unsigned char status = (unsigned char)(midiMessage & 0x000000FF); + if (!(status & 0x80)) return; + + // Determine the number of bytes in the MIDI message. + unsigned short nBytes = 1; + if (status < 0xC0) + nBytes = 3; + else if (status < 0xE0) + nBytes = 2; + else if (status < 0xF0) + nBytes = 3; + else if (status == 0xF1) { + if (data->ignoreFlags & 0x02) + return; + else + nBytes = 2; + } else if (status == 0xF2) + nBytes = 3; + else if (status == 0xF3) + nBytes = 2; + else if (status == 0xF8 && (data->ignoreFlags & 0x02)) { + // A MIDI timing tick message and we're ignoring it. + return; + } else if (status == 0xFE && (data->ignoreFlags & 0x04)) { + // A MIDI active sensing message and we're ignoring it. + return; + } + + // Copy bytes to our MIDI message. + unsigned char *ptr = (unsigned char *)&midiMessage; + for (int i = 0; i < nBytes; ++i) + apiData->message.bytes.push_back(*ptr++); + } else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) + MIDIHDR *sysex = (MIDIHDR *)midiMessage; + if (!(data->ignoreFlags & 0x01) && inputStatus != MIM_LONGERROR) { + // Sysex message and we're not ignoring it + for (int i = 0; i < (int)sysex->dwBytesRecorded; ++i) + apiData->message.bytes.push_back(sysex->lpData[i]); + } + + // The WinMM API requires that the sysex buffer be requeued after + // input of each sysex message. Even if we are ignoring sysex + // messages, we still need to requeue the buffer in case the user + // decides to not ignore sysex messages in the future. However, + // it seems that WinMM calls this function with an empty sysex + // buffer when an application closes and in this case, we should + // avoid requeueing it, else the computer suddenly reboots after + // one or two minutes. + if (apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0) { + //if ( sysex->dwBytesRecorded > 0 ) { + EnterCriticalSection(&(apiData->_mutex)); + MMRESULT result = midiInAddBuffer(apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR)); + LeaveCriticalSection(&(apiData->_mutex)); + if (result != MMSYSERR_NOERROR) + std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; + + if (data->ignoreFlags & 0x01) return; + } else + return; + } + + // Save the time of the last non-filtered message + apiData->lastTime = timestamp; + + if (data->usingCallback) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback)data->userCallback; + callback(apiData->message.timeStamp, &apiData->message.bytes, data->userData); + } else { + // As long as we haven't reached our queue size limit, push the message. + if (!data->queue.push(apiData->message)) + std::cerr << "\nMidiInWinMM: message queue limit reached!!\n\n"; + } + + // Clear the vector for the next input message. + apiData->message.bytes.clear(); +} + +MidiInWinMM ::MidiInWinMM(const std::string &clientName, unsigned int queueSizeLimit) : + MidiInApi(queueSizeLimit) { + MidiInWinMM::initialize(clientName); +} + +MidiInWinMM ::~MidiInWinMM() { + // Close a connection if it exists. + MidiInWinMM::closePort(); + + WinMidiData *data = static_cast(apiData_); + DeleteCriticalSection(&(data->_mutex)); + + // Cleanup. + delete data; +} + +void MidiInWinMM ::initialize(const std::string & /*clientName*/) { + // We'll issue a warning here if no devices are available but not + // throw an error since the user can plugin something later. + unsigned int nDevices = midiInGetNumDevs(); + if (nDevices == 0) { + errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; + error(RtMidiError::WARNING, errorString_); + } + + // Save our api-specific connection information. + WinMidiData *data = (WinMidiData *)new WinMidiData; + apiData_ = (void *)data; + inputData_.apiData = (void *)data; + data->message.bytes.clear(); // needs to be empty for first input message + + if (!InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400)) { + errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; + error(RtMidiError::WARNING, errorString_); + } +} + +void MidiInWinMM ::openPort(unsigned int portNumber, const std::string & /*portName*/) { + if (connected_) { + errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + unsigned int nDevices = midiInGetNumDevs(); + if (nDevices == 0) { + errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; + error(RtMidiError::NO_DEVICES_FOUND, errorString_); + return; + } + + if (portNumber >= nDevices) { + std::ostringstream ost; + ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::INVALID_PARAMETER, errorString_); + return; + } + + WinMidiData *data = static_cast(apiData_); + MMRESULT result = midiInOpen(&data->inHandle, + portNumber, + (DWORD_PTR)&midiInputCallback, + (DWORD_PTR)&inputData_, + CALLBACK_FUNCTION); + if (result != MMSYSERR_NOERROR) { + errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Allocate and init the sysex buffers. + for (int i = 0; i < RT_SYSEX_BUFFER_COUNT; ++i) { + data->sysexBuffer[i] = (MIDIHDR *)new char[sizeof(MIDIHDR)]; + data->sysexBuffer[i]->lpData = new char[RT_SYSEX_BUFFER_SIZE]; + data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE; + data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator + data->sysexBuffer[i]->dwFlags = 0; + + result = midiInPrepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + midiInClose(data->inHandle); + data->inHandle = 0; + errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Register the buffer. + result = midiInAddBuffer(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + midiInClose(data->inHandle); + data->inHandle = 0; + errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + } + + result = midiInStart(data->inHandle); + if (result != MMSYSERR_NOERROR) { + midiInClose(data->inHandle); + data->inHandle = 0; + errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + connected_ = true; +} + +void MidiInWinMM ::openVirtualPort(const std::string & /*portName*/) { + // This function cannot be implemented for the Windows MM MIDI API. + errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiInWinMM ::closePort(void) { + if (connected_) { + WinMidiData *data = static_cast(apiData_); + EnterCriticalSection(&(data->_mutex)); + midiInReset(data->inHandle); + midiInStop(data->inHandle); + + for (int i = 0; i < RT_SYSEX_BUFFER_COUNT; ++i) { + int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); + delete[] data->sysexBuffer[i]->lpData; + delete[] data->sysexBuffer[i]; + if (result != MMSYSERR_NOERROR) { + midiInClose(data->inHandle); + data->inHandle = 0; + errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + } + + midiInClose(data->inHandle); + data->inHandle = 0; + connected_ = false; + LeaveCriticalSection(&(data->_mutex)); + } +} + +void MidiInWinMM ::setClientName(const std::string &) { + + errorString_ = "MidiInWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiInWinMM ::setPortName(const std::string &) { + + errorString_ = "MidiInWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; + error(RtMidiError::WARNING, errorString_); +} + +unsigned int MidiInWinMM ::getPortCount() { + return midiInGetNumDevs(); +} + +std::string MidiInWinMM ::getPortName(unsigned int portNumber) { + std::string stringName; + unsigned int nDevices = midiInGetNumDevs(); + if (portNumber >= nDevices) { + std::ostringstream ost; + ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::WARNING, errorString_); + return stringName; + } + + MIDIINCAPS deviceCaps; + midiInGetDevCaps(portNumber, &deviceCaps, sizeof(MIDIINCAPS)); + stringName = ConvertToUTF8(deviceCaps.szPname); + + // Next lines added to add the portNumber to the name so that + // the device's names are sure to be listed with individual names + // even when they have the same brand name +#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES + std::ostringstream os; + os << " "; + os << portNumber; + stringName += os.str(); +#endif + + return stringName; +} + +//*********************************************************************// +// API: Windows MM +// Class Definitions: MidiOutWinMM +//*********************************************************************// + +MidiOutWinMM ::MidiOutWinMM(const std::string &clientName) : + MidiOutApi() { + MidiOutWinMM::initialize(clientName); +} + +MidiOutWinMM ::~MidiOutWinMM() { + // Close a connection if it exists. + MidiOutWinMM::closePort(); + + // Cleanup. + WinMidiData *data = static_cast(apiData_); + delete data; +} + +void MidiOutWinMM ::initialize(const std::string & /*clientName*/) { + // We'll issue a warning here if no devices are available but not + // throw an error since the user can plug something in later. + unsigned int nDevices = midiOutGetNumDevs(); + if (nDevices == 0) { + errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; + error(RtMidiError::WARNING, errorString_); + } + + // Save our api-specific connection information. + WinMidiData *data = (WinMidiData *)new WinMidiData; + apiData_ = (void *)data; +} + +unsigned int MidiOutWinMM ::getPortCount() { + return midiOutGetNumDevs(); +} + +std::string MidiOutWinMM ::getPortName(unsigned int portNumber) { + std::string stringName; + unsigned int nDevices = midiOutGetNumDevs(); + if (portNumber >= nDevices) { + std::ostringstream ost; + ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::WARNING, errorString_); + return stringName; + } + + MIDIOUTCAPS deviceCaps; + midiOutGetDevCaps(portNumber, &deviceCaps, sizeof(MIDIOUTCAPS)); + stringName = ConvertToUTF8(deviceCaps.szPname); + + // Next lines added to add the portNumber to the name so that + // the device's names are sure to be listed with individual names + // even when they have the same brand name + std::ostringstream os; +#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES + os << " "; + os << portNumber; + stringName += os.str(); +#endif + + return stringName; +} + +void MidiOutWinMM ::openPort(unsigned int portNumber, const std::string & /*portName*/) { + if (connected_) { + errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + unsigned int nDevices = midiOutGetNumDevs(); + if (nDevices < 1) { + errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; + error(RtMidiError::NO_DEVICES_FOUND, errorString_); + return; + } + + if (portNumber >= nDevices) { + std::ostringstream ost; + ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::INVALID_PARAMETER, errorString_); + return; + } + + WinMidiData *data = static_cast(apiData_); + MMRESULT result = midiOutOpen(&data->outHandle, + portNumber, + (DWORD)NULL, + (DWORD)NULL, + CALLBACK_NULL); + if (result != MMSYSERR_NOERROR) { + errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + connected_ = true; +} + +void MidiOutWinMM ::closePort(void) { + if (connected_) { + WinMidiData *data = static_cast(apiData_); + midiOutReset(data->outHandle); + midiOutClose(data->outHandle); + data->outHandle = 0; + connected_ = false; + } +} + +void MidiOutWinMM ::setClientName(const std::string &) { + + errorString_ = "MidiOutWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutWinMM ::setPortName(const std::string &) { + + errorString_ = "MidiOutWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutWinMM ::openVirtualPort(const std::string & /*portName*/) { + // This function cannot be implemented for the Windows MM MIDI API. + errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutWinMM ::sendMessage(const unsigned char *message, size_t size) { + if (!connected_) return; + + unsigned int nBytes = static_cast(size); + if (nBytes == 0) { + errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + MMRESULT result; + WinMidiData *data = static_cast(apiData_); + if (message[0] == 0xF0) { // Sysex message + + // Allocate buffer for sysex data. + char *buffer = (char *)malloc(nBytes); + if (buffer == NULL) { + errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; + error(RtMidiError::MEMORY_ERROR, errorString_); + return; + } + + // Copy data to buffer. + for (unsigned int i = 0; i < nBytes; ++i) + buffer[i] = message[i]; + + // Create and prepare MIDIHDR structure. + MIDIHDR sysex; + sysex.lpData = (LPSTR)buffer; + sysex.dwBufferLength = nBytes; + sysex.dwFlags = 0; + result = midiOutPrepareHeader(data->outHandle, &sysex, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + free(buffer); + errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Send the message. + result = midiOutLongMsg(data->outHandle, &sysex, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + free(buffer); + errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Unprepare the buffer and MIDIHDR. + while (MIDIERR_STILLPLAYING == midiOutUnprepareHeader(data->outHandle, &sysex, sizeof(MIDIHDR))) + Sleep(1); + free(buffer); + } else { // Channel or system message. + + // Make sure the message size isn't too big. + if (nBytes > 3) { + errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; + error(RtMidiError::WARNING, errorString_); + return; + } + + // Pack MIDI bytes into double word. + DWORD packet; + unsigned char *ptr = (unsigned char *)&packet; + for (unsigned int i = 0; i < nBytes; ++i) { + *ptr = message[i]; + ++ptr; + } + + // Send the message immediately. + result = midiOutShortMsg(data->outHandle, packet); + if (result != MMSYSERR_NOERROR) { + errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; + error(RtMidiError::DRIVER_ERROR, errorString_); + } + } +} + +#endif // __WINDOWS_MM__ + +//*********************************************************************// +// API: UNIX JACK +// +// Written primarily by Alexander Svetalkin, with updates for delta +// time by Gary Scavone, April 2011. +// +// *********************************************************************// + +#if defined(__UNIX_JACK__) + +// JACK header files +#include +#include +#include +#ifdef HAVE_SEMAPHORE +#include +#endif + +#define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer + +struct JackMidiData { + jack_client_t *client; + jack_port_t *port; + jack_ringbuffer_t *buffSize; + jack_ringbuffer_t *buffMessage; + jack_time_t lastTime; +#ifdef HAVE_SEMAPHORE + sem_t sem_cleanup; + sem_t sem_needpost; +#endif + MidiInApi ::RtMidiInData *rtMidiIn; +}; + +//*********************************************************************// +// API: JACK +// Class Definitions: MidiInJack +//*********************************************************************// + +static int jackProcessIn(jack_nframes_t nframes, void *arg) { + JackMidiData *jData = (JackMidiData *)arg; + MidiInApi ::RtMidiInData *rtData = jData->rtMidiIn; + jack_midi_event_t event; + jack_time_t time; + + // Is port created? + if (jData->port == NULL) return 0; + + void *buff = jack_port_get_buffer(jData->port, nframes); + bool &continueSysex = rtData->continueSysex; + unsigned char &ignoreFlags = rtData->ignoreFlags; + + // We have midi events in buffer + int evCount = jack_midi_get_event_count(buff); + for (int j = 0; j < evCount; j++) { + MidiInApi::MidiMessage &message = rtData->message; + jack_midi_event_get(&event, buff, j); + + // Compute the delta time. + time = jack_get_time(); + if (rtData->firstMessage == true) { + message.timeStamp = 0.0; + rtData->firstMessage = false; + } else + message.timeStamp = (time - jData->lastTime) * 0.000001; + + jData->lastTime = time; + + if (!continueSysex) + message.bytes.clear(); + + if (!((continueSysex || event.buffer[0] == 0xF0) && (ignoreFlags & 0x01))) { + // Unless this is a (possibly continued) SysEx message and we're ignoring SysEx, + // copy the event buffer into the MIDI message struct. + for (unsigned int i = 0; i < event.size; i++) + message.bytes.push_back(event.buffer[i]); + } + + switch (event.buffer[0]) { + case 0xF0: + // Start of a SysEx message + continueSysex = event.buffer[event.size - 1] != 0xF7; + if (ignoreFlags & 0x01) continue; + break; + case 0xF1: + case 0xF8: + // MIDI Time Code or Timing Clock message + if (ignoreFlags & 0x02) continue; + break; + case 0xFE: + // Active Sensing message + if (ignoreFlags & 0x04) continue; + break; + default: + if (continueSysex) { + // Continuation of a SysEx message + continueSysex = event.buffer[event.size - 1] != 0xF7; + if (ignoreFlags & 0x01) continue; + } + // All other MIDI messages + } + + if (!continueSysex) { + // If not a continuation of a SysEx message, + // invoke the user callback function or queue the message. + if (rtData->usingCallback) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback)rtData->userCallback; + callback(message.timeStamp, &message.bytes, rtData->userData); + } else { + // As long as we haven't reached our queue size limit, push the message. + if (!rtData->queue.push(message)) + std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; + } + } + } + + return 0; +} + +MidiInJack ::MidiInJack(const std::string &clientName, unsigned int queueSizeLimit) : + MidiInApi(queueSizeLimit) { + MidiInJack::initialize(clientName); +} + +void MidiInJack ::initialize(const std::string &clientName) { + JackMidiData *data = new JackMidiData; + apiData_ = (void *)data; + + data->rtMidiIn = &inputData_; + data->port = NULL; + data->client = NULL; + this->clientName = clientName; + + connect(); +} + +void MidiInJack ::connect() { + JackMidiData *data = static_cast(apiData_); + if (data->client) + return; + + // Initialize JACK client + if ((data->client = jack_client_open(clientName.c_str(), JackNoStartServer, NULL)) == 0) { + errorString_ = "MidiInJack::initialize: JACK server not running?"; + error(RtMidiError::WARNING, errorString_); + return; + } + + jack_set_process_callback(data->client, jackProcessIn, data); + jack_activate(data->client); +} + +MidiInJack ::~MidiInJack() { + JackMidiData *data = static_cast(apiData_); + MidiInJack::closePort(); + + if (data->client) + jack_client_close(data->client); + delete data; +} + +void MidiInJack ::openPort(unsigned int portNumber, const std::string &portName) { + JackMidiData *data = static_cast(apiData_); + + connect(); + + // Creating new port + if (data->port == NULL) + data->port = jack_port_register(data->client, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); + + if (data->port == NULL) { + errorString_ = "MidiInJack::openPort: JACK error creating port"; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Connecting to the output + std::string name = getPortName(portNumber); + jack_connect(data->client, name.c_str(), jack_port_name(data->port)); + + connected_ = true; +} + +void MidiInJack ::openVirtualPort(const std::string &portName) { + JackMidiData *data = static_cast(apiData_); + + connect(); + if (data->port == NULL) + data->port = jack_port_register(data->client, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); + + if (data->port == NULL) { + errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; + error(RtMidiError::DRIVER_ERROR, errorString_); + } +} + +unsigned int MidiInJack ::getPortCount() { + int count = 0; + JackMidiData *data = static_cast(apiData_); + connect(); + if (!data->client) + return 0; + + // List of available ports + const char **ports = jack_get_ports(data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput); + + if (ports == NULL) return 0; + while (ports[count] != NULL) + count++; + + free(ports); + + return count; +} + +std::string MidiInJack ::getPortName(unsigned int portNumber) { + JackMidiData *data = static_cast(apiData_); + std::string retStr(""); + + connect(); + + // List of available ports + const char **ports = jack_get_ports(data->client, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput); + + // Check port validity + if (ports == NULL) { + errorString_ = "MidiInJack::getPortName: no ports available!"; + error(RtMidiError::WARNING, errorString_); + return retStr; + } + + unsigned int i; + for (i = 0; i < portNumber && ports[i]; i++) { + } + if (i < portNumber || !ports[portNumber]) { + std::ostringstream ost; + ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::WARNING, errorString_); + } else + retStr.assign(ports[portNumber]); + + jack_free(ports); + return retStr; +} + +void MidiInJack ::closePort() { + JackMidiData *data = static_cast(apiData_); + + if (data->port == NULL) return; + jack_port_unregister(data->client, data->port); + data->port = NULL; + + connected_ = false; +} + +void MidiInJack::setClientName(const std::string &) { + + errorString_ = "MidiInJack::setClientName: this function is not implemented for the UNIX_JACK API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiInJack ::setPortName(const std::string &portName) { + JackMidiData *data = static_cast(apiData_); +#ifdef JACK_HAS_PORT_RENAME + jack_port_rename(data->client, data->port, portName.c_str()); +#else + jack_port_set_name(data->port, portName.c_str()); +#endif +} + +//*********************************************************************// +// API: JACK +// Class Definitions: MidiOutJack +//*********************************************************************// + +// Jack process callback +static int jackProcessOut(jack_nframes_t nframes, void *arg) { + JackMidiData *data = (JackMidiData *)arg; + jack_midi_data_t *midiData; + int space; + + // Is port created? + if (data->port == NULL) return 0; + + void *buff = jack_port_get_buffer(data->port, nframes); + jack_midi_clear_buffer(buff); + + while (jack_ringbuffer_read_space(data->buffSize) > 0) { + jack_ringbuffer_read(data->buffSize, (char *)&space, (size_t)sizeof(space)); + midiData = jack_midi_event_reserve(buff, 0, space); + + jack_ringbuffer_read(data->buffMessage, (char *)midiData, (size_t)space); + } + +#ifdef HAVE_SEMAPHORE + if (!sem_trywait(&data->sem_needpost)) + sem_post(&data->sem_cleanup); +#endif + + return 0; +} + +MidiOutJack ::MidiOutJack(const std::string &clientName) : + MidiOutApi() { + MidiOutJack::initialize(clientName); +} + +void MidiOutJack ::initialize(const std::string &clientName) { + JackMidiData *data = new JackMidiData; + apiData_ = (void *)data; + + data->port = NULL; + data->client = NULL; +#ifdef HAVE_SEMAPHORE + sem_init(&data->sem_cleanup, 0, 0); + sem_init(&data->sem_needpost, 0, 0); +#endif + this->clientName = clientName; + + connect(); +} + +void MidiOutJack ::connect() { + JackMidiData *data = static_cast(apiData_); + if (data->client) + return; + + // Initialize output ringbuffers + data->buffSize = jack_ringbuffer_create(JACK_RINGBUFFER_SIZE); + data->buffMessage = jack_ringbuffer_create(JACK_RINGBUFFER_SIZE); + + // Initialize JACK client + if ((data->client = jack_client_open(clientName.c_str(), JackNoStartServer, NULL)) == 0) { + errorString_ = "MidiOutJack::initialize: JACK server not running?"; + error(RtMidiError::WARNING, errorString_); + return; + } + + jack_set_process_callback(data->client, jackProcessOut, data); + jack_activate(data->client); +} + +MidiOutJack ::~MidiOutJack() { + JackMidiData *data = static_cast(apiData_); + MidiOutJack::closePort(); + + // Cleanup + jack_ringbuffer_free(data->buffSize); + jack_ringbuffer_free(data->buffMessage); + if (data->client) { + jack_client_close(data->client); + } + +#ifdef HAVE_SEMAPHORE + sem_destroy(&data->sem_cleanup); + sem_destroy(&data->sem_needpost); +#endif + + delete data; +} + +void MidiOutJack ::openPort(unsigned int portNumber, const std::string &portName) { + JackMidiData *data = static_cast(apiData_); + + connect(); + + // Creating new port + if (data->port == NULL) + data->port = jack_port_register(data->client, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); + + if (data->port == NULL) { + errorString_ = "MidiOutJack::openPort: JACK error creating port"; + error(RtMidiError::DRIVER_ERROR, errorString_); + return; + } + + // Connecting to the output + std::string name = getPortName(portNumber); + jack_connect(data->client, jack_port_name(data->port), name.c_str()); + + connected_ = true; +} + +void MidiOutJack ::openVirtualPort(const std::string &portName) { + JackMidiData *data = static_cast(apiData_); + + connect(); + if (data->port == NULL) + data->port = jack_port_register(data->client, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); + + if (data->port == NULL) { + errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; + error(RtMidiError::DRIVER_ERROR, errorString_); + } +} + +unsigned int MidiOutJack ::getPortCount() { + int count = 0; + JackMidiData *data = static_cast(apiData_); + connect(); + if (!data->client) + return 0; + + // List of available ports + const char **ports = jack_get_ports(data->client, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput); + + if (ports == NULL) return 0; + while (ports[count] != NULL) + count++; + + free(ports); + + return count; +} + +std::string MidiOutJack ::getPortName(unsigned int portNumber) { + JackMidiData *data = static_cast(apiData_); + std::string retStr(""); + + connect(); + + // List of available ports + const char **ports = jack_get_ports(data->client, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput); + + // Check port validity + if (ports == NULL) { + errorString_ = "MidiOutJack::getPortName: no ports available!"; + error(RtMidiError::WARNING, errorString_); + return retStr; + } + + if (ports[portNumber] == NULL) { + std::ostringstream ost; + ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error(RtMidiError::WARNING, errorString_); + } else + retStr.assign(ports[portNumber]); + + free(ports); + return retStr; +} + +void MidiOutJack ::closePort() { + JackMidiData *data = static_cast(apiData_); + + if (data->port == NULL) return; + +#ifdef HAVE_SEMAPHORE + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) != -1) { + ts.tv_sec += 1; // wait max one second + sem_post(&data->sem_needpost); + sem_timedwait(&data->sem_cleanup, &ts); + } +#endif + + jack_port_unregister(data->client, data->port); + data->port = NULL; + + connected_ = false; +} + +void MidiOutJack::setClientName(const std::string &) { + + errorString_ = "MidiOutJack::setClientName: this function is not implemented for the UNIX_JACK API!"; + error(RtMidiError::WARNING, errorString_); +} + +void MidiOutJack ::setPortName(const std::string &portName) { + JackMidiData *data = static_cast(apiData_); +#ifdef JACK_HAS_PORT_RENAME + jack_port_rename(data->client, data->port, portName.c_str()); +#else + jack_port_set_name(data->port, portName.c_str()); +#endif +} + +void MidiOutJack ::sendMessage(const unsigned char *message, size_t size) { + int nBytes = static_cast(size); + JackMidiData *data = static_cast(apiData_); + + // Write full message to buffer + jack_ringbuffer_write(data->buffMessage, (const char *)message, nBytes); + jack_ringbuffer_write(data->buffSize, (char *)&nBytes, sizeof(nBytes)); +} + +#endif // __UNIX_JACK__ diff --git a/drivers/rtmidi/rtmidi/RtMidi.h b/drivers/rtmidi/rtmidi/RtMidi.h new file mode 100644 index 0000000..03c5d8f --- /dev/null +++ b/drivers/rtmidi/rtmidi/RtMidi.h @@ -0,0 +1,673 @@ +/**********************************************************************/ +/*! \class RtMidi + \brief An abstract base class for realtime MIDI input/output. + + This class implements some common functionality for the realtime + MIDI input/output subclasses RtMidiIn and RtMidiOut. + + RtMidi GitHub site: https://github.com/thestk/rtmidi + RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ + + RtMidi: realtime MIDI i/o C++ classes + Copyright (c) 2003-2019 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/**********************************************************************/ + +/*! + \file RtMidi.h + */ + +#ifndef RTMIDI_H +#define RTMIDI_H + +#if defined _WIN32 || defined __CYGWIN__ +#if defined(RTMIDI_EXPORT) +#define RTMIDI_DLL_PUBLIC __declspec(dllexport) +#else +#define RTMIDI_DLL_PUBLIC +#endif +#else +#if __GNUC__ >= 4 +#define RTMIDI_DLL_PUBLIC __attribute__((visibility("default"))) +#else +#define RTMIDI_DLL_PUBLIC +#endif +#endif + +#define RTMIDI_VERSION "4.0.0" + +#include +#include +#include +#include + +/************************************************************************/ +/*! \class RtMidiError + \brief Exception handling class for RtMidi. + + The RtMidiError class is quite simple but it does allow errors to be + "caught" by RtMidiError::Type. See the RtMidi documentation to know + which methods can throw an RtMidiError. +*/ +/************************************************************************/ + +class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception { +public: + //! Defined RtMidiError types. + enum Type { + WARNING, /*!< A non-critical error. */ + DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + UNSPECIFIED, /*!< The default, unspecified error type. */ + NO_DEVICES_FOUND, /*!< No devices found on system. */ + INVALID_DEVICE, /*!< An invalid device ID was specified. */ + MEMORY_ERROR, /*!< An error occured during memory allocation. */ + INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + INVALID_USE, /*!< The function was called incorrectly. */ + DRIVER_ERROR, /*!< A system driver error occured. */ + SYSTEM_ERROR, /*!< A system error occured. */ + THREAD_ERROR /*!< A thread error occured. */ + }; + + //! The constructor. + RtMidiError(const std::string &message, Type type = RtMidiError::UNSPECIFIED) throw() : + message_(message), + type_(type) {} + + //! The destructor. + virtual ~RtMidiError(void) throw() {} + + //! Prints thrown error message to stderr. + virtual void printMessage(void) const throw() { std::cerr << '\n' + << message_ << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type &getType(void) const throw() { return type_; } + + //! Returns the thrown error message string. + virtual const std::string &getMessage(void) const throw() { return message_; } + + //! Returns the thrown error message as a c-style string. + virtual const char *what(void) const throw() { return message_.c_str(); } + +protected: + std::string message_; + Type type_; +}; + +//! RtMidi error callback function prototype. +/*! + \param type Type of error. + \param errorText Error description. + + Note that class behaviour is undefined after a critical error (not + a warning) is reported. + */ +typedef void (*RtMidiErrorCallback)(RtMidiError::Type type, const std::string &errorText, void *userData); + +class MidiApi; + +class RTMIDI_DLL_PUBLIC RtMidi { +public: + //! MIDI API specifier arguments. + enum Api { + UNSPECIFIED, /*!< Search for a working compiled API. */ + MACOSX_CORE, /*!< Macintosh OS-X CoreMIDI API. */ + LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ + UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ + WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ + RTMIDI_DUMMY, /*!< A compilable but non-functional API. */ + NUM_APIS /*!< Number of values in this enum. */ + }; + + //! A static function to determine the current RtMidi version. + static std::string getVersion(void) throw(); + + //! A static function to determine the available compiled MIDI APIs. + /*! + The values returned in the std::vector can be compared against + the enumerated list values. Note that there can be more than one + API compiled for certain operating systems. + */ + static void getCompiledApi(std::vector &apis) throw(); + + //! Return the name of a specified compiled MIDI API. + /*! + This obtains a short lower-case name used for identification purposes. + This value is guaranteed to remain identical across library versions. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiName(RtMidi::Api api); + + //! Return the display name of a specified compiled MIDI API. + /*! + This obtains a long name used for display purposes. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiDisplayName(RtMidi::Api api); + + //! Return the compiled MIDI API having the given name. + /*! + A case insensitive comparison will check the specified name + against the list of compiled APIs, and return the one which + matches. On failure, the function returns UNSPECIFIED. + */ + static RtMidi::Api getCompiledApiByName(const std::string &name); + + //! Pure virtual openPort() function. + virtual void openPort(unsigned int portNumber = 0, const std::string &portName = std::string("RtMidi")) = 0; + + //! Pure virtual openVirtualPort() function. + virtual void openVirtualPort(const std::string &portName = std::string("RtMidi")) = 0; + + //! Pure virtual getPortCount() function. + virtual unsigned int getPortCount() = 0; + + //! Pure virtual getPortName() function. + virtual std::string getPortName(unsigned int portNumber = 0) = 0; + + //! Pure virtual closePort() function. + virtual void closePort(void) = 0; + + void setClientName(const std::string &clientName); + void setPortName(const std::string &portName); + + //! Returns true if a port is open and false if not. + /*! + Note that this only applies to connections made with the openPort() + function, not to virtual ports. + */ + virtual bool isPortOpen(void) const = 0; + + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback(RtMidiErrorCallback errorCallback = NULL, void *userData = 0) = 0; + +protected: + RtMidi(); + virtual ~RtMidi(); + MidiApi *rtapi_; +}; + +/**********************************************************************/ +/*! \class RtMidiIn + \brief A realtime MIDI input class. + + This class provides a common, platform-independent API for + realtime MIDI input. It allows access to a single MIDI input + port. Incoming MIDI messages are either saved to a queue for + retrieval using the getMessage() function or immediately passed to + a user-specified callback function. Create multiple instances of + this class to connect to more than one MIDI device at the same + time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also + possible to open a virtual input port to which other MIDI software + clients can connect. +*/ +/**********************************************************************/ + +// **************************************************************** // +// +// RtMidiIn and RtMidiOut class declarations. +// +// RtMidiIn / RtMidiOut are "controllers" used to select an available +// MIDI input or output interface. They present common APIs for the +// user to call but all functionality is implemented by the classes +// MidiInApi, MidiOutApi and their subclasses. RtMidiIn and RtMidiOut +// each create an instance of a MidiInApi or MidiOutApi subclass based +// on the user's API choice. If no choice is made, they attempt to +// make a "logical" API selection. +// +// **************************************************************** // + +class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi { +public: + //! User callback function type definition. + typedef void (*RtMidiCallback)(double timeStamp, std::vector *message, void *userData); + + //! Default constructor that allows an optional api, client name and queue size. + /*! + An exception will be thrown if a MIDI system initialization + error occurs. The queue size defines the maximum number of + messages that can be held in the MIDI queue (when not using a + callback function). If the queue size limit is reached, + incoming messages will be ignored. + + If no API argument is specified and multiple API support has been + compiled, the default order of use is ALSA, JACK (Linux) and CORE, + JACK (OS-X). + + \param api An optional API id can be specified. + \param clientName An optional client name can be specified. This + will be used to group the ports that are created + by the application. + \param queueSizeLimit An optional size of the MIDI input queue can be specified. + */ + RtMidiIn(RtMidi::Api api = UNSPECIFIED, + const std::string &clientName = "RtMidi Input Client", + unsigned int queueSizeLimit = 100); + + //! If a MIDI connection is still open, it will be closed by the destructor. + ~RtMidiIn(void) throw(); + + //! Returns the MIDI API specifier for the current instance of RtMidiIn. + RtMidi::Api getCurrentApi(void) throw(); + + //! Open a MIDI input connection given by enumeration number. + /*! + \param portNumber An optional port number greater than 0 can be specified. + Otherwise, the default or first port found is opened. + \param portName An optional name for the application port that is used to connect to portId can be specified. + */ + void openPort(unsigned int portNumber = 0, const std::string &portName = std::string("RtMidi Input")); + + //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only). + /*! + This function creates a virtual MIDI input port to which other + software applications can connect. This type of functionality + is currently only supported by the Macintosh OS-X, any JACK, + and Linux ALSA APIs (the function returns an error for the other APIs). + + \param portName An optional name for the application port that is + used to connect to portId can be specified. + */ + void openVirtualPort(const std::string &portName = std::string("RtMidi Input")); + + //! Set a callback function to be invoked for incoming MIDI messages. + /*! + The callback function will be called whenever an incoming MIDI + message is received. While not absolutely necessary, it is best + to set the callback function before opening a MIDI port to avoid + leaving some messages in the queue. + + \param callback A callback function must be given. + \param userData Optionally, a pointer to additional data can be + passed to the callback function whenever it is called. + */ + void setCallback(RtMidiCallback callback, void *userData = 0); + + //! Cancel use of the current callback function (if one exists). + /*! + Subsequent incoming MIDI messages will be written to the queue + and can be retrieved with the \e getMessage function. + */ + void cancelCallback(); + + //! Close an open MIDI connection (if one exists). + void closePort(void); + + //! Returns true if a port is open and false if not. + /*! + Note that this only applies to connections made with the openPort() + function, not to virtual ports. + */ + virtual bool isPortOpen() const; + + //! Return the number of available MIDI input ports. + /*! + \return This function returns the number of MIDI ports of the selected API. + */ + unsigned int getPortCount(); + + //! Return a string identifier for the specified MIDI input port number. + /*! + \return The name of the port with the given Id is returned. + \retval An empty string is returned if an invalid port specifier + is provided. User code should assume a UTF-8 encoding. + */ + std::string getPortName(unsigned int portNumber = 0); + + //! Specify whether certain MIDI message types should be queued or ignored during input. + /*! + By default, MIDI timing and active sensing messages are ignored + during message input because of their relative high data rates. + MIDI sysex messages are ignored by default as well. Variable + values of "true" imply that the respective message type will be + ignored. + */ + void ignoreTypes(bool midiSysex = true, bool midiTime = true, bool midiSense = true); + + //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds. + /*! + This function returns immediately whether a new message is + available or not. A valid message is indicated by a non-zero + vector size. An exception is thrown if an error occurs during + message retrieval or an input connection was not previously + established. + */ + double getMessage(std::vector *message); + + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback(RtMidiErrorCallback errorCallback = NULL, void *userData = 0); + +protected: + void openMidiApi(RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit); +}; + +/**********************************************************************/ +/*! \class RtMidiOut + \brief A realtime MIDI output class. + + This class provides a common, platform-independent API for MIDI + output. It allows one to probe available MIDI output ports, to + connect to one such port, and to send MIDI bytes immediately over + the connection. Create multiple instances of this class to + connect to more than one MIDI device at the same time. With the + OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a + virtual port to which other MIDI software clients can connect. +*/ +/**********************************************************************/ + +class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi { +public: + //! Default constructor that allows an optional client name. + /*! + An exception will be thrown if a MIDI system initialization error occurs. + + If no API argument is specified and multiple API support has been + compiled, the default order of use is ALSA, JACK (Linux) and CORE, + JACK (OS-X). + */ + RtMidiOut(RtMidi::Api api = UNSPECIFIED, + const std::string &clientName = "RtMidi Output Client"); + + //! The destructor closes any open MIDI connections. + ~RtMidiOut(void) throw(); + + //! Returns the MIDI API specifier for the current instance of RtMidiOut. + RtMidi::Api getCurrentApi(void) throw(); + + //! Open a MIDI output connection. + /*! + An optional port number greater than 0 can be specified. + Otherwise, the default or first port found is opened. An + exception is thrown if an error occurs while attempting to make + the port connection. + */ + void openPort(unsigned int portNumber = 0, const std::string &portName = std::string("RtMidi Output")); + + //! Close an open MIDI connection (if one exists). + void closePort(void); + + //! Returns true if a port is open and false if not. + /*! + Note that this only applies to connections made with the openPort() + function, not to virtual ports. + */ + virtual bool isPortOpen() const; + + //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only). + /*! + This function creates a virtual MIDI output port to which other + software applications can connect. This type of functionality + is currently only supported by the Macintosh OS-X, Linux ALSA + and JACK APIs (the function does nothing with the other APIs). + An exception is thrown if an error occurs while attempting to + create the virtual port. + */ + void openVirtualPort(const std::string &portName = std::string("RtMidi Output")); + + //! Return the number of available MIDI output ports. + unsigned int getPortCount(void); + + //! Return a string identifier for the specified MIDI port type and number. + /*! + \return The name of the port with the given Id is returned. + \retval An empty string is returned if an invalid port specifier + is provided. User code should assume a UTF-8 encoding. + */ + std::string getPortName(unsigned int portNumber = 0); + + //! Immediately send a single message out an open MIDI output port. + /*! + An exception is thrown if an error occurs during output or an + output connection was not previously established. + */ + void sendMessage(const std::vector *message); + + //! Immediately send a single message out an open MIDI output port. + /*! + An exception is thrown if an error occurs during output or an + output connection was not previously established. + + \param message A pointer to the MIDI message as raw bytes + \param size Length of the MIDI message in bytes + */ + void sendMessage(const unsigned char *message, size_t size); + + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback(RtMidiErrorCallback errorCallback = NULL, void *userData = 0); + +protected: + void openMidiApi(RtMidi::Api api, const std::string &clientName); +}; + +// **************************************************************** // +// +// MidiInApi / MidiOutApi class declarations. +// +// Subclasses of MidiInApi and MidiOutApi contain all API- and +// OS-specific code necessary to fully implement the RtMidi API. +// +// Note that MidiInApi and MidiOutApi are abstract base classes and +// cannot be explicitly instantiated. RtMidiIn and RtMidiOut will +// create instances of a MidiInApi or MidiOutApi subclass. +// +// **************************************************************** // + +class RTMIDI_DLL_PUBLIC MidiApi { +public: + MidiApi(); + virtual ~MidiApi(); + virtual RtMidi::Api getCurrentApi(void) = 0; + virtual void openPort(unsigned int portNumber, const std::string &portName) = 0; + virtual void openVirtualPort(const std::string &portName) = 0; + virtual void closePort(void) = 0; + virtual void setClientName(const std::string &clientName) = 0; + virtual void setPortName(const std::string &portName) = 0; + + virtual unsigned int getPortCount(void) = 0; + virtual std::string getPortName(unsigned int portNumber) = 0; + + inline bool isPortOpen() const { return connected_; } + void setErrorCallback(RtMidiErrorCallback errorCallback, void *userData); + + //! A basic error reporting function for RtMidi classes. + void error(RtMidiError::Type type, std::string errorString); + +protected: + virtual void initialize(const std::string &clientName) = 0; + + void *apiData_; + bool connected_; + std::string errorString_; + RtMidiErrorCallback errorCallback_; + bool firstErrorOccurred_; + void *errorCallbackUserData_; +}; + +class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi { +public: + MidiInApi(unsigned int queueSizeLimit); + virtual ~MidiInApi(void); + void setCallback(RtMidiIn::RtMidiCallback callback, void *userData); + void cancelCallback(void); + virtual void ignoreTypes(bool midiSysex, bool midiTime, bool midiSense); + double getMessage(std::vector *message); + + // A MIDI structure used internally by the class to store incoming + // messages. Each message represents one and only one MIDI message. + struct MidiMessage { + std::vector bytes; + + //! Time in seconds elapsed since the previous message + double timeStamp; + + // Default constructor. + MidiMessage() : + bytes(0), + timeStamp(0.0) {} + }; + + struct MidiQueue { + unsigned int front; + unsigned int back; + unsigned int ringSize; + MidiMessage *ring; + + // Default constructor. + MidiQueue() : + front(0), + back(0), + ringSize(0), + ring(0) {} + bool push(const MidiMessage &); + bool pop(std::vector *, double *); + unsigned int size(unsigned int *back = 0, unsigned int *front = 0); + }; + + // The RtMidiInData structure is used to pass private class data to + // the MIDI input handling function or thread. + struct RtMidiInData { + MidiQueue queue; + MidiMessage message; + unsigned char ignoreFlags; + bool doInput; + bool firstMessage; + void *apiData; + bool usingCallback; + RtMidiIn::RtMidiCallback userCallback; + void *userData; + bool continueSysex; + + // Default constructor. + RtMidiInData() : + ignoreFlags(7), + doInput(false), + firstMessage(true), + apiData(0), + usingCallback(false), + userCallback(0), + userData(0), + continueSysex(false) {} + }; + +protected: + RtMidiInData inputData_; +}; + +class RTMIDI_DLL_PUBLIC MidiOutApi : public MidiApi { +public: + MidiOutApi(void); + virtual ~MidiOutApi(void); + virtual void sendMessage(const unsigned char *message, size_t size) = 0; +}; + +// **************************************************************** // +// +// Inline RtMidiIn and RtMidiOut definitions. +// +// **************************************************************** // + +inline RtMidi::Api RtMidiIn ::getCurrentApi(void) throw() { + return rtapi_->getCurrentApi(); +} +inline void RtMidiIn ::openPort(unsigned int portNumber, const std::string &portName) { + rtapi_->openPort(portNumber, portName); +} +inline void RtMidiIn ::openVirtualPort(const std::string &portName) { + rtapi_->openVirtualPort(portName); +} +inline void RtMidiIn ::closePort(void) { + rtapi_->closePort(); +} +inline bool RtMidiIn ::isPortOpen() const { + return rtapi_->isPortOpen(); +} +inline void RtMidiIn ::setCallback(RtMidiCallback callback, void *userData) { + static_cast(rtapi_)->setCallback(callback, userData); +} +inline void RtMidiIn ::cancelCallback(void) { + static_cast(rtapi_)->cancelCallback(); +} +inline unsigned int RtMidiIn ::getPortCount(void) { + return rtapi_->getPortCount(); +} +inline std::string RtMidiIn ::getPortName(unsigned int portNumber) { + return rtapi_->getPortName(portNumber); +} +inline void RtMidiIn ::ignoreTypes(bool midiSysex, bool midiTime, bool midiSense) { + static_cast(rtapi_)->ignoreTypes(midiSysex, midiTime, midiSense); +} +inline double RtMidiIn ::getMessage(std::vector *message) { + return static_cast(rtapi_)->getMessage(message); +} +inline void RtMidiIn ::setErrorCallback(RtMidiErrorCallback errorCallback, void *userData) { + rtapi_->setErrorCallback(errorCallback, userData); +} + +inline RtMidi::Api RtMidiOut ::getCurrentApi(void) throw() { + return rtapi_->getCurrentApi(); +} +inline void RtMidiOut ::openPort(unsigned int portNumber, const std::string &portName) { + rtapi_->openPort(portNumber, portName); +} +inline void RtMidiOut ::openVirtualPort(const std::string &portName) { + rtapi_->openVirtualPort(portName); +} +inline void RtMidiOut ::closePort(void) { + rtapi_->closePort(); +} +inline bool RtMidiOut ::isPortOpen() const { + return rtapi_->isPortOpen(); +} +inline unsigned int RtMidiOut ::getPortCount(void) { + return rtapi_->getPortCount(); +} +inline std::string RtMidiOut ::getPortName(unsigned int portNumber) { + return rtapi_->getPortName(portNumber); +} +inline void RtMidiOut ::sendMessage(const std::vector *message) { + static_cast(rtapi_)->sendMessage(&message->at(0), message->size()); +} +inline void RtMidiOut ::sendMessage(const unsigned char *message, size_t size) { + static_cast(rtapi_)->sendMessage(message, size); +} +inline void RtMidiOut ::setErrorCallback(RtMidiErrorCallback errorCallback, void *userData) { + rtapi_->setErrorCallback(errorCallback, userData); +} + +#endif diff --git a/dsp/midi_event.cpp b/dsp/midi_event.cpp index 5a63dde..41e8285 100644 --- a/dsp/midi_event.cpp +++ b/dsp/midi_event.cpp @@ -83,6 +83,48 @@ const unsigned char MIDIEvent::cc_indices[CC_MAX]{ 128 }; +Error MIDIEvent::parse(unsigned char *p_raw) { + + switch (p_raw[0] >> 4) { + case 0x8: { + type = MIDI_NOTE_OFF; + note.note = p_raw[1]; + note.velocity = p_raw[2]; + } break; + case 0x9: { + type = MIDI_NOTE_ON; + note.note = p_raw[1]; + note.velocity = p_raw[2]; + } break; + case 0xA: { + type = MIDI_NOTE_PRESSURE; + note.note = p_raw[1]; + note.velocity = p_raw[2]; + } break; + case 0xB: { + type = MIDI_CONTROLLER; + control.index = p_raw[1]; + control.parameter = p_raw[2]; + } break; + case 0xC: { + type = MIDI_PATCH_SELECT; + patch.index = p_raw[1]; + } break; + case 0xD: { + type = MIDI_AFTERTOUCH; + aftertouch.pressure = p_raw[1]; + } break; + case 0xE: { + type = MIDI_PITCH_BEND; + pitch_bend.bend = (short(p_raw[1]) << 7) | short(p_raw[2]); + } break; + default: { + return ERR_INVALID_PARAMETER; + } + } + channel = p_raw[0] & 0xF; + return OK; +} MIDIEvent::MIDIEvent() { type = NONE; raw.param1 = 0; diff --git a/dsp/midi_event.h b/dsp/midi_event.h index e9dafea..dd9ce43 100644 --- a/dsp/midi_event.h +++ b/dsp/midi_event.h @@ -1,6 +1,8 @@ #ifndef MIDI_EVENT_H #define MIDI_EVENT_H +#include "globals/error_list.h" + struct MIDIEvent { public: enum CC { //enum of those supported, use cc_indices to get actual number @@ -139,6 +141,7 @@ struct MIDIEvent { } scale; }; + Error parse(unsigned char *p_raw); MIDIEvent(); MIDIEvent(Type p_type, unsigned char p_chan, unsigned char data1, unsigned char data2); MIDIEvent(Type p_type, unsigned char p_chan, unsigned short data); diff --git a/effects/internal/effect_note_puncher.cpp b/effects/internal/effect_note_puncher.cpp index e7e6bf3..ebe3326 100644 --- a/effects/internal/effect_note_puncher.cpp +++ b/effects/internal/effect_note_puncher.cpp @@ -15,7 +15,7 @@ void AudioEffectNotePuncher::process_with_secondary(const Event *p_events, int p int attack_samples = (control_ports[CONTROL_PORT_ATTACK_MS].value / 1000.0) * sampling_rate; int decay_samples = (control_ports[CONTROL_PORT_DECAY_MS].value / 1000.0) * sampling_rate; - float amplify = db2linear(control_ports[CONTROL_PORT_PUNCH_DB].value); + float amplify_db = control_ports[CONTROL_PORT_PUNCH_DB].value; //check notes for (int i = 0; i < p_event_count; i++) { @@ -57,7 +57,9 @@ void AudioEffectNotePuncher::process_with_secondary(const Event *p_events, int p } } - amp *= amplify; + amp = db2linear(amplify_db * amp); + + //printf("%i att %i dec %i - %f\n", punches[j].time, attack_samples, decay_samples, amp); envelope[i] = MAX(envelope[i], amp); punches[j].time++; diff --git a/engine/midi_driver_manager.cpp b/engine/midi_driver_manager.cpp new file mode 100644 index 0000000..cd5b7e3 --- /dev/null +++ b/engine/midi_driver_manager.cpp @@ -0,0 +1,69 @@ +#include "midi_driver_manager.h" + +void MIDIInputDriver::event(double p_stamp, const MIDIEvent &p_event) { + if (MIDIDriverManager::event_callback) { + MIDIDriverManager::event_callback(p_stamp, p_event); + } +} + +/////////////////////// + +MIDIInputDriver *MIDIDriverManager::input_drivers[MIDIDriverManager::MAX_MIDI_DRIVERS]; +int MIDIDriverManager::input_driver_count = 0; +int MIDIDriverManager::input_current_driver = -1; + +MIDIDriverManager::EventCallback MIDIDriverManager::event_callback = NULL; + +void MIDIDriverManager::lock_driver() { + if (input_current_driver != -1 && input_drivers[input_current_driver]->is_active()) { + input_drivers[input_current_driver]->lock(); + } +} +void MIDIDriverManager::unlock_driver() { + if (input_current_driver != -1 && input_drivers[input_current_driver]->is_active()) { + input_drivers[input_current_driver]->unlock(); + } +} + +bool MIDIDriverManager::init_input_driver(int p_driver) { + ERR_FAIL_COND_V(p_driver != -1 && p_driver < 0 && p_driver >= input_driver_count, false); + + if (input_current_driver != -1 && input_drivers[input_current_driver]->is_active()) { + input_drivers[input_current_driver]->finish(); + } + + input_current_driver = p_driver; + + if (input_current_driver != -1) { + return input_drivers[input_current_driver]->init(); + } + + return false; +} +void MIDIDriverManager::finish_input_driver() { + if (input_current_driver != -1 && input_drivers[input_current_driver]->is_active()) { + input_drivers[input_current_driver]->finish(); + } +} +bool MIDIDriverManager::is_input_driver_active() { + return (input_current_driver != -1 && input_drivers[input_current_driver]->is_active()); +} +int MIDIDriverManager::get_input_driver_count() { + return input_driver_count; +} +MIDIInputDriver *MIDIDriverManager::get_input_driver(int p_which) { + ERR_FAIL_INDEX_V(p_which, input_driver_count, NULL); + return input_drivers[p_which]; +} + +int MIDIDriverManager::get_current_input_driver_index() { + return input_current_driver; +} + +void MIDIDriverManager::add_input_driver(MIDIInputDriver *p_driver) { + ERR_FAIL_COND(input_driver_count == MAX_MIDI_DRIVERS); + input_drivers[input_driver_count++] = p_driver; +} +void MIDIDriverManager::set_event_callback(EventCallback p_callback) { + event_callback = p_callback; +} diff --git a/engine/midi_driver_manager.h b/engine/midi_driver_manager.h new file mode 100644 index 0000000..af8b201 --- /dev/null +++ b/engine/midi_driver_manager.h @@ -0,0 +1,60 @@ +#ifndef MIDI_DRIVER_MANAGER_H +#define MIDI_DRIVER_MANAGER_H + +#include "dsp/midi_event.h" +#include "globals/error_macros.h" +#include "globals/rstring.h" + +class MIDIInputDriver { +protected: + void event(double p_stamp, const MIDIEvent &p_event); + +public: + virtual void lock() = 0; ///< Lock called from UI,game,etc (non-audio) thread, to access audio variables + virtual void unlock() = 0; ///< UnLock called from UI,game,etc (non-audio) thread, to access audio variables + + virtual String get_name() const = 0; + virtual String get_id() const = 0; + + virtual bool is_active() = 0; + virtual bool init() = 0; + virtual void finish() = 0; + + MIDIInputDriver() {} + virtual ~MIDIInputDriver() {} +}; + +class MIDIDriverManager { + + enum { + MAX_MIDI_DRIVERS = 64 + }; + +public: + typedef void (*EventCallback)(double p_stamp, const MIDIEvent &); + +private: + static MIDIInputDriver *input_drivers[MAX_MIDI_DRIVERS]; + static int input_driver_count; + static int input_current_driver; + + friend class MIDIInputDriver; + static EventCallback event_callback; + +public: + static void lock_driver(); ///< Protect audio thread variables from ui,game,etc (non-audio) threads + static void unlock_driver(); ///< Protect audio thread variables from ui,game,etc (non-audio) threads + + static bool init_input_driver(int p_driver = -1); ///< -1 is current + static void finish_input_driver(); + static bool is_input_driver_active(); + static int get_input_driver_count(); + static MIDIInputDriver *get_input_driver(int p_which = -1); ///< -1 is current + + static int get_current_input_driver_index(); + + static void add_input_driver(MIDIInputDriver *p_driver); + static void set_event_callback(EventCallback p_callback); +}; + +#endif // MIDI_DRIVER_MANAGER_H diff --git a/engine/song.cpp b/engine/song.cpp index a1fdbc4..a2ce95b 100644 --- a/engine/song.cpp +++ b/engine/song.cpp @@ -111,6 +111,12 @@ void Song::_process_audio_step() { playback.range.active = false; } + for (int i = 0; i < playback.single_event_count; i++) { + ERR_CONTINUE(playback.single_event[i].track < 0 || playback.single_event[i].track >= tracks.size()); + tracks[playback.single_event[i].track]->add_single_event(playback.single_event[i].event); + } + playback.single_event_count = 0; + //process audio in track-order ERR_FAIL_COND(track_process_order.size() != tracks.size()); @@ -706,6 +712,18 @@ void Song::play_event_range(int p_pattern, int p_from_column, int p_to_column, T playback.range.to_column = p_to_column; playback.range.to_tick = p_to_tick; } +void Song::play_single_event(int p_track, const AudioEffect::Event &p_event) { + _AUDIO_LOCK_ + if (playback.single_event_count == SINGLE_EVENT_MAX) { + return; + } + if (p_track < 0 || p_track >= tracks.size()) { + return; + } + playback.single_event[playback.single_event_count].event = p_event; + playback.single_event[playback.single_event_count].track = p_track; + playback.single_event_count++; +} void Song::stop() { _AUDIO_LOCK_ @@ -852,7 +870,9 @@ Song::Song() { playback.prev_volume = 1.0; playback.range.active = false; playback.can_loop = true; + playback.single_event_count = 0; main_volume_db = -12; peak_volume_l = 0; peak_volume_r = 0; + playback.single_event_count = 0; } diff --git a/engine/song.h b/engine/song.h index 45368d7..b2b3825 100644 --- a/engine/song.h +++ b/engine/song.h @@ -13,7 +13,8 @@ class Song { ORDER_MAX = 999, ORDER_EMPTY = 0xFFFFF, ORDER_SKIP = 0xFFFFE, - MAX_PATTERN = 999 + MAX_PATTERN = 999, + SINGLE_EVENT_MAX = 1024 }; enum SwingBeatDivisor { @@ -89,6 +90,14 @@ class Song { Tick to_tick; } range; + struct SingleEvent { + int track; + AudioEffect::Event event; + }; + + SingleEvent single_event[SINGLE_EVENT_MAX]; + int single_event_count; + } playback; void _pre_capture_automations(); @@ -156,6 +165,7 @@ class Song { void play_event_range(int p_pattern, int p_from_column, int p_to_column, Tick p_from_tick, Tick p_to_tick); void play_next_pattern(); void play_prev_pattern(); + void play_single_event(int p_track, const AudioEffect::Event &p_event); void stop(); bool is_playing() const; diff --git a/engine/track.cpp b/engine/track.cpp index e5690af..652cd8b 100644 --- a/engine/track.cpp +++ b/engine/track.cpp @@ -968,6 +968,14 @@ Tick Track::_get_swinged_tick(Tick p_tick, int p_swing_divisor, float p_swing) { return tick_frac + tick_debased; } +void Track::add_single_event(const AudioEffect::Event &p_event) { + + if (event_buffer_size == EVENT_BUFFER_MAX) { + return; + } + event_buffer[event_buffer_size] = p_event; + event_buffer_size++; +} void Track::process_events(int p_pattern, Tick p_offset, Tick p_from_tick, Tick p_to_tick, int p_bpm, int p_swing_divisor, float p_swing, int p_from, int p_to) { if (event_buffer_size == EVENT_BUFFER_MAX) { diff --git a/engine/track.h b/engine/track.h index c25fc4c..92e6e1a 100644 --- a/engine/track.h +++ b/engine/track.h @@ -258,6 +258,7 @@ class Track { int event_buffer_size; void process_events(int p_pattern, Tick p_offset, Tick p_from_tick, Tick p_to_tick, int p_bpm, int p_swing_divisor, float p_swing, int p_from = -1, int p_to = -1); + void add_single_event(const AudioEffect::Event &p_event); const AudioFrame *process_audio_step(); bool first_mix; diff --git a/gui/interface.cpp b/gui/interface.cpp index e8a95fc..aed8185 100644 --- a/gui/interface.cpp +++ b/gui/interface.cpp @@ -981,6 +981,8 @@ void Interface::_on_effect_request_editor(int p_track, int p_effect) { //nope, dont do it //effect_editor->set_transient_for(*this); effect_editor->set_position(Gtk::WIN_POS_CENTER_ALWAYS); + + effect_editor->signal_focus_in_event().connect(sigc::bind(sigc::mem_fun(*this, &Interface::_on_editor_window_gained_focus), track)); } else { active_effect_editors[effect]->hide(); } @@ -1254,6 +1256,36 @@ void Interface::_process_audio(AudioFrame *p_frames, int p_amount) { singleton->song.process_audio(p_frames, p_amount); } +void Interface::_process_midi(double p_delta, const MIDIEvent &p_event) { + AudioEffect::Event ev; + switch (p_event.type) { + case MIDIEvent::MIDI_NOTE_ON: { + ev.type = AudioEffect::Event::TYPE_NOTE; + ev.param8 = p_event.note.note; + ev.paramf = p_event.note.velocity / 127.0; + + } break; + case MIDIEvent::MIDI_NOTE_OFF: { + ev.type = AudioEffect::Event::TYPE_NOTE_OFF; + ev.param8 = p_event.note.note; + ev.paramf = p_event.note.velocity / 127.0; + + } break; + case MIDIEvent::MIDI_NOTE_PRESSURE: { + ev.type = AudioEffect::Event::TYPE_AFTERTOUCH; + ev.param8 = p_event.note.note; + ev.paramf = p_event.note.velocity / 127.0; + + } break; + default: { + return; + } + } + + ev.offset = 0; + singleton->song.play_single_event(singleton->pattern_editor.get_current_track(), ev); +} + Interface *Interface::singleton = NULL; bool Interface::_playback_timer_callback() { @@ -1303,6 +1335,17 @@ void Interface::_update_song_mixing_parameters() { song.set_sampling_rate(SoundDriverManager::get_mix_frequency_hz(SoundDriverManager::get_mix_frequency())); song.set_process_buffer_size(SoundDriverManager::get_buffer_size_frames(SoundDriverManager::get_step_buffer_size())); } + +bool Interface::_on_editor_window_gained_focus(GdkEventFocus *, Track *p_track) { + for (int i = 0; i < song.get_track_count(); i++) { + if (song.get_track(i) == p_track) { + pattern_editor.set_focus_on_track(i); + return false; + } + } + + return false; +} Interface::Interface(Gtk::Application *p_application, AudioEffectFactory *p_fx_factory, Theme *p_theme, KeyBindings *p_key_bindings) : add_effect_dialog(p_fx_factory), song_file(&song, p_fx_factory), @@ -1577,6 +1620,7 @@ Interface::Interface(Gtk::Application *p_application, AudioEffectFactory *p_fx_f //setup song _update_song_mixing_parameters(); SoundDriverManager::set_mix_callback(_process_audio); + MIDIDriverManager::set_event_callback(_process_midi); playback_timer = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Interface::_playback_timer_callback), 10, Glib::PRIORITY_DEFAULT); diff --git a/gui/interface.h b/gui/interface.h index cece114..c132cca 100644 --- a/gui/interface.h +++ b/gui/interface.h @@ -248,6 +248,7 @@ class Interface : public Gtk::ApplicationWindow { bool _close_request(GdkEventAny *event); static void _process_audio(AudioFrame *p_frames, int p_amount); + static void _process_midi(double p_delta, const MIDIEvent &p_event); void _update_song_process_order(); @@ -270,6 +271,8 @@ class Interface : public Gtk::ApplicationWindow { Gtk::Label export_wav_label; String last_wav_export_path; + bool _on_editor_window_gained_focus(GdkEventFocus *, Track *p_track); + public: void add_editor_plugin_function(EffectEditorPluginFunc p_plugin); diff --git a/gui/pattern_editor.cpp b/gui/pattern_editor.cpp index 69b9f7f..c3eaae0 100644 --- a/gui/pattern_editor.cpp +++ b/gui/pattern_editor.cpp @@ -3813,6 +3813,23 @@ void PatternEditor::set_playback_pos(int p_pattern, Tick p_tick) { } } +void PatternEditor::set_focus_on_track(int p_track) { + + if (song->get_event_column_track(cursor.column) == p_track) { + return; //it's already there + } + //not there, look for it + + for (int i = 0; i < song->get_event_column_count(); i++) { + if (song->get_event_column_track(i) == p_track) { + cursor.column = i; + cursor.field = 0; + queue_draw(); + return; + } + } +} + void PatternEditor::on_parsing_error( const Glib::RefPtr §ion, const Glib::Error &error) {} diff --git a/gui/pattern_editor.h b/gui/pattern_editor.h index 1ca650e..d1f5e9d 100644 --- a/gui/pattern_editor.h +++ b/gui/pattern_editor.h @@ -256,6 +256,7 @@ class PatternEditor : public Gtk::Widget { void redraw_and_validate_cursor(); + void set_focus_on_track(int p_track); void initialize_menus(); PatternEditor(Song *p_song, UndoRedo *p_undo_redo, Theme *p_theme, KeyBindings *p_bindings); diff --git a/gui/settings_dialog.cpp b/gui/settings_dialog.cpp index 529f690..880f14d 100644 --- a/gui/settings_dialog.cpp +++ b/gui/settings_dialog.cpp @@ -239,6 +239,22 @@ void SettingsDialog::_driver_changed() { } } } + +void SettingsDialog::_midi_input_driver_changed() { + + Gtk::TreeModel::iterator iter = midi_input_driver_combo.get_active(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + if (row) { + //Get the data for the selected row, using our knowledge of the tree + //model: + int id = row[model_columns.index]; + + MIDIDriverManager::init_input_driver(id); + _save_settings(); + } + } +} void SettingsDialog::_driver_freq_changed() { Gtk::TreeModel::iterator iter = frequency_combo.get_active(); @@ -554,10 +570,19 @@ void SettingsDialog::_save_settings() { } } + std::string midi_driver_id; + if (MIDIDriverManager::get_current_input_driver_index() >= 0) { + MIDIInputDriver *driver = MIDIDriverManager::get_input_driver(MIDIDriverManager::get_current_input_driver_index()); + if (driver) { + midi_driver_id = driver->get_id().utf8().get_data(); + } + } + audio_node.add("id", driver_id); audio_node.add("mixing_hz", SoundDriverManager::get_mix_frequency()); audio_node.add("buffer_size", SoundDriverManager::get_buffer_size()); audio_node.add("block_size", SoundDriverManager::get_step_buffer_size()); + audio_node.add("midi_in_id", midi_driver_id); node.add("audio", audio_node); } @@ -728,6 +753,29 @@ SettingsDialog::SettingsDialog(Theme *p_theme, KeyBindings *p_key_bindings, Audi fx_factory = p_fx_factory; key_bindings = p_key_bindings; theme = p_theme; + { + + midi_input_driver_list_store = Gtk::ListStore::create(model_columns); + midi_input_driver_combo.set_model(midi_input_driver_list_store); + + for (int i = 0; i < MIDIDriverManager::get_input_driver_count(); i++) { + MIDIInputDriver *midi_input_driver = MIDIDriverManager::get_input_driver(i); + + Gtk::TreeModel::Row row = *(midi_input_driver_list_store->append()); + row[model_columns.name] = midi_input_driver->get_name().utf8().get_data(); + row[model_columns.index] = i; + midi_input_driver_rows.push_back(row); + } + + midi_input_driver_combo.pack_start(model_columns.name); + + int active_index = MIDIDriverManager::get_current_input_driver_index(); + if (active_index >= 0) { + midi_input_driver_combo.set_active(midi_input_driver_rows[active_index]); + } + } + midi_input_driver_combo.signal_changed().connect(sigc::mem_fun(*this, &SettingsDialog::_midi_input_driver_changed)); + { driver_list_store = Gtk::ListStore::create(model_columns); @@ -859,6 +907,12 @@ SettingsDialog::SettingsDialog(Theme *p_theme, KeyBindings *p_key_bindings, Audi step_combo.set_hexpand(true); sound_settings_grid.attach(step_combo, 1, 3, 1, 1); + midi_input_driver_label.set_text("MIDI-In Driver: "); + midi_input_driver_label.set_hexpand(true); + sound_settings_grid.attach(midi_input_driver_label, 0, 4, 1, 1); + midi_input_driver_combo.set_hexpand(true); + sound_settings_grid.attach(midi_input_driver_combo, 1, 4, 1, 1); + sound_settings_grid.set_margin_left(8); sound_settings_grid.set_margin_right(8); sound_settings_grid.set_margin_bottom(8); diff --git a/gui/settings_dialog.h b/gui/settings_dialog.h index 50344a5..597b98b 100644 --- a/gui/settings_dialog.h +++ b/gui/settings_dialog.h @@ -2,6 +2,7 @@ #define SETTINGS_DIALOG_H #include "engine/audio_effect.h" +#include "engine/midi_driver_manager.h" #include "engine/sound_driver_manager.h" #include "gui/color_theme.h" #include "gui/key_bindings.h" @@ -74,6 +75,9 @@ class SettingsDialog : public Gtk::MessageDialog { ModelColumns model_columns; + Glib::RefPtr midi_input_driver_list_store; + Vector midi_input_driver_rows; + Glib::RefPtr driver_list_store; Vector driver_rows; @@ -91,6 +95,8 @@ class SettingsDialog : public Gtk::MessageDialog { Gtk::Frame sound_settings_frame; Gtk::Grid sound_settings_grid; + Gtk::ComboBox midi_input_driver_combo; + Gtk::Label midi_input_driver_label; Gtk::ComboBox driver_combo; Gtk::Label driver_label; Gtk::ComboBox frequency_combo; @@ -103,6 +109,7 @@ class SettingsDialog : public Gtk::MessageDialog { Gtk::Frame plugin_path_frame; Gtk::VBox plugin_path_vbox; + void _midi_input_driver_changed(); void _driver_changed(); void _driver_freq_changed(); void _driver_buffer_changed(); diff --git a/sytrax.includes b/sytrax.includes index b4cfce4..7fa2745 100644 --- a/sytrax.includes +++ b/sytrax.includes @@ -12,3 +12,5 @@ drivers/vst2/vst drivers/vst2 effects/internal effects +drivers/rtmidi/rtmidi +drivers/rtmidi