diff --git a/.clang-format b/.clang-format
index 2dcae007..b34ee789 100644
--- a/.clang-format
+++ b/.clang-format
@@ -2,7 +2,8 @@
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
Language: Cpp
# BasedOnStyle: WebKit
-AccessModifierOffset: -2
+IndentAccessModifiers: false
+AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
@@ -36,7 +37,7 @@ BraceWrapping:
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
- BeforeCatch: false
+ BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: true
IndentBraces: false
@@ -111,7 +112,7 @@ SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
-SpaceAfterTemplateKeyword: true
+SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
diff --git a/.gitignore b/.gitignore
index afdefd7b..b33bca0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.DS_Store
+.cache
.svn
.vs
/builds
@@ -29,3 +30,4 @@ artifacts
xcuserdata
.ccls-cache
/SIDFactoryII/x64/Debug
+compile_commands.json
diff --git a/Makefile b/Makefile
index b0616ca0..8fceac11 100644
--- a/Makefile
+++ b/Makefile
@@ -42,8 +42,19 @@ EXE=$(ARTIFACTS_FOLDER)/$(APP_NAME)
CC=g++
CC_FLAGS=$(shell sdl2-config --cflags) -I$(SOURCE) -D_SF2_$(PLATFORM) -D_BUILD_NR=\"$(BUILD_NR)\" -std=gnu++14 -g
LINKER_FLAGS=$(shell sdl2-config --libs) -lstdc++ -flto
+
+ifeq ($(PLATFORM),LINUX)
+ CC_FLAGS := $(CC_FLAGS) -DUNIX_JACK
+ LINKER_FLAGS := $(LINKER_FLAGS) -ljack
+endif
+
ifeq ($(PLATFORM),MACOS)
- LINKER_FLAGS := $(LINKER_FLAGS) -framework ApplicationServices
+ CC_FLAGS := $(CC_FLAGS) -D__MACOSX_CORE__
+ LINKER_FLAGS := $(LINKER_FLAGS) \
+ -framework ApplicationServices \
+ -framework CoreMIDI \
+ -framework CoreAudio \
+ -framework CoreFoundation
endif
ifneq ($(TARGET),DEBUG)
@@ -115,7 +126,8 @@ clean:
.PHONY: run
run: $(EXE)
- cd $(ARTIFACTS_FOLDER) && ./$(APP_NAME)
+ cd $(ARTIFACTS_FOLDER) && ./$(APP_NAME) "../SIDFactoryII/music/Laxity/Laxity - Farfisa.sf2"
+
.PHONY: debug
debug: $(EXE)
diff --git a/README.md b/README.md
index fa676188..68d513ab 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,13 @@ Please report issues in our [issue tracker](https://github.com/Chordian/sidfacto
![Commits since last
release](https://img.shields.io/github/commits-since/chordian/sidfactory2/release-20231002)
+### Next release
+
+- Added: [#28](https://github.com/Chordian/sidfactory2/issues/28) ASID support!
+ Use real hardware that supports the ASID protocol (for example the TherapSID)
+- Added: Config option `Playback.ASID.MidiInterface` to select the MIDI
+ interface for ASID playback
+
### Build 20231002
- Added: [#102](https://github.com/Chordian/sidfactory2/issues/102) Visualizers
diff --git a/SIDFactoryII/SIDFactoryII.vcxproj b/SIDFactoryII/SIDFactoryII.vcxproj
index 8032479f..689f94e4 100644
--- a/SIDFactoryII/SIDFactoryII.vcxproj
+++ b/SIDFactoryII/SIDFactoryII.vcxproj
@@ -112,7 +112,7 @@
true
.;.\source;.\source\libraries;..\libs\SDL2-2.0.12\include;%(AdditionalIncludeDirectories)
Speed
- _CRT_SECURE_NO_WARNINGS;_SF2_WINDOWS;__SSE__;%(PreprocessorDefinitions)
+ _CRT_SECURE_NO_WARNINGS;__WINDOWS_MM__;_SF2_WINDOWS;__SSE__;%(PreprocessorDefinitions)
stdcpp17
AnySuitable
MultiThreaded
@@ -121,7 +121,7 @@
true
true
..\libs\SDL2-2.0.12\lib\x86;%(AdditionalLibraryDirectories)
- SDL2.lib;SDL2main.lib;%(AdditionalDependencies)
+ SDL2.lib;SDL2main.lib;winmm.lib;%(AdditionalDependencies)
Windows
UseLinkTimeCodeGeneration
@@ -179,6 +179,83 @@
+
+ NoListing
+ Release\
+ false
+ false
+ Release\
+ Default
+ Default
+ true
+ Column
+ false
+ Prompt
+ false
+ Sync
+ false
+ false
+ false
+ NotSet
+ Precise
+ true
+ false
+ false
+ false
+ AnySuitable
+ true
+ false
+ stdcpp17
+ Default
+ false
+ Release\
+ Release\
+ false
+ Speed
+ true
+ MaxSpeed
+ Release\
+ Cdecl
+ Release\vc143.pdb
+ NotUsing
+ stdafx.h
+ Release\SIDFactoryII.pch
+ false
+ false
+ false
+ true
+ MultiThreaded
+ false
+ false
+ Release\
+ true
+ true
+ false
+ false
+ Default
+ false
+ Release\SIDFactoryII.tlog\
+ false
+ true
+ false
+ true
+ true
+ Level3
+ Release\
+ ProgramDatabase
+ false
+ false
+ InheritWarningLevel
+ true
+ false
+ _CRT_SECURE_NO_WARNINGS;__WINDOWS_MM__;_SF2_WINDOWS;__SSE__;_MBCS;
+ false
+ false
+ true
+ true
+ .;.\source;.\source\libraries;..\libs\SDL2-2.0.12\include;
+ true
+
@@ -246,6 +323,7 @@
+
@@ -287,6 +365,7 @@
+
@@ -304,11 +383,89 @@
+
+ NoListing
+ Release\
+ false
+ false
+ Release\
+ Default
+ Default
+ true
+ Column
+ false
+ Prompt
+ false
+ Sync
+ false
+ false
+ false
+ NotSet
+ Precise
+ true
+ false
+ false
+ false
+ AnySuitable
+ true
+ false
+ stdcpp17
+ Default
+ false
+ Release\
+ Release\
+ false
+ Speed
+ true
+ MaxSpeed
+ Release\
+ Cdecl
+ Release\vc143.pdb
+ NotUsing
+ stdafx.h
+ Release\SIDFactoryII.pch
+ false
+ false
+ false
+ true
+ MultiThreaded
+ false
+ false
+ Release\
+ true
+ true
+ false
+ false
+ Default
+ false
+ Release\SIDFactoryII.tlog\
+ false
+ true
+ false
+ true
+ true
+ Level3
+ Release\
+ ProgramDatabase
+ false
+ false
+ InheritWarningLevel
+ true
+ false
+ _CRT_SECURE_NO_WARNINGS;__WINDOWS_MM__;_SF2_WINDOWS;__SSE__;_MBCS;
+ false
+ false
+ true
+ true
+ .;.\source;.\source\libraries;..\libs\SDL2-2.0.12\include;
+ true
+
+
@@ -357,6 +514,7 @@
+
@@ -429,6 +587,7 @@
+
@@ -478,6 +637,7 @@
+
@@ -501,6 +661,7 @@
+
diff --git a/SIDFactoryII/SIDFactoryII.vcxproj.filters b/SIDFactoryII/SIDFactoryII.vcxproj.filters
index d9fc13f7..c73d2fb5 100644
--- a/SIDFactoryII/SIDFactoryII.vcxproj.filters
+++ b/SIDFactoryII/SIDFactoryII.vcxproj.filters
@@ -623,6 +623,18 @@
source\runtime\editor\dialogs
+
+ source
+
+
+ source
+
+
+ source
+
+
+ source\runtime\execution
+
@@ -1197,6 +1209,12 @@
source\runtime\editor\dialogs
+
+
+
+
+ source\runtime\execution
+
diff --git a/SIDFactoryII/config.ini b/SIDFactoryII/config.ini
index 023a4a86..92c943f4 100644
--- a/SIDFactoryII/config.ini
+++ b/SIDFactoryII/config.ini
@@ -61,6 +61,7 @@ Editor.Confirm.QuickSave = 1 // If you set this to 1, a confi
Playback.StopEmulationIfDriverStops = 1 // If the driver stops (at the end of a jingle, for instance), setting this value to non-zero
// will stop the emulation and follow play also.
+Playback.ASID.MidiInterface = ""
// Virtual piano keyboard layout
// From left to right, the keys to assign to notes, starting at C and upwards
diff --git a/SIDFactoryII/source/foundation/base/toptional.h b/SIDFactoryII/source/foundation/base/toptional.h
new file mode 100644
index 00000000..004e1f18
--- /dev/null
+++ b/SIDFactoryII/source/foundation/base/toptional.h
@@ -0,0 +1,97 @@
+#pragma once
+
+#include
+#include "assert.h"
+
+namespace Foundation
+{
+ template
+ class TOptional final
+ {
+ public:
+ TOptional()
+ : m_IsValid(false)
+ {
+ }
+
+ TOptional(const T& InValue)
+ : m_IsValid(true)
+ , m_Value(InValue)
+ {
+ }
+
+ TOptional(T&& InValue)
+ : m_IsValid(true)
+ , m_Value(std::move(InValue))
+ {
+ }
+
+ TOptional(const TOptional& InOther)
+ : m_IsValid(InOther.m_IsValid)
+ {
+ if(m_IsValid)
+ new (&m_Value) T(InOther.m_Value);
+ }
+
+ TOptional(TOptional&& InOther)
+ : m_IsValid(InOther.m_IsValid)
+ {
+ if (m_IsValid)
+ new (&m_Value) T(std::move(InOther.m_Value));
+ }
+
+ TOptional& operator=(const TOptional& InOther)
+ {
+ if (m_IsValid)
+ m_Value.~T();
+
+ m_IsValid = InOther.m_IsValid;
+ if (m_IsValid)
+ new (&m_Value) T(InOther.m_Value);
+
+ return *this;
+ }
+
+ TOptional& operator=(TOptional&& InOther)
+ {
+ m_IsValid = InOther.m_IsValid;
+ InOther.IsValid = false;
+
+ if (m_IsValid)
+ new (&m_Value) T(std::move(InOther.m_Value));
+
+ return *this;
+ }
+
+ ~TOptional()
+ {
+ if (m_IsValid)
+ m_Value.~T();
+ }
+
+ bool IsValid() const
+ {
+ return m_IsValid;
+ }
+
+ const T& Get() const
+ {
+ FOUNDATION_ASSERT(m_IsValid);
+ return m_Value;
+ }
+
+ T& Get()
+ {
+ FOUNDATION_ASSERT(m_IsValid);
+ return m_Value;
+ }
+
+ private:
+ bool m_IsValid;
+
+ union
+ {
+ T m_Value;
+ };
+ };
+}
\ No newline at end of file
diff --git a/SIDFactoryII/source/libraries/rtmidi/RtMidi.cpp b/SIDFactoryII/source/libraries/rtmidi/RtMidi.cpp
new file mode 100644
index 00000000..2216d50d
--- /dev/null
+++ b/SIDFactoryII/source/libraries/rtmidi/RtMidi.cpp
@@ -0,0 +1,5266 @@
+/**********************************************************************/
+/*! \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-2023 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(__APPLE__)
+#include
+#endif
+
+#if (TARGET_OS_IPHONE == 1)
+
+ #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime
+ #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos
+
+ #include
+ class CTime2nsFactor
+ {
+ public:
+ CTime2nsFactor()
+ {
+ mach_timebase_info_data_t tinfo;
+ mach_timebase_info(&tinfo);
+ Factor = (double)tinfo.numer / tinfo.denom;
+ }
+ static double Factor;
+ };
+ double CTime2nsFactor::Factor;
+ static CTime2nsFactor InitTime2nsFactor;
+ #undef AudioGetCurrentHostTime
+ #undef AudioConvertHostTimeToNanos
+ #define AudioGetCurrentHostTime (uint64_t) mach_absolute_time
+ #define AudioConvertHostTimeToNanos(t) t *CTime2nsFactor::Factor
+ #define EndianS32_BtoN(n) n
+
+#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
+
+// Default for Windows UWP is to enable a workaround to fix BLE-MIDI IN ports'
+// wrong timestamps that occur at least in Windows 10 21H2;
+// this flag can be defined (e.g. in your project file)
+// to disable this behavior.
+//#define RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS
+
+// **************************************************************** //
+//
+// MidiInApi and MidiOutApi subclass prototypes.
+//
+// **************************************************************** //
+
+#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(__WINDOWS_UWP__) && !defined(TARGET_IPHONE_OS) && !defined(__WEB_MIDI_API__) && !defined(__AMIDI__)
+ #define __RTMIDI_DUMMY__
+#endif
+
+#if defined(__MACOSX_CORE__)
+#include
+
+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:
+ MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw();
+ 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:
+ MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw();
+ 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(__WINDOWS_UWP__)
+
+class MidiInWinUWP : public MidiInApi
+{
+public:
+ MidiInWinUWP(const std::string& clientName, unsigned int queueSizeLimit);
+ ~MidiInWinUWP(void) override;
+ RtMidi::Api getCurrentApi(void) override { return RtMidi::WINDOWS_UWP; };
+ void openPort(unsigned int portNumber, const std::string& portName) override;
+ void openVirtualPort(const std::string& portName) override;
+ void closePort(void) override;
+ void setClientName(const std::string& clientName) override;
+ void setPortName(const std::string& portName) override;
+ unsigned int getPortCount(void) override;
+ std::string getPortName(unsigned int portNumber) override;
+ double getMessage(std::vector* message) override;
+
+protected:
+ void initialize(const std::string& clientName) override;
+};
+
+class MidiOutWinUWP : public MidiOutApi
+{
+public:
+ MidiOutWinUWP(const std::string& clientName);
+ ~MidiOutWinUWP(void) override;
+ RtMidi::Api getCurrentApi(void) override { return RtMidi::WINDOWS_UWP; };
+ void openPort(unsigned int portNumber, const std::string& portName) override;
+ void openVirtualPort(const std::string& portName) override;
+ void closePort(void) override;
+ void setClientName(const std::string& clientName) override;
+ void setPortName(const std::string& portName) override;
+ unsigned int getPortCount(void) override;
+ std::string getPortName(unsigned int portNumber) override;
+ void sendMessage(const unsigned char* message, size_t size) override;
+
+protected:
+ void initialize(const std::string& clientName) override;
+};
+
+#endif
+
+#if defined(__WEB_MIDI_API__)
+
+class MidiInWeb : public MidiInApi
+{
+ std::string client_name{};
+ std::string web_midi_id{};
+ int open_port_number{-1};
+
+ public:
+ MidiInWeb(const std::string &/*clientName*/, unsigned int queueSizeLimit );
+ ~MidiInWeb( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; };
+ 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 onMidiMessage( uint8_t* data, double domHishResTimeStamp );
+
+ protected:
+ void initialize( const std::string& clientName );
+};
+
+class MidiOutWeb: public MidiOutApi
+{
+ std::string client_name{};
+ std::string web_midi_id{};
+ int open_port_number{-1};
+
+ public:
+ MidiOutWeb( const std::string &clientName );
+ ~MidiOutWeb( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; };
+ 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(__AMIDI__)
+
+#define LOG_TAG "RtMidi"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class MidiInAndroid : public MidiInApi
+{
+ public:
+ MidiInAndroid(const std::string &/*clientName*/, unsigned int queueSizeLimit );
+ ~MidiInAndroid( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::ANDROID_AMIDI; };
+ 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 onMidiMessage( uint8_t* data, double domHishResTimeStamp );
+
+ void initialize( const std::string& clientName );
+ void connect();
+ AMidiDevice* receiveDevice = NULL;
+ AMidiOutputPort* midiOutputPort = NULL;
+ pthread_t readThread;
+ std::atomic reading = ATOMIC_VAR_INIT(false);
+ static void* pollMidi(void* context);
+ double lastTime;
+};
+
+class MidiOutAndroid: public MidiOutApi
+{
+ public:
+ MidiOutAndroid( const std::string &clientName );
+ ~MidiOutAndroid( void );
+ RtMidi::Api getCurrentApi( void ) { return RtMidi::ANDROID_AMIDI; };
+ 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 );
+
+ void initialize( const std::string& clientName );
+ void connect();
+ AMidiDevice* sendDevice = NULL;
+ AMidiInputPort* midiInputPort = NULL;
+};
+
+#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;
+}
+
+RtMidi::RtMidi(RtMidi&& other) noexcept {
+ rtapi_ = other.rtapi_;
+ other.rtapi_ = nullptr;
+}
+
+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" },
+ { "web" , "Web MIDI API" },
+ { "winuwp" , "Windows UWP" },
+ { "amidi" , "Android MIDI API" },
+};
+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(__WINDOWS_UWP__)
+ RtMidi::WINDOWS_UWP,
+#endif
+#if defined(__WEB_MIDI_API__)
+ RtMidi::WEB_MIDI_API,
+#endif
+#if defined(__WEB_MIDI_API__)
+ RtMidi::WEB_MIDI_API,
+#endif
+#if defined(__AMIDI__)
+ RtMidi::ANDROID_AMIDI,
+#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 < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS)
+ return "";
+ return rtmidi_api_names[api][0];
+}
+
+std::string RtMidi :: getApiDisplayName( RtMidi::Api api )
+{
+ if (api < RtMidi::UNSPECIFIED || 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(__WINDOWS_UWP__)
+ if (api == WINDOWS_UWP)
+ rtapi_ = new MidiInWinUWP(clientName, queueSizeLimit);
+#endif
+#if defined(__MACOSX_CORE__)
+ if ( api == MACOSX_CORE )
+ rtapi_ = new MidiInCore( clientName, queueSizeLimit );
+#endif
+#if defined(__WEB_MIDI_API__)
+ if ( api == WEB_MIDI_API )
+ rtapi_ = new MidiInWeb( clientName, queueSizeLimit );
+#endif
+#if defined(__AMIDI__)
+ if ( api == ANDROID_AMIDI )
+ rtapi_ = new MidiInAndroid( 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< RtMidi::Api > apis;
+ getCompiledApi( apis );
+ for ( unsigned int i=0; igetPortCount() ) 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(__WINDOWS_UWP__)
+ if (api == WINDOWS_UWP)
+ rtapi_ = new MidiOutWinUWP(clientName);
+#endif
+#if defined(__MACOSX_CORE__)
+ if ( api == MACOSX_CORE )
+ rtapi_ = new MidiOutCore( clientName );
+#endif
+#if defined(__WEB_MIDI_API__)
+ if ( api == WEB_MIDI_API )
+ rtapi_ = new MidiOutWeb( clientName );
+#endif
+#if defined(__AMIDI__)
+ if ( api == ANDROID_AMIDI )
+ rtapi_ = new MidiOutAndroid( 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< RtMidi::Api > apis;
+ getCompiledApi( apis );
+ for ( unsigned int i=0; igetPortCount() ) 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;
+}
+
+void MidiInApi :: setBufferSize( unsigned int size, unsigned int count )
+{
+ inputData_.bufferSize = size;
+ inputData_.bufferCount = count;
+}
+
+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.
+
+// These are not available on iOS.
+#if (TARGET_OS_IPHONE == 0)
+ #include
+ #include
+#endif
+
+// 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;
+};
+
+static MIDIClientRef CoreMidiClientSingleton = 0;
+
+void RtMidi_setCoreMidiClientSingleton(MIDIClientRef client){
+ CoreMidiClientSingleton = client;
+}
+
+void RtMidi_disposeCoreMidiClientSingleton(){
+ if (CoreMidiClientSingleton == 0){
+ return;
+ }
+ MIDIClientDispose( CoreMidiClientSingleton );
+ CoreMidiClientSingleton = 0;
+}
+
+//*********************************************************************//
+// 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; inumPackets; ++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; jdata[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_);
+ if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
+ delete data;
+}
+
+MIDIClientRef MidiInCore::getCoreMidiClientSingleton(const std::string& clientName) throw() {
+
+ if (CoreMidiClientSingleton == 0){
+ // 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 0;
+ }
+ CFRelease( name );
+
+ CoreMidiClientSingleton = client;
+ }
+
+ return CoreMidiClientSingleton;
+}
+
+void MidiInCore :: initialize( const std::string& clientName )
+{
+ // Set up our client.
+ MIDIClientRef client = getCoreMidiClientSingleton(clientName);
+
+ // Save our api-specific connection information.
+ CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
+ data->client = client;
+ data->endpoint = 0;
+ apiData_ = (void *) data;
+ inputData_.apiData = (void *) data;
+}
+
+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 ) {
+ 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 );
+ 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 );
+ 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 CreateEndpointName( 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 );
+ }
+
+ // some MIDI devices have a leading space in endpoint name. trim
+ CFStringTrim(result, CFSTR(" "));
+
+ 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 );
+ }
+ }
+ // 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 );
+ CFRetain( str );
+ 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 );
+ CFRetain( str );
+ return str;
+ } else {
+ if ( CFStringGetLength( str ) == 0 ) {
+ 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 );
+ }
+ }
+ }
+ return result;
+}
+
+// This function was submitted by Douglas Casey Tucker and apparently
+// derived largely from PortMidi.
+static CFStringRef CreateConnectedEndpointName( 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= 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 = CreateConnectedEndpointName( 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_);
+ if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
+ delete data;
+}
+
+MIDIClientRef MidiOutCore::getCoreMidiClientSingleton(const std::string& clientName) throw() {
+
+ if (CoreMidiClientSingleton == 0){
+ // 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 0;
+ }
+ CFRelease( name );
+
+ CoreMidiClientSingleton = client;
+ }
+
+ return CoreMidiClientSingleton;
+}
+
+void MidiOutCore :: initialize( const std::string& clientName )
+{
+ // Set up our client.
+ MIDIClientRef client = getCoreMidiClientSingleton(clientName);
+
+ // Save our api-specific connection information.
+ CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
+ data->client = client;
+ data->endpoint = 0;
+ apiData_ = (void *) data;
+}
+
+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 = CreateConnectedEndpointName(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 ) {
+ 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 );
+ 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;
+ }
+
+ if ( message[0] != 0xF0 && nBytes > 3 ) {
+ errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ MIDITimeStamp timeStamp = AudioGetCurrentHostTime();
+ CoreMidiData *data = static_cast (apiData_);
+ OSStatus result;
+
+ ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes;
+ Byte buffer[bufsize+16]; // pad for other struct members
+ ByteCount listSize = sizeof( buffer );
+ MIDIPacketList *packetList = (MIDIPacketList*)buffer;
+
+ ByteCount remainingBytes = nBytes;
+ while ( remainingBytes ) {
+ MIDIPacket *packet = MIDIPacketListInit( packetList );
+ // A MIDIPacketList can only contain a maximum of 64K of data, so if our message is longer,
+ // break it up into chunks of 64K or less and send out as a MIDIPacketList with only one
+ // MIDIPacket. Here, we reuse the memory allocated above on the stack for all.
+ ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes;
+ 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 int requestedBufferSize;
+ 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;
+ 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;
+ data->bufferSize = inputData_.bufferSize;
+ 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 ) ||
+ ( ( caps & SND_SEQ_PORT_CAP_NO_EXPORT ) != 0 ) ) 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 )
+{
+ long 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;
+ }
+ }
+
+ for ( unsigned int i=0; ibuffer[i] = message[i];
+
+ unsigned int offset = 0;
+ while (offset < nBytes) {
+ 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 );
+ result = snd_midi_event_encode( data->coder, data->buffer + offset,
+ (long)(nBytes - offset), &ev );
+ if ( result < 0 ) {
+ errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ if ( ev.type == SND_SEQ_EVENT_NONE ) {
+ errorString_ = "MidiOutAlsa::sendMessage: incomplete message!";
+ error( RtMidiError::WARNING, errorString_ );
+ return;
+ }
+
+ offset += result;
+
+ // 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;
+}
+
+// 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;
+ std::vector sysexBuffer;
+ 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; imessage.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.
+ data->sysexBuffer.resize( inputData_.bufferCount );
+ for ( unsigned int i=0; i < inputData_.bufferCount; ++i ) {
+ data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];
+ data->sysexBuffer[i]->lpData = new char[ inputData_.bufferSize ];
+ data->sysexBuffer[i]->dwBufferLength = inputData_.bufferSize;
+ 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 ( size_t i=0; i < data->sysexBuffer.size(); ++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_);
+ // Disabled because midiOutReset triggers 0x7b (if any note was ON) and 0x79 "Reset All
+ // Controllers" (to all 16 channels) CC messages which is undesirable (see issue #222)
+ // 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; ioutHandle, &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; ioutHandle, packet );
+ if ( result != MMSYSERR_NOERROR ) {
+ errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message.";
+ error( RtMidiError::DRIVER_ERROR, errorString_ );
+ }
+ }
+}
+
+#endif // __WINDOWS_MM__
+
+
+//*********************************************************************//
+// API: Universal Windows Platform (UWP)
+//*********************************************************************//
+
+// C++/WinRT
+// https://github.com/microsoft/cppwinrt
+//
+// UWP MIDI API
+// https://docs.microsoft.com/en-us/windows/uwp/audio-video-camera/midi
+//
+// Example implementation using UWP MIDI in C++/WinRT
+// https://github.com/trueroad/uwp_midiio
+
+#if defined(__WINDOWS_UWP__)
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace winrt;
+using namespace Windows::Foundation;
+using namespace Windows::Devices::Enumeration;
+using namespace Windows::Devices::Midi;
+using namespace Windows::Storage::Streams;
+using namespace Windows::Security::Cryptography;
+
+// Class for initializing C++/WinRT
+class UWPMidiInit
+{
+public:
+ UWPMidiInit()
+ {
+ winrt::init_apartment();
+ }
+};
+
+// Class for handling UWP MIDI
+class UWPMidiClass
+{
+public:
+ // Structure to store MIDI port name and ID
+ struct port
+ {
+ std::string name;
+ std::wstring id;
+ std::string hex_id;
+ std::string display_name;
+ };
+
+ UWPMidiClass(MidiApi& midi_api) :
+ midi_api_(midi_api)
+ {
+ }
+
+ ~UWPMidiClass()
+ {
+ close();
+ }
+
+ // Initialize for MIDI IN
+ void in_init(MidiInApi::RtMidiInData* input_data)
+ {
+ input_data_ = input_data;
+
+ try
+ {
+ ports_ = list_ports(MidiInPort::GetDeviceSelector());
+ }
+ catch (hresult_error const& ex)
+ {
+ raise_hresult_error("UWPMidiClass::in_init: ", ex);
+ }
+ sort_display_name(ports_);
+ }
+
+ // Initialize for MIDI OUT
+ void out_init()
+ {
+ try
+ {
+ ports_ = list_ports(MidiOutPort::GetDeviceSelector());
+ fix_display_name(list_ports(MidiInPort::GetDeviceSelector()), ports_);
+ }
+ catch (hresult_error const& ex)
+ {
+ raise_hresult_error("UWPMidiClass::out_init: ", ex);
+ }
+ sort_display_name(ports_);
+ }
+
+ size_t get_num_ports()
+ {
+ return ports_.size();
+ }
+
+ std::string get_port_name(size_t n)
+ {
+ return ports_[n].display_name;
+ }
+
+ bool in_open(size_t port_number);
+ bool out_open(size_t port_number);
+ void close();
+
+ void midi_in_callback(const MidiInPort&, const MidiMessageReceivedEventArgs& e);
+ bool send_buffer(const unsigned char* buff, size_t len);
+
+ // Raise RtMidi error for hresult error
+ void raise_hresult_error(std::string_view message, hresult_error const& ex)
+ {
+ std::ostringstream ss;
+ ss << message << "exception HRESULT 0x" << std::hex << ex.code() << ", "
+ << utf16_to_utf8(static_cast(ex.message()))
+ << "\n";
+ midi_api_.error(RtMidiError::DRIVER_ERROR, ss.str());
+ }
+
+ // Mutex for MIDI port open/close
+ std::mutex mtx_open_close_;
+ // Mutex for MIDI IN message queue access
+ std::mutex mtx_queue_;
+
+private:
+ std::vector list_ports(winrt::hstring device_selector);
+ void fix_display_name(const std::vector& in_ports,
+ std::vector& out_ports);
+ void sort_display_name(std::vector& ports);
+ std::string utf16_to_utf8(const std::wstring_view wstr);
+
+ template
+ IMidiPort_T open(size_t port_number);
+
+ // MidiApi class
+ MidiApi& midi_api_;
+
+ // List of MIDI ports
+ std::vector ports_;
+ // MIDI IN port
+ MidiInPort in_port_{ nullptr };
+ // MIDI OUT port
+ IMidiOutPort out_port_{ nullptr };
+ // Backup initial MessageReceived event token
+ winrt::event_token before_token_;
+ // Input data
+ MidiInApi::RtMidiInData* input_data_{ nullptr };
+ // Last timestamp
+ std::chrono::duration last_time_{ 0 };
+
+ // C++/WinRT initializer
+ static UWPMidiInit uwp_midi_init_;
+ // Regex pattern to extract 8 hex digits from UWP MIDI ID string
+ static const std::wregex hex_id_pattern_;
+
+#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS
+ // QueryPerformanceFrequency
+ LONGLONG qpc_freq_{ 0 };
+ // Last QueryPerformanceCounter
+ LONGLONG before_qpc_;
+ // Weather overflow low occurred or not
+ bool b_overflow_low_{ false };
+
+ // BLE-MIDI timestamp periods
+ inline constexpr static std::chrono::duration ble_midi_period_low_{ std::chrono::milliseconds{128} };
+ inline constexpr static std::chrono::duration ble_midi_period_high_{ std::chrono::milliseconds{8192} };
+ // QPC threshold 4096 ms
+ inline constexpr static LONGLONG qpc_threshold_{ 4096 };
+
+ // Regex pattern to detect BLE-MIDI IN
+ static const std::wregex ble_midi_pattern_;
+#endif
+};
+
+// C++/WinRT initializer
+UWPMidiInit UWPMidiClass::uwp_midi_init_;
+// Regex pattern to extract 8 hex digits from UWP MIDI ID string
+const std::wregex UWPMidiClass::hex_id_pattern_{ std::wregex(L"#MIDII_([0-9A-F]{8})\\..+#") };
+
+#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS
+const std::wregex UWPMidiClass::ble_midi_pattern_{ std::wregex(L"#MIDII_[0-9A-F]{8}\\.BLE[0-9]{2}#") };
+#endif
+
+// Find and create a list of UWP MIDI ports
+std::vector UWPMidiClass::list_ports(winrt::hstring device_selector)
+{
+ const auto devs{ DeviceInformation::FindAllAsync(device_selector).get() };
+
+ std::vector retval;
+ for (const auto& d : devs)
+ {
+ port p;
+ p.name = utf16_to_utf8(d.Name());
+ p.id = d.Id();
+
+ std::wsmatch m;
+ if (std::regex_search(p.id, m, hex_id_pattern_))
+ {
+ // Ordinary MIDI ports
+ // Append hex digits extracted from the UWP MIDI ID string to the port name.
+ p.hex_id = utf16_to_utf8(m[1].str());
+
+ std::ostringstream ss;
+ ss << p.name
+ << " [ "
+ << p.hex_id
+ << " ]";
+ p.display_name = ss.str();
+ }
+ else
+ {
+ // Microsoft GS Wavetable Synth etc.
+ // Unable to extract hex digits from UWP MIDI ID string.
+ // Use the device name as the port name.
+ p.display_name = p.name;
+ }
+
+ retval.push_back(p);
+ }
+ return retval;
+}
+
+// Fix MIDI OUT port names starting with `MIDI` to MIDI IN port names with similar ID strings
+void UWPMidiClass::fix_display_name(const std::vector& in_ports,
+ std::vector& out_ports)
+{
+ for (auto& outp : out_ports)
+ {
+ if (outp.hex_id.empty() ||
+ std::string_view{ outp.name }.substr(0, 4) != "MIDI")
+ continue;
+
+ for (const auto& inp : in_ports)
+ {
+ if (outp.hex_id == inp.hex_id)
+ {
+ outp.display_name = inp.display_name;
+ break;
+ }
+ }
+ }
+}
+
+void UWPMidiClass::sort_display_name(std::vector& ports)
+{
+ std::sort(ports.begin(), ports.end(),
+ [](const auto& lhs, const auto& rhs)
+ {
+ return lhs.display_name < rhs.display_name;
+ });
+}
+
+std::string UWPMidiClass::utf16_to_utf8(const std::wstring_view wstr)
+{
+ auto len{ WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr) };
+ std::string u8str(len, '\0');
+ if (len)
+ WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), u8str.data(), len, nullptr, nullptr);
+ return u8str;
+}
+
+// Open MIDI IN/OUT port
+template
+IMidiPort_T UWPMidiClass::open(size_t port_number)
+{
+ try
+ {
+ auto async{ MidiPort_T::FromIdAsync(ports_[port_number].id) };
+ // Timeout 3 seconds
+ if (async.wait_for(std::chrono::seconds(3)) == AsyncStatus::Completed)
+ return async.GetResults();
+ }
+ catch (hresult_error const& ex)
+ {
+ raise_hresult_error("UWPMidiClass::open: ", ex);
+ }
+ return nullptr;
+}
+
+// Open MIDI IN port
+bool UWPMidiClass::in_open(size_t port_number)
+{
+ if (in_port_)
+ in_port_.Close();
+
+ in_port_ = open(port_number);
+ if (!in_port_)
+ return false;
+
+#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS
+ std::wsmatch m;
+ if (std::regex_search(ports_[port_number].id, m, ble_midi_pattern_))
+ {
+ // BLE-MIDI IN port
+ LARGE_INTEGER li;
+ if (::QueryPerformanceFrequency(&li))
+ qpc_freq_ = li.QuadPart;
+ }
+#endif
+
+ try
+ {
+ before_token_ = in_port_.MessageReceived({ this, &UWPMidiClass::midi_in_callback });
+ }
+ catch (hresult_error const& ex)
+ {
+ raise_hresult_error("UWPMidiClass::in_open: ", ex);
+ }
+
+ return true;
+}
+
+// Open MIDI Out port
+bool UWPMidiClass::out_open(size_t port_number)
+{
+ if (out_port_)
+ out_port_.Close();
+
+ out_port_ = open(port_number);
+ if (!out_port_)
+ return false;
+
+ return true;
+}
+
+// Close MIDI IN/OUT port
+void UWPMidiClass::close()
+{
+ if (in_port_)
+ {
+ if (before_token_)
+ in_port_.MessageReceived(before_token_);
+
+ in_port_.Close();
+ in_port_ = nullptr;
+ }
+ if (out_port_)
+ {
+ out_port_.Close();
+ out_port_ = nullptr;
+ }
+}
+
+// MessageReceived event handler
+void UWPMidiClass::midi_in_callback(const MidiInPort&, const MidiMessageReceivedEventArgs& e)
+{
+#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS
+ LARGE_INTEGER qpc;
+ if (qpc_freq_)
+ {
+ if (!::QueryPerformanceCounter(&qpc))
+ qpc_freq_ = 0;
+ }
+#endif
+
+ const auto& m{ e.Message() };
+ if (!m)
+ return;
+
+ MidiInApi::MidiMessage message;
+ const std::chrono::duration duration{ m.Timestamp() };
+
+ // Calculate time stamp.
+ if (input_data_->firstMessage == true)
+ {
+ message.timeStamp = 0.0;
+ input_data_->firstMessage = false;
+ last_time_ = duration;
+
+#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS
+ if (qpc_freq_)
+ before_qpc_ = qpc.QuadPart;
+#endif
+ }
+ else
+ {
+ auto delta{ duration - last_time_ };
+
+#ifndef RTMIDI_DO_NOT_ENABLE_WORKAROUND_UWP_WRONG_TIMESTAMPS
+ if (qpc_freq_)
+ {
+ if (b_overflow_low_)
+ {
+ if (delta >= ble_midi_period_low_)
+ {
+ // Fix after overflow low
+ // https://github.com/trueroad/BLE_MIDI_packet_data_set#page-7-overflow-both
+ delta -= ble_midi_period_low_;
+ b_overflow_low_ = false;
+ }
+ }
+ else
+ {
+ if ((ble_midi_period_high_ - ble_midi_period_low_) < delta && delta < ble_midi_period_high_ &&
+ ((before_qpc_ - qpc.QuadPart) * 1000 / qpc_freq_) < qpc_threshold_)
+ {
+ // Fix overflow low
+ // https://github.com/trueroad/BLE_MIDI_packet_data_set#page-7-overflow-low
+ delta = delta - ble_midi_period_high_ + ble_midi_period_low_;
+ b_overflow_low_ = true;
+ }
+ }
+
+ before_qpc_ = qpc.QuadPart;
+ }
+#endif
+
+ const std::chrono::duration sec{ delta };
+ message.timeStamp = sec.count();
+ }
+
+ if (((input_data_->ignoreFlags & 0x01) &&
+ (m.Type() == MidiMessageType::SystemExclusive || m.Type() == MidiMessageType::EndSystemExclusive)) ||
+ ((input_data_->ignoreFlags & 0x02) &&
+ (m.Type() == MidiMessageType::MidiTimeCode || m.Type() == MidiMessageType::TimingClock)) ||
+ ((input_data_->ignoreFlags & 0x04) &&
+ m.Type() == MidiMessageType::ActiveSensing))
+ {
+ return;
+ }
+
+ const auto& raw_data{ m.RawData() };
+ const size_t len{ raw_data.Length() };
+
+ if (len)
+ message.bytes.assign(raw_data.data(), raw_data.data() + len);
+
+ last_time_ = duration;
+
+ if (input_data_->usingCallback)
+ {
+ (input_data_->userCallback)(message.timeStamp, &message.bytes, input_data_->userData);
+ }
+ else
+ {
+ std::lock_guard lock(mtx_queue_);
+
+ if (!input_data_->queue.push(message))
+ {
+ std::cerr << "\nMidiInWinUWP: message queue limit reached!!\n\n";
+ }
+ }
+}
+
+// Send MIDI message
+bool UWPMidiClass::send_buffer(const unsigned char* buff, size_t len)
+{
+ if (!out_port_)
+ return false;
+
+ try
+ {
+ out_port_.SendBuffer(CryptographicBuffer::CreateFromByteArray(array_view(buff, buff + len)));
+ }
+ catch (hresult_error const& ex)
+ {
+ raise_hresult_error("UWPMidiClass::send_buffer: ", ex);
+ }
+
+ return true;
+}
+
+//*********************************************************************//
+// API: Windows UWP
+// Class Definitions: MidiInWinUWP
+//*********************************************************************//
+
+MidiInWinUWP::MidiInWinUWP(const std::string& clientName, unsigned int queueSizeLimit)
+ : MidiInApi(queueSizeLimit)
+{
+ MidiInWinUWP::initialize(clientName);
+}
+
+MidiInWinUWP :: ~MidiInWinUWP()
+{
+ // Close a connection if it exists.
+ MidiInWinUWP::closePort();
+
+ // Cleanup.
+ UWPMidiClass *data = static_cast (apiData_);
+ delete data;
+}
+
+void MidiInWinUWP::initialize(const std::string& /*clientName*/)
+{
+ // Save our api-specific connection information.
+ UWPMidiClass* data{ new UWPMidiClass(*this) };
+ data->in_init(&inputData_);
+ apiData_ = static_cast(data);
+
+ // We'll issue a warning here if no devices are available but not
+ // throw an error since the user can plugin something later.
+ const auto nDevices{ data->get_num_ports() };
+ if (nDevices == 0)
+ {
+ errorString_ = "MidiInWinUWP::initialize: no MIDI input devices currently available.";
+ error(RtMidiError::WARNING, errorString_);
+ }
+}
+
+void MidiInWinUWP::openPort(unsigned int portNumber, const std::string&/*portName*/)
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+ std::lock_guard lock(data->mtx_open_close_);
+
+ if (connected_)
+ {
+ errorString_ = "MidiInWinUWP::openPort: a valid connection already exists!";
+ error(RtMidiError::WARNING, errorString_);
+ return;
+ }
+
+ if (data->get_num_ports() == 0)
+ {
+ errorString_ = "MidiInWinUWP::openPort: no MIDI input sources found!";
+ error(RtMidiError::NO_DEVICES_FOUND, errorString_);
+ return;
+ }
+
+ if (portNumber >= data->get_num_ports())
+ {
+ std::ostringstream ost;
+ ost << "MidiInWinUWP::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error(RtMidiError::INVALID_PARAMETER, errorString_);
+ return;
+ }
+
+ if (!data->in_open(portNumber))
+ {
+ errorString_ = "MidiInWinUWP::openPort: error creating Windows UWP MIDI input port.";
+ error(RtMidiError::DRIVER_ERROR, errorString_);
+ return;
+ }
+
+ connected_ = true;
+}
+
+void MidiInWinUWP::openVirtualPort(const std::string&/*portName*/)
+{
+ // This function cannot be implemented for the Windows UWP MIDI API.
+ errorString_ = "MidiInWinUWP::openVirtualPort: cannot be implemented in Windows UWP MIDI API!";
+ error(RtMidiError::WARNING, errorString_);
+}
+
+void MidiInWinUWP::closePort(void)
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+ std::lock_guard lock(data->mtx_open_close_);
+
+ if (connected_)
+ {
+ data->close();
+ connected_ = false;
+ }
+}
+
+void MidiInWinUWP::setClientName(const std::string&)
+{
+ errorString_ = "MidiInWinUWP::setClientName: this function is not implemented for the WINDOWS_UWP API!";
+ error(RtMidiError::WARNING, errorString_);
+}
+
+void MidiInWinUWP::setPortName(const std::string&)
+{
+ errorString_ = "MidiInWinUWP::setPortName: this function is not implemented for the WINDOWS_UWP API!";
+ error(RtMidiError::WARNING, errorString_);
+}
+
+unsigned int MidiInWinUWP::getPortCount()
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+ return static_cast(data->get_num_ports());
+}
+
+std::string MidiInWinUWP::getPortName(unsigned int portNumber)
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+
+ const auto nDevices{ data->get_num_ports() };
+ if (portNumber >= nDevices)
+ {
+ std::ostringstream ost;
+ ost << "MidiInWinUWP::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error(RtMidiError::WARNING, errorString_);
+ return "";
+ }
+
+ return data->get_port_name(portNumber);
+}
+
+double MidiInWinUWP::getMessage(std::vector* message)
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+ std::lock_guard lock(data->mtx_queue_);
+
+ return MidiInApi::getMessage(message);
+}
+
+//*********************************************************************//
+// API: Windows UWP
+// Class Definitions: MidiOutWinUWP
+//*********************************************************************//
+
+MidiOutWinUWP::MidiOutWinUWP(const std::string& clientName) : MidiOutApi()
+{
+ MidiOutWinUWP::initialize(clientName);
+}
+
+MidiOutWinUWP :: ~MidiOutWinUWP()
+{
+ // Close a connection if it exists.
+ MidiOutWinUWP::closePort();
+
+ // Cleanup.
+ UWPMidiClass* data = static_cast (apiData_);
+ delete data;
+}
+
+void MidiOutWinUWP::initialize(const std::string& /*clientName*/)
+{
+ // Save our api-specific connection information.
+ UWPMidiClass* data{ new UWPMidiClass(*this) };
+ data->out_init();
+ apiData_ = static_cast(data);
+
+ // We'll issue a warning here if no devices are available but not
+ // throw an error since the user can plug something in later.
+ const auto nDevices{ data->get_num_ports() };
+ if (nDevices == 0)
+ {
+ errorString_ = "MidiOutWinUWP::initialize: no MIDI output devices currently available.";
+ error(RtMidiError::WARNING, errorString_);
+ }
+}
+
+unsigned int MidiOutWinUWP::getPortCount()
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+ return static_cast(data->get_num_ports());
+}
+
+std::string MidiOutWinUWP::getPortName(unsigned int portNumber)
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+
+ const auto nDevices{ data->get_num_ports() };
+ if (portNumber >= nDevices)
+ {
+ std::ostringstream ost;
+ ost << "MidiOutWinUWP::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error(RtMidiError::WARNING, errorString_);
+ return "";
+ }
+
+ return data->get_port_name(portNumber);
+}
+
+void MidiOutWinUWP::openPort(unsigned int portNumber, const std::string&/*portName*/)
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+ std::lock_guard lock(data->mtx_open_close_);
+
+ if (connected_)
+ {
+ errorString_ = "MidiOutWinUWP::openPort: a valid connection already exists!";
+ error(RtMidiError::WARNING, errorString_);
+ return;
+ }
+
+ if (data->get_num_ports() == 0)
+ {
+ errorString_ = "MidiOutWinUWP::openPort: no MIDI output destinations found!";
+ error(RtMidiError::NO_DEVICES_FOUND, errorString_);
+ return;
+ }
+
+ if (portNumber >= data->get_num_ports())
+ {
+ std::ostringstream ost;
+ ost << "MidiOutWinUWP::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+ errorString_ = ost.str();
+ error(RtMidiError::INVALID_PARAMETER, errorString_);
+ return;
+ }
+
+ if (!data->out_open(portNumber))
+ {
+ errorString_ = "MidiOutWinUWP::openPort: error creating Windows UWP MIDI output port.";
+ error(RtMidiError::DRIVER_ERROR, errorString_);
+ return;
+ }
+
+ connected_ = true;
+}
+
+void MidiOutWinUWP::closePort(void)
+{
+ UWPMidiClass* data{ static_cast(apiData_) };
+ std::lock_guard lock(data->mtx_open_close_);
+
+ if (connected_)
+ {
+ data->close();
+ connected_ = false;
+ }
+}
+
+void MidiOutWinUWP::setClientName(const std::string&)
+{
+ errorString_ = "MidiOutWinUWP::setClientName: this function is not implemented for the WINDOWS_UWP API!";
+ error(RtMidiError::WARNING, errorString_);
+}
+
+void MidiOutWinUWP::setPortName(const std::string&)
+{
+ errorString_ = "MidiOutWinUWP::setPortName: this function is not implemented for the WINDOWS_UWP API!";
+ error(RtMidiError::WARNING, errorString_);
+}
+
+void MidiOutWinUWP::openVirtualPort(const std::string&/*portName*/)
+{
+ // This function cannot be implemented for the Windows UWP MIDI API.
+ errorString_ = "MidiOutWinUWP::openVirtualPort: cannot be implemented in Windows UWP MIDI API!";
+ error(RtMidiError::WARNING, errorString_);
+}
+
+void MidiOutWinUWP::sendMessage(const unsigned char* message, size_t size)
+{
+ if (!connected_)
+ return;
+
+ if (size == 0)
+ {
+ errorString_ = "MidiOutWinUWP::sendMessage: message argument is empty!";
+ error(RtMidiError::WARNING, errorString_);
+ return;
+ }
+
+ UWPMidiClass* data{ static_cast(apiData_) };
+ if (!data->send_buffer(message, size))
+ {
+ errorString_ = "MidiOutWinUWP::sendMessage: error sending message.";
+ error(RtMidiError::DRIVER_ERROR, errorString_);
+ }
+}
+
+#endif // __WINDOWS_UWP__
+
+
+//*********************************************************************//
+// 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
+#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 *buff;
+ int buffMaxWrite; // actual writable size, usually 1 less than ringbuffer
+ 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";
+ if (portName.size() >= (size_t)jack_port_name_size())
+ errorString_ += " (port name too long?)";
+ 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";
+ if (portName.size() >= (size_t)jack_port_name_size())
+ errorString_ += " (port name too long?)";
+ 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 (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_peek( data->buff, (char *) &space, sizeof( space ) ) == sizeof(space) &&
+ jack_ringbuffer_read_space( data->buff ) >= sizeof(space) + space ) {
+ jack_ringbuffer_read_advance( data->buff, sizeof(space) );
+
+ midiData = jack_midi_event_reserve( buff, 0, space );
+ if ( midiData )
+ jack_ringbuffer_read( data->buff, (char *) midiData, (size_t) space );
+ else
+ jack_ringbuffer_read_advance( data->buff, (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->buff = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );
+ data->buffMaxWrite = (int) jack_ringbuffer_write_space( data->buff );
+
+ // 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->buff );
+ 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";
+ if (portName.size() >= (size_t)jack_port_name_size())
+ errorString_ += " (port name too long?)";
+ 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";
+ if (portName.size() >= (size_t)jack_port_name_size())
+ errorString_ += " (port name too long?)";
+ 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_);
+
+ if ( size + sizeof(nBytes) > (size_t) data->buffMaxWrite )
+ return;
+
+ while ( jack_ringbuffer_write_space(data->buff) < sizeof(nBytes) + size )
+ sched_yield();
+
+ // Write full message to buffer
+ jack_ringbuffer_write( data->buff, ( char * ) &nBytes, sizeof( nBytes ) );
+ jack_ringbuffer_write( data->buff, ( const char * ) message, nBytes );
+}
+
+#endif // __UNIX_JACK__
+
+//*********************************************************************//
+// API: Web MIDI
+//
+// Written primarily by Atsushi Eno, February 2020.
+//
+// *********************************************************************//
+
+#if defined(__WEB_MIDI_API__)
+
+#include
+
+//*********************************************************************//
+// API: WEB MIDI
+// Class Definitions: WebMidiAccessShim
+//*********************************************************************//
+
+class WebMidiAccessShim
+{
+public:
+ WebMidiAccessShim();
+ ~WebMidiAccessShim();
+ std::string getPortName( unsigned int portNumber, bool isInput );
+};
+
+std::unique_ptr shim{nullptr};
+
+void ensureShim()
+{
+ if ( shim.get() != nullptr )
+ return;
+ shim.reset( new WebMidiAccessShim() );
+}
+
+bool checkWebMidiAvailability()
+{
+ ensureShim();
+
+ return MAIN_THREAD_EM_ASM_INT( {
+ if ( typeof window._rtmidi_internals_waiting === "undefined" ) {
+ console.log ( "Attempted to use Web MIDI API without trying to open it." );
+ return false;
+ }
+ if ( window._rtmidi_internals_waiting ) {
+ console.log ( "Attempted to use Web MIDI API while it is being queried." );
+ return false;
+ }
+ if ( _rtmidi_internals_midi_access == null ) {
+ console.log ( "Attempted to use Web MIDI API while it already turned out to be unavailable." );
+ return false;
+ }
+ return true;
+ } );
+}
+
+WebMidiAccessShim::WebMidiAccessShim()
+{
+ MAIN_THREAD_ASYNC_EM_ASM( {
+ if( typeof window._rtmidi_internals_midi_access !== "undefined" )
+ return;
+ if( typeof window._rtmidi_internals_waiting !== "undefined" ) {
+ console.log( "MIDI Access was requested while another request is in progress." );
+ return;
+ }
+
+ // define functions
+ window._rtmidi_internals_get_port_by_number = function( portNumber, isInput ) {
+ var midi = window._rtmidi_internals_midi_access;
+ var devices = isInput ? midi.inputs : midi.outputs;
+ var i = 0;
+ for (var device of devices.values()) {
+ if ( i == portNumber )
+ return device;
+ i++;
+ }
+ console.log( "MIDI " + (isInput ? "input" : "output") + " device of portNumber " + portNumber + " is not found.");
+ return null;
+ };
+
+ window._rtmidi_internals_waiting = true;
+ window.navigator.requestMIDIAccess( {"sysex": true} ).then( (midiAccess) => {
+ window._rtmidi_internals_midi_access = midiAccess;
+ window._rtmidi_internals_latest_message_timestamp = 0.0;
+ window._rtmidi_internals_waiting = false;
+ if( midiAccess == null ) {
+ console.log ( "Could not get access to MIDI API" );
+ }
+ } );
+ } );
+}
+
+WebMidiAccessShim::~WebMidiAccessShim()
+{
+}
+
+std::string WebMidiAccessShim::getPortName( unsigned int portNumber, bool isInput )
+{
+ if( !checkWebMidiAvailability() )
+ return "";
+ char *ret = (char*) MAIN_THREAD_EM_ASM_INT( {
+ var port = window._rtmidi_internals_get_port_by_number($0, $1);
+ if( port == null)
+ return null;
+ var length = lengthBytesUTF8(port.name) + 1;
+ var ret = _malloc(length);
+ stringToUTF8(port.name, ret, length);
+ return ret;
+ }, portNumber, isInput);
+ if (ret == nullptr)
+ return "";
+ std::string s = ret;
+ free(ret);
+ return s;
+}
+
+//*********************************************************************//
+// API: WEB MIDI
+// Class Definitions: MidiInWeb
+//*********************************************************************//
+
+MidiInWeb::MidiInWeb( const std::string &clientName, unsigned int queueSizeLimit )
+ : MidiInApi( queueSizeLimit )
+{
+ initialize( clientName );
+}
+
+MidiInWeb::~MidiInWeb( void )
+{
+ closePort();
+}
+
+extern "C" void EMSCRIPTEN_KEEPALIVE rtmidi_onMidiMessageProc( MidiInApi::RtMidiInData* data, uint8_t* inputBytes, int32_t length, double domHighResTimeStamp )
+{
+ auto &message = data->message;
+ message.bytes.resize(message.bytes.size() + length);
+ memcpy(message.bytes.data(), inputBytes, length);
+ // FIXME: handle timestamp
+ if ( data->usingCallback ) {
+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+ callback( message.timeStamp, &message.bytes, data->userData );
+ }
+}
+
+void MidiInWeb::openPort( unsigned int portNumber, const std::string &portName )
+{
+ if( !checkWebMidiAvailability() )
+ return;
+ if (open_port_number >= 0)
+ return;
+
+ MAIN_THREAD_EM_ASM( {
+ // In Web MIDI API world, there is no step to open a port, but we have to register the input callback instead.
+ var input = window._rtmidi_internals_get_port_by_number($0, true);
+ input.onmidimessage = function(e) {
+ // In RtMidi world, timestamps are delta time from previous message, while in Web MIDI world
+ // timestamps are relative to window creation time (i.e. kind of absolute time with window "epoch" time).
+ var rtmidiTimestamp = window._rtmidi_internals_latest_message_timestamp == 0.0 ? 0.0 : e.timeStamp - window._rtmidi_internals_latest_message_timestamp;
+ window._rtmidi_internals_latest_message_timestamp = e.timeStamp;
+ Module.ccall( 'rtmidi_onMidiMessageProc', 'void', ['number', 'array', 'number', 'number'], [$1, e.data, e.data.length, rtmidiTimestamp] );
+ };
+ }, portNumber, &inputData_ );
+ open_port_number = portNumber;
+ connected_ = true;
+}
+
+void MidiInWeb::openVirtualPort( const std::string &portName )
+{
+
+ errorString_ = "MidiInWeb::openVirtualPort: this function is not implemented for the Web MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiInWeb::closePort( void )
+{
+ if( open_port_number < 0 )
+ return;
+
+ MAIN_THREAD_EM_ASM( {
+ var input = _rtmidi_internals_get_port_by_number($0, true);
+ if( input == null ) {
+ console.log( "Port #" + $0 + " could not be found.");
+ return;
+ }
+ // unregister event handler
+ input.onmidimessage = null;
+ }, open_port_number );
+ open_port_number = -1;
+ connected_ = false;
+}
+
+void MidiInWeb::setClientName( const std::string &clientName )
+{
+ client_name = clientName;
+}
+
+void MidiInWeb::setPortName( const std::string &portName )
+{
+
+ errorString_ = "MidiInWeb::setPortName: this function is not implemented for the Web MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+unsigned int MidiInWeb::getPortCount( void )
+{
+ if( !checkWebMidiAvailability() )
+ return 0;
+ return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.inputs.size; } );
+}
+
+std::string MidiInWeb::getPortName( unsigned int portNumber )
+{
+ if( !checkWebMidiAvailability() )
+ return "";
+ return shim->getPortName( portNumber, true );
+}
+
+void MidiInWeb::initialize( const std::string& clientName )
+{
+ ensureShim();
+ setClientName( clientName );
+}
+
+//*********************************************************************//
+// API: WEB MIDI
+// Class Definitions: MidiOutWeb
+//*********************************************************************//
+
+MidiOutWeb::MidiOutWeb( const std::string &clientName )
+{
+ initialize( clientName );
+}
+
+MidiOutWeb::~MidiOutWeb( void )
+{
+ closePort();
+}
+
+void MidiOutWeb::openPort( unsigned int portNumber, const std::string &portName )
+{
+ if( !checkWebMidiAvailability() )
+ return;
+ if (open_port_number >= 0)
+ return;
+ // In Web MIDI API world, there is no step to open a port.
+
+ open_port_number = portNumber;
+ connected_ = true;
+}
+
+void MidiOutWeb::openVirtualPort( const std::string &portName )
+{
+
+ errorString_ = "MidiOutWeb::openVirtualPort: this function is not implemented for the Web MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+void MidiOutWeb::closePort( void )
+{
+ // there is really nothing to do for output at JS side.
+ open_port_number = -1;
+ connected_ = false;
+}
+
+void MidiOutWeb::setClientName( const std::string &clientName )
+{
+ client_name = clientName;
+}
+
+void MidiOutWeb::setPortName( const std::string &portName )
+{
+
+ errorString_ = "MidiOutWeb::setPortName: this function is not implemented for the Web MIDI API!";
+ error( RtMidiError::WARNING, errorString_ );
+
+}
+
+unsigned int MidiOutWeb::getPortCount( void )
+{
+ if( !checkWebMidiAvailability() )
+ return 0;
+ return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.outputs.size; } );
+}
+
+std::string MidiOutWeb::getPortName( unsigned int portNumber )
+{
+ if( !checkWebMidiAvailability() )
+ return "";
+ return shim->getPortName( portNumber, false );
+}
+
+void MidiOutWeb::sendMessage( const unsigned char *message, size_t size )
+{
+ if( open_port_number < 0 )
+ return;
+
+ MAIN_THREAD_EM_ASM( {
+ var output = _rtmidi_internals_get_port_by_number( $0, false );
+ if( output == null ) {
+ console.log( "Port #" + $0 + " could not be found.");
+ return;
+ }
+ var buf = new ArrayBuffer ($2);
+ var msg = new Uint8Array( buf );
+ msg.set( new Uint8Array( Module.HEAPU8.buffer.slice( $1, $1 + $2 ) ) );
+ output.send( msg );
+ }, open_port_number, message, size );
+}
+
+void MidiOutWeb::initialize( const std::string& clientName )
+{
+ if ( shim.get() != nullptr )
+ return;
+ shim.reset( new WebMidiAccessShim() );
+ setClientName( clientName );
+}
+
+#endif // __WEB_MIDI_API__
+
+
+//*********************************************************************//
+// API: ANDROID AMIDI
+//
+// Written by Yellow Labrador, May 2023.
+// https://github.com/YellowLabrador/rtmidi
+// *********************************************************************//
+
+#if defined(__AMIDI__)
+
+#include
+
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+
+static std::string androidClientName;
+static std::vector androidMidiDevices;
+
+//*********************************************************************//
+// API: Android AMIDI
+// Class Definitions: MidiInAndroid
+//*********************************************************************//
+
+static JNIEnv* androidGetThreadEnv() {
+ // Every Android app has only one JVM. Calling JNI_GetCreatedJavaVMs
+ // will retrieve the JVM running the app.
+ jsize jvmsFound = 0;
+ JavaVM jvms[1];
+ JavaVM* pjvms = jvms;
+ jint result = JNI_GetCreatedJavaVMs(&pjvms, 1, &jvmsFound);
+
+ // Something went terribly wrong, no JVM was found
+ if (jvmsFound != 1 || result != JNI_OK) {
+ LOGE("No JVM found");
+ return NULL;
+ }
+
+ // Get the JNIEnv for the current thread
+ JNIEnv* env = NULL;
+ int rc = pjvms->GetEnv((void**)&env, JNI_VERSION_1_6);
+
+ // The current thread was not attached to the JVM. Add it to the JVM
+ if (rc == JNI_EDETACHED) {
+ pjvms->AttachCurrentThreadAsDaemon(&env, NULL);
+ }
+
+ // Neither way to retrieve the JNIEnv worked
+ if (env == NULL) {
+ LOGE("Unable to retrieve JNI environment");
+ }
+
+ return env;
+}
+
+static jobject androidGetContext(JNIEnv *env) {
+ auto activityThread = env->FindClass("android/app/ActivityThread");
+ auto currentActivityThread = env->GetStaticMethodID(activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;");
+ auto at = env->CallStaticObjectMethod(activityThread, currentActivityThread);
+ if (at == NULL) {
+ LOGE("Unable to locate the global ActivityThread");
+ return NULL;
+ }
+
+ auto getApplication = env->GetMethodID(activityThread, "getApplication", "()Landroid/app/Application;");
+ auto context = env->CallObjectMethod(at, getApplication);
+ if (context == NULL) {
+ LOGE("Application context was NULL");
+ }
+
+ return context;
+}
+
+static jobject androidGetMidiManager(JNIEnv *env, jobject context) {
+ // MidiManager midiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
+ auto contextClass = env->FindClass("android/content/Context");
+ auto getServiceMethod = env->GetMethodID(contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
+ return env->CallObjectMethod(context, getServiceMethod, env->NewStringUTF("midi"));
+}
+
+static void androidRefreshMidiDevices(JNIEnv *env, jobject context, bool isOutput) {
+ // Remove all midi devices
+ for (jobject jMidiDevice : androidMidiDevices) {
+ env->DeleteGlobalRef(jMidiDevice);
+ }
+ androidMidiDevices.clear();
+
+ auto midiService = androidGetMidiManager(env, context);
+
+ // MidiDeviceInfo[] devInfos = mMidiManager.getDevices();
+ auto midiMgrClass = env->FindClass("android/media/midi/MidiManager");
+ auto getDevicesMethod = env->GetMethodID(midiMgrClass, "getDevices", "()[Landroid/media/midi/MidiDeviceInfo;");
+ auto jDevices = (jobjectArray) env->CallObjectMethod(midiService, getDevicesMethod);
+
+ auto deviceInfoClass = env->FindClass("android/media/midi/MidiDeviceInfo");
+ auto getInputPortCountMethod = env->GetMethodID(deviceInfoClass, "getInputPortCount", "()I");
+ auto getOutputPortCountMethod = env->GetMethodID(deviceInfoClass, "getOutputPortCount", "()I");
+
+ jsize len = env->GetArrayLength((jarray)jDevices);
+ for (int i=0; iGetObjectArrayElement(jDevices, i);
+
+ int numPorts = env->CallIntMethod(jDeviceInfo, isOutput ? getOutputPortCountMethod : getInputPortCountMethod);
+ if (numPorts > 0) {
+ androidMidiDevices.push_back(env->NewGlobalRef(jDeviceInfo));
+ }
+ }
+}
+
+
+extern "C"
+JNIEXPORT void JNICALL
+Java_com_yellowlab_rtmidi_MidiDeviceOpenedListener_midiDeviceOpened(JNIEnv *env, jclass clazz,
+ jobject midi_device, jlong targetPtr, jboolean isOutput) {
+ if (isOutput) {
+ auto midiOut = reinterpret_cast(targetPtr);
+ AMidiDevice_fromJava(env, midi_device, &midiOut->sendDevice);
+ AMidiInputPort_open(midiOut->sendDevice, 0, &midiOut->midiInputPort);
+ } else {
+ auto midiIn = reinterpret_cast(targetPtr);
+ AMidiDevice_fromJava(env, midi_device, &midiIn->receiveDevice);
+ AMidiOutputPort_open(midiIn->receiveDevice, 0, &midiIn->midiOutputPort);
+ pthread_create(&midiIn->readThread, NULL, MidiInAndroid::pollMidi, midiIn);
+ }
+}
+
+static void androidOpenDevice(jobject deviceInfo, void* target, bool isOutput) {
+ auto env = androidGetThreadEnv();
+ auto context = androidGetContext(env);
+ auto midiMgr = androidGetMidiManager(env, context);
+
+ // openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, Handler handler)
+ auto midiMgrClass = env->GetObjectClass(midiMgr);
+ auto openDevicesMethod = env->GetMethodID(midiMgrClass, "openDevice", "(Landroid/media/midi/MidiDeviceInfo;Landroid/media/midi/MidiManager$OnDeviceOpenedListener;Landroid/os/Handler;)V");
+
+ auto handlerClass = env->FindClass("android/os/Handler");
+ auto handlerCtor = env->GetMethodID(handlerClass, "", "()V");
+ auto handler = env->NewObject(handlerClass, handlerCtor);
+
+ auto listenerClass = env->FindClass("com/yellowlab/rtmidi/MidiDeviceOpenedListener");
+ if (!listenerClass) {
+ LOGE("Midi listener class not found com.yellowlab.rtmidi.MidiDeviceOpenedListener. Did you forget to add it to your APK?");
+ return;
+ }
+
+ auto targetPtr = reinterpret_cast(target);
+ auto listenerCtor = env->GetMethodID(listenerClass, "", "(JZ)V");
+ auto listener = env->NewObject(listenerClass, listenerCtor, targetPtr, isOutput);
+
+ env->CallVoidMethod(midiMgr, openDevicesMethod, deviceInfo, listener, handler);
+ env->DeleteLocalRef(handler);
+}
+
+static std::string androidPortName(JNIEnv *env, unsigned int portNumber) {
+ if (portNumber >= androidMidiDevices.size()) {
+ LOGE("androidPortName: Invalid port number");
+ return "";
+ }
+
+ // String deviceName = devInfo.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ auto deviceInfoClass = env->FindClass("android/media/midi/MidiDeviceInfo");
+ auto getPropsMethod = env->GetMethodID(deviceInfoClass, "getProperties", "()Landroid/os/Bundle;");
+ auto bundle = env->CallObjectMethod(androidMidiDevices[portNumber], getPropsMethod);
+
+ auto bundleClass = env->FindClass("android/os/Bundle");
+ auto getStringMethod = env->GetMethodID(bundleClass, "getString", "(Ljava/lang/String;)Ljava/lang/String;");
+ auto jPortName = (jstring) env->CallObjectMethod(bundle, getStringMethod, env->NewStringUTF("name"));
+
+ auto portNameChars = env->GetStringUTFChars(jPortName, NULL);
+ auto name = std::string(portNameChars);
+ env->ReleaseStringUTFChars(jPortName, portNameChars);
+
+ return name;
+}
+
+MidiInAndroid :: MidiInAndroid( const std::string &clientName, unsigned int queueSizeLimit )
+ : MidiInApi( queueSizeLimit ) {
+ MidiInAndroid::initialize( clientName );
+}
+
+void MidiInAndroid :: initialize( const std::string& clientName ) {
+ androidClientName = clientName;
+ connect();
+}
+
+void MidiInAndroid :: connect() {
+ auto env = androidGetThreadEnv();
+ auto context = androidGetContext(env);
+ androidRefreshMidiDevices(env, context, true);
+
+ env->DeleteLocalRef(context);
+}
+
+MidiInAndroid :: ~MidiInAndroid() {
+ auto env = androidGetThreadEnv();
+
+ // Remove all midi devices
+ for (jobject jMidiDevice : androidMidiDevices) {
+ env->DeleteGlobalRef(jMidiDevice);
+ }
+ androidMidiDevices.clear();
+
+ androidClientName = "";
+}
+
+void MidiInAndroid :: openPort(unsigned int portNumber, const std::string &portName) {
+ if (portNumber >= androidMidiDevices.size()) {
+ errorString_ = "MidiInAndroid::openPort: Invalid port number";
+ error( RtMidiError::INVALID_PARAMETER, errorString_ );
+
+ return;
+ }
+
+ if (reading) {
+ errorString_ = "MidiInAndroid::openPort: A port is already open";
+ error( RtMidiError::INVALID_USE, errorString_ );
+
+ return;
+ }
+
+ androidOpenDevice(androidMidiDevices[portNumber], this, false);
+}
+
+void MidiInAndroid :: openVirtualPort(const std::string &portName) {
+ errorString_ = "MidiInAndroid::openVirtualPort: this function is not implemented for the Android API!";
+ error( RtMidiError::WARNING, errorString_ );
+}
+
+unsigned int MidiInAndroid :: getPortCount() {
+ connect();
+ return androidMidiDevices.size();
+}
+
+std::string MidiInAndroid :: getPortName(unsigned int portNumber) {
+ auto env = androidGetThreadEnv();
+ return androidPortName(env, portNumber);
+}
+
+void MidiInAndroid :: closePort() {
+ // Don't try to close a port before it was open
+ if (!reading) {
+ return;
+ }
+
+ reading = false;
+ pthread_join(readThread, NULL);
+
+ AMidiDevice_release(receiveDevice);
+ receiveDevice = NULL;
+ midiOutputPort = NULL;
+}
+
+void MidiInAndroid:: setClientName(const std::string& clientName) {
+ androidClientName = clientName;
+}
+
+void MidiInAndroid :: setPortName(const std::string &portName) {
+ errorString_ = "MidiInAndroid::setPortName: this function is not implemented for the Android API!";
+ error( RtMidiError::WARNING, errorString_ );
+}
+
+void* MidiInAndroid :: pollMidi(void* context) {
+ auto self = (MidiInAndroid*) context;
+ self->reading = true;
+
+ const size_t MAX_BYTES_TO_RECEIVE = 128;
+ uint8_t incomingMessage[MAX_BYTES_TO_RECEIVE];
+
+ while (self->reading) {
+ // AMidiOutputPort_receive is non-blocking, must poll with some sleep
+ usleep(2000);
+ auto ignoreFlags = self->inputData_.ignoreFlags;
+ bool& continueSysex = self->inputData_.continueSysex;
+
+ int32_t opcode;
+ size_t numBytesReceived;
+ int64_t timestamp;
+ ssize_t numMessagesReceived = AMidiOutputPort_receive(
+ self->midiOutputPort, &opcode, incomingMessage, MAX_BYTES_TO_RECEIVE,
+ &numBytesReceived, ×tamp);
+
+ if (numMessagesReceived < 0) {
+ self->errorString_ = "MidiInAndroid::pollMidi: error receiving MIDI data";
+ self->error( RtMidiError::SYSTEM_ERROR, self->errorString_ );
+ self->reading = false;
+ break;
+ }
+
+ switch (incomingMessage[0]) {
+ case 0xF0:
+ // Start of a SysEx message
+ continueSysex = incomingMessage[numBytesReceived - 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 = incomingMessage[numBytesReceived - 1] != 0xF7;
+ if (ignoreFlags & 0x01) continue;
+ }
+ // All other MIDI messages
+ }
+
+ if (numMessagesReceived > 0 && numBytesReceived >= 0) {
+ auto message = self->inputData_.message;
+
+ if (self->inputData_.firstMessage == true) {
+ message.timeStamp = 0.0;
+ self->inputData_.firstMessage = false;
+ } else {
+ message.timeStamp = (timestamp * 0.000001) - self->lastTime;
+ }
+ self->lastTime = (timestamp * 0.000001);
+
+ if (!continueSysex) message.bytes.clear();
+
+ if ( !( ( continueSysex || incomingMessage[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; iinputData_.usingCallback) {
+ auto callback = (RtMidiIn::RtMidiCallback) self->inputData_.userCallback;
+ callback(message.timeStamp, &message.bytes, self->inputData_.userData);
+ } else {
+ if (!self->inputData_.queue.push(message))
+ std::cerr << "\nMidiInAndroid: message queue limit reached!!\n\n";
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+//*********************************************************************//
+// API: Android AMIDI
+// Class Definitions: MidiOutAndroid
+//*********************************************************************//
+
+
+MidiOutAndroid :: MidiOutAndroid( const std::string &clientName ) : MidiOutApi() {
+ MidiOutAndroid::initialize( clientName );
+}
+
+void MidiOutAndroid :: initialize( const std::string& clientName ) {
+ androidClientName = clientName;
+ connect();
+}
+
+void MidiOutAndroid :: connect() {
+ auto env = androidGetThreadEnv();
+ auto context = androidGetContext(env);
+ androidRefreshMidiDevices(env, context, false);
+
+ env->DeleteLocalRef(context);
+}
+
+MidiOutAndroid :: ~MidiOutAndroid() {
+ auto env = androidGetThreadEnv();
+
+ // Remove all midi devices
+ for (jobject jMidiDevice : androidMidiDevices) {
+ env->DeleteGlobalRef(jMidiDevice);
+ }
+ androidMidiDevices.clear();
+
+ androidClientName = "";
+}
+
+void MidiOutAndroid :: openPort( unsigned int portNumber, const std::string &portName ) {
+ if (portNumber >= androidMidiDevices.size()) {
+ errorString_ = "MidiOutAndroid::openPort: Invalid port number";
+ error( RtMidiError::INVALID_PARAMETER, errorString_ );
+
+ return;
+ }
+
+ androidOpenDevice(androidMidiDevices[portNumber], this, true);
+}
+
+void MidiOutAndroid :: openVirtualPort( const std::string &portName ) {
+ errorString_ = "MidiOutAndroid::openVirtualPort: this function is not implemented for the Android API!";
+ error( RtMidiError::WARNING, errorString_ );
+}
+
+unsigned int MidiOutAndroid :: getPortCount() {
+ connect();
+ return androidMidiDevices.size();
+}
+
+std::string MidiOutAndroid :: getPortName( unsigned int portNumber ) {
+ auto env = androidGetThreadEnv();
+ return androidPortName(env, portNumber);
+}
+
+void MidiOutAndroid :: closePort() {
+ AMidiDevice_release(sendDevice);
+ sendDevice = NULL;
+ midiInputPort = NULL;
+}
+
+void MidiOutAndroid:: setClientName( const std::string& name ) {
+ androidClientName = name;
+}
+
+void MidiOutAndroid :: setPortName( const std::string &portName ) {
+ errorString_ = "MidiOutAndroid::setPortName: this function is not implemented for the Android API!";
+ error( RtMidiError::WARNING, errorString_ );
+}
+
+void MidiOutAndroid :: sendMessage( const unsigned char *message, size_t size ) {
+ AMidiInputPort_send(midiInputPort, (uint8_t*)message, size);
+}
+
+#endif // __AMIDI__
diff --git a/SIDFactoryII/source/libraries/rtmidi/RtMidi.h b/SIDFactoryII/source/libraries/rtmidi/RtMidi.h
new file mode 100644
index 00000000..1d645441
--- /dev/null
+++ b/SIDFactoryII/source/libraries/rtmidi/RtMidi.h
@@ -0,0 +1,677 @@
+/**********************************************************************/
+/*! \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-2023 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_MAJOR 6
+#define RTMIDI_VERSION_MINOR 0
+#define RTMIDI_VERSION_PATCH 0
+#define RTMIDI_VERSION_BETA 0
+
+#define RTMIDI_TOSTRING2(n) #n
+#define RTMIDI_TOSTRING(n) RTMIDI_TOSTRING2(n)
+
+#if RTMIDI_VERSION_BETA > 0
+ #define RTMIDI_VERSION RTMIDI_TOSTRING(RTMIDI_VERSION_MAJOR) \
+ "." RTMIDI_TOSTRING(RTMIDI_VERSION_MINOR) \
+ "." RTMIDI_TOSTRING(RTMIDI_VERSION_PATCH) \
+ "beta" RTMIDI_TOSTRING(RTMIDI_VERSION_BETA)
+#else
+ #define RTMIDI_VERSION RTMIDI_TOSTRING(RTMIDI_VERSION_MAJOR) \
+ "." RTMIDI_TOSTRING(RTMIDI_VERSION_MINOR) \
+ "." RTMIDI_TOSTRING(RTMIDI_VERSION_PATCH)
+#endif
+
+#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 occurred 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 occurred. */
+ SYSTEM_ERROR, /*!< A system error occurred. */
+ THREAD_ERROR /*!< A thread error occurred. */
+ };
+
+ //! 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:
+
+ RtMidi(RtMidi&& other) noexcept;
+ //! 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. */
+ WEB_MIDI_API, /*!< W3C Web MIDI API. */
+ WINDOWS_UWP, /*!< The Microsoft Universal Windows Platform MIDI API. */
+ ANDROID_AMIDI, /*!< Native Android MIDI 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 occurred.
+ /*!
+ The callback function will be called whenever an error has occurred. 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_;
+
+ /* Make the class non-copyable */
+ RtMidi(RtMidi& other) = delete;
+ RtMidi& operator=(RtMidi& other) = delete;
+};
+
+/**********************************************************************/
+/*! \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 );
+
+ RtMidiIn(RtMidiIn&& other) noexcept : RtMidi(std::move(other)) { }
+
+ //! 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 occurred.
+ /*!
+ The callback function will be called whenever an error has occurred. It is best
+ to set the error callback function before opening a port.
+ */
+ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
+
+ //! Set maximum expected incoming message size.
+ /*!
+ For APIs that require manual buffer management, it can be useful to set the buffer
+ size and buffer count when expecting to receive large SysEx messages. Note that
+ currently this function has no effect when called after openPort(). The default
+ buffer size is 1024 with a count of 4 buffers, which should be sufficient for most
+ cases; as mentioned, this does not affect all API backends, since most either support
+ dynamically scalable buffers or take care of buffer handling themselves. It is
+ principally intended for users of the Windows MM backend who must support receiving
+ especially large messages.
+ */
+ virtual void setBufferSize( unsigned int size, unsigned int count );
+
+ 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" );
+
+ RtMidiOut(RtMidiOut&& other) noexcept : RtMidi(std::move(other)) { }
+
+ //! 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 occurred.
+ /*!
+ The callback function will be called whenever an error has occurred. 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 );
+ virtual double getMessage( std::vector *message );
+ virtual void setBufferSize( unsigned int size, unsigned int count );
+
+ // 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;
+ unsigned int bufferSize;
+ unsigned int bufferCount;
+
+ // Default constructor.
+ RtMidiInData()
+ : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false),
+ userCallback(0), userData(0), continueSysex(false), bufferSize(1024), bufferCount(4) {}
+ };
+
+ 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 void RtMidiIn :: setBufferSize( unsigned int size, unsigned int count ) { static_cast(rtapi_)->setBufferSize(size, count); }
+
+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/SIDFactoryII/source/runtime/editor/dialog/dialog_midi_out_devices.cpp b/SIDFactoryII/source/runtime/editor/dialog/dialog_midi_out_devices.cpp
new file mode 100644
index 00000000..e69de29b
diff --git a/SIDFactoryII/source/runtime/editor/dialog/dialog_midi_out_devices.h b/SIDFactoryII/source/runtime/editor/dialog/dialog_midi_out_devices.h
new file mode 100644
index 00000000..e69de29b
diff --git a/SIDFactoryII/source/runtime/editor/editor_facility.cpp b/SIDFactoryII/source/runtime/editor/editor_facility.cpp
index f22d9ecd..5bb65e4e 100644
--- a/SIDFactoryII/source/runtime/editor/editor_facility.cpp
+++ b/SIDFactoryII/source/runtime/editor/editor_facility.cpp
@@ -29,6 +29,7 @@
#include "runtime/editor/utilities/import_utils.h"
#include "runtime/emulation/cpumemory.h"
#include "runtime/emulation/cpumos6510.h"
+#include "runtime/emulation/asid/asid.h"
#include "runtime/emulation/sid/sidproxy.h"
#include "runtime/environmentdefines.h"
#include "runtime/execution/executionhandler.h"
@@ -51,6 +52,8 @@
// System
#include "foundation/base/assert.h"
+#include "libraries/rtmidi/RtMidi.h"
+#include "utils/rtmidi_utils.h"
using namespace Foundation;
using namespace Emulation;
@@ -70,7 +73,6 @@ namespace Editor
, m_FlipOverlayState(false)
, m_SelectedColorScheme(0)
{
-
ConfigFile& config = Global::instance().GetConfig();
IPlatform& platform = Global::instance().GetPlatform();
@@ -122,16 +124,29 @@ namespace Editor
sid_configuration.m_eModel = SID_MODEL_6581;
sid_configuration.m_nSampleFrequency = sid_sample_frequency;
+ m_RtMidiOut = new RtMidiOut();
+ m_ASID = new ASid(m_RtMidiOut);
m_SIDProxy = new SIDProxy(sid_configuration);
m_CPUMemory = new CPUMemory(0x10000, &platform);
m_CPU = new CPUmos6510();
m_FlightRecorder = new FlightRecorder(&platform, 0x800);
- m_ExecutionHandler = new ExecutionHandler(m_CPU, m_CPUMemory, m_SIDProxy, m_FlightRecorder);
+ m_ExecutionHandler = new ExecutionHandler(m_CPU, m_CPUMemory, m_SIDProxy, m_ASID, m_FlightRecorder);
// Create audio stream
const int audio_buffer_size = GetSingleConfigurationValue(config, "Sound.Buffer.Size", 256);
m_AudioStream = new AudioStream(sid_sample_frequency, 16, std::max(audio_buffer_size, 0x80), m_ExecutionHandler);
+ // Setup midi out device, if default selected from the ini file
+ if(Global::instance().GetConfig().HasKey("Playback.ASID.MidiInterface"))
+ {
+ std::string config_asid_midi_port_name = GetSingleConfigurationValue(Global::instance().GetConfig(), "Playback.ASID.MidiInterface", std::string(""));
+ const auto midi_out_ports = RtMidiUtils::RtMidiOut_GetPorts(m_RtMidiOut);
+ const auto selected_out_port = RtMidiUtils::RtMidi_GetPortInfoByName(midi_out_ports, config_asid_midi_port_name);
+
+ if(selected_out_port.IsValid())
+ RtMidiUtils::RtMidiOut_OpenPort(m_RtMidiOut, selected_out_port.Get());
+ }
+
// Create the main text field
m_TextField = m_Viewport->CreateTextField(m_Viewport->GetClientWidth() / TextField::font_width, m_Viewport->GetClientHeight() / TextField::font_height, 0, 0);
m_TextField->SetEnable(true);
@@ -149,6 +164,7 @@ namespace Editor
&m_CursorControl,
m_DisplayState,
m_KeyHookSetup.GetKeyHookStore(),
+ m_RtMidiOut,
m_DriverInfo,
[&]() { OnExitIntroScreen(); },
[&]() { OnExitIntroScreenToLoad(); });
@@ -213,9 +229,11 @@ namespace Editor
delete m_AudioStream;
delete m_ExecutionHandler;
delete m_FlightRecorder;
+ delete m_ASID;
delete m_SIDProxy;
delete m_CPU;
delete m_CPUMemory;
+ delete m_RtMidiOut;
}
//--------------------------------------------------------------------------------
diff --git a/SIDFactoryII/source/runtime/editor/editor_facility.h b/SIDFactoryII/source/runtime/editor/editor_facility.h
index 21f5061e..9cffd730 100644
--- a/SIDFactoryII/source/runtime/editor/editor_facility.h
+++ b/SIDFactoryII/source/runtime/editor/editor_facility.h
@@ -11,6 +11,8 @@
#include
#include
+class RtMidiOut;
+
namespace Foundation
{
class IPlatform;
@@ -23,6 +25,7 @@ namespace Foundation
namespace Emulation
{
+ class ASid;
class CPUmos6510;
class CPUMemory;
class SIDProxy;
@@ -112,6 +115,8 @@ namespace Editor
int m_ColorSchemeCount;
int m_SelectedColorScheme;
+ RtMidiOut* m_RtMidiOut;
+
Foundation::Viewport* m_Viewport;
Foundation::TextField* m_TextField;
Foundation::AudioStream* m_AudioStream;
@@ -119,6 +124,7 @@ namespace Editor
Emulation::CPUmos6510* m_CPU;
Emulation::CPUMemory* m_CPUMemory;
Emulation::SIDProxy* m_SIDProxy;
+ Emulation::ASid *m_ASID;
Emulation::ExecutionHandler* m_ExecutionHandler;
Emulation::FlightRecorder* m_FlightRecorder;
diff --git a/SIDFactoryII/source/runtime/editor/keys/keyhook_setup.cpp b/SIDFactoryII/source/runtime/editor/keys/keyhook_setup.cpp
index 391d2b6c..fc06318e 100644
--- a/SIDFactoryII/source/runtime/editor/keys/keyhook_setup.cpp
+++ b/SIDFactoryII/source/runtime/editor/keys/keyhook_setup.cpp
@@ -82,6 +82,7 @@ namespace Editor
definitions.push_back({ "Key.ScreenEdit.ToggleMuteChannel1", {{ SDLK_1, Keyboard::Control }} });
definitions.push_back({ "Key.ScreenEdit.ToggleMuteChannel2", {{ SDLK_2, Keyboard::Control }} });
definitions.push_back({ "Key.ScreenEdit.ToggleMuteChannel3", {{ SDLK_3, Keyboard::Control }} });
+ definitions.push_back({ "Key.ScreenEdit.ToggleOutputDevice", {{ SDLK_o, Keyboard::Alt }} });
definitions.push_back({ "Key.ScreenEdit.SetMarker", {{ SDLK_m, Keyboard::Control }} });
definitions.push_back({ "Key.ScreenEdit.GotoMarker", {{ SDLK_g, Keyboard::Control }} });
definitions.push_back({ "Key.ScreenEdit.QuickSave", {{ SDLK_s, Keyboard::Control }} });
diff --git a/SIDFactoryII/source/runtime/editor/screens/screen_edit.cpp b/SIDFactoryII/source/runtime/editor/screens/screen_edit.cpp
index 19f4f79c..79bf61c3 100644
--- a/SIDFactoryII/source/runtime/editor/screens/screen_edit.cpp
+++ b/SIDFactoryII/source/runtime/editor/screens/screen_edit.cpp
@@ -184,6 +184,8 @@ namespace Editor
for(const auto& SIDWriteInfo : SIDWriteInfoList)
Utility::Logging::instance().Info("Write to address $d4%02x at cycle offset: %02x", SIDWriteInfo.m_AddressLow, SIDWriteInfo.m_CycleOffset);
+ m_ExecutionHandler->TellSIDWriteOrderInfo(SIDWriteInfoList);
+
// Create debug views
m_DebugViews = std::make_unique(m_Viewport, &*m_ComponentsManager, m_CPUMemory, m_MainTextField->GetDimensions(), m_DriverInfo);
@@ -204,6 +206,11 @@ namespace Editor
DoToggleSIDModelAndRegion(KeyboardUtils::IsModifierExclusivelyDown(inKeyboardModifiers, Keyboard::Control));
};
+ auto mouse_button_output_device = [&](Foundation::Mouse::Button inMouseButton, int inKeyboardModifiers)
+ {
+ DoToggleOutputDevice();
+ };
+
auto mouse_button_context_highlight = [&](Foundation::Mouse::Button inMouseButton, int inKeyboardModifiers)
{
DoToggleContextHighlight();
@@ -224,7 +231,8 @@ namespace Editor
? std::to_string(selected_song_index + 1)
: song_name;
- m_StatusBar = std::make_unique(m_MainTextField, m_EditState, m_DriverState, m_DriverInfo->GetAuxilaryDataCollection(), mouse_button_octave, mouse_button_flat_sharp, mouse_button_sid_model, mouse_button_context_highlight, mouse_button_follow_play);
+ m_StatusBar = std::make_unique(m_MainTextField, m_EditState, m_DriverState, m_DriverInfo->GetAuxilaryDataCollection(), *m_ExecutionHandler,
+ mouse_button_octave, mouse_button_flat_sharp, mouse_button_sid_model, mouse_button_output_device, mouse_button_context_highlight, mouse_button_follow_play);
m_StatusBar->SetText(m_ActivationMessage.length() > 0 ? m_ActivationMessage : " SID Factory II [Selected song: " + song_selection_text + "]", 2500, false);
m_ActivationMessage = "";
@@ -592,6 +600,19 @@ namespace Editor
}
}
+ void ScreenEdit::DoToggleOutputDevice()
+ {
+ ExecutionHandler::OutputDevice device = m_ExecutionHandler->GetOutputDevice();
+
+ if (device == ExecutionHandler::OutputDevice::RESID) {
+ device = ExecutionHandler::OutputDevice::ASID;
+ }
+ else {
+ device = ExecutionHandler::OutputDevice::RESID;
+ }
+ m_ExecutionHandler->SetOutputDevice(device);
+ }
+
void ScreenEdit::DoClearAllMuteState()
{
@@ -1961,6 +1982,12 @@ namespace Editor
return true;
} });
+ m_KeyHooks.push_back( { "Key.ScreenEdit.ToggleOutputDevice", m_KeyHookStore, [&]()
+ {
+ DoToggleOutputDevice();
+ return true;
+ }});
+
m_KeyHooks.push_back({ "Key.ScreenEdit.SetMarker", m_KeyHookStore, [&]()
{
const int selected_marker = m_PlayMarkerListComponent->GetSelectionIndex();
diff --git a/SIDFactoryII/source/runtime/editor/screens/screen_edit.h b/SIDFactoryII/source/runtime/editor/screens/screen_edit.h
index 14782c58..f88e9e4e 100644
--- a/SIDFactoryII/source/runtime/editor/screens/screen_edit.h
+++ b/SIDFactoryII/source/runtime/editor/screens/screen_edit.h
@@ -116,6 +116,7 @@ namespace Editor
void DoPlayFromSelectedMarker();
void DoStop();
void DoToggleMute(unsigned int inChannel);
+ void DoToggleOutputDevice();
void DoClearAllMuteState();
void DoRestoreMuteState();
void DoMoveToEventPositionOfSelectedMarker();
diff --git a/SIDFactoryII/source/runtime/editor/screens/screen_intro.cpp b/SIDFactoryII/source/runtime/editor/screens/screen_intro.cpp
index b9d41df2..e850ee7e 100644
--- a/SIDFactoryII/source/runtime/editor/screens/screen_intro.cpp
+++ b/SIDFactoryII/source/runtime/editor/screens/screen_intro.cpp
@@ -10,16 +10,23 @@
#include "utils/utilities.h"
#include "foundation/base/assert.h"
+#include "libraries/rtmidi/RtMidi.h"
+#include "runtime/editor/dialog/dialog_selection_list.h"
+#include "utils/rtmidi_utils.h"
+
#include
namespace Editor
{
+ using namespace Utility;
+
ScreenIntro::ScreenIntro(
Foundation::Viewport* inViewport,
Foundation::TextField* inMainTextField,
CursorControl* inCursorControl,
DisplayState& inDisplayState,
- Utility::KeyHookStore& inKeyHookStore,
+ KeyHookStore& inKeyHookStore,
+ RtMidiOut* inRtMidiOut,
std::shared_ptr& inDriverInfo,
std::function inExitScreenCallback,
std::function inExitScreenToLoadCallback)
@@ -27,6 +34,8 @@ namespace Editor
, m_DriverInfo(inDriverInfo)
, m_ExitScreenCallback(inExitScreenCallback)
, m_ExitScreenToLoadCallback(inExitScreenToLoadCallback)
+ , m_RtMidiOut(inRtMidiOut)
+ , m_AddMidiPortSelectionOption(false)
{
}
@@ -36,6 +45,8 @@ namespace Editor
FOUNDATION_ASSERT(m_DriverInfo != nullptr);
ScreenBase::Activate();
+ m_AddMidiPortSelectionOption = !RtMidiUtils::RtMidiOut_HasOpenPort(m_RtMidiOut);
+
// Build string
#ifdef _BUILD_NR
const std::string build_number = _BUILD_NR;
@@ -73,23 +84,25 @@ namespace Editor
const auto& dimensions = m_MainTextField->GetDimensions();
- const int credits_margin = 25;
const int credits_y = 26;
const int driver_info_y = 41;
const int continue_info_y = 43;
const int build_y = dimensions.m_Height - 1;
const int build_x = dimensions.m_Width;
- const int block_width = dimensions.m_Width >> 1;
-
- const Foundation::Rect credits_rect_left({ { credits_margin, credits_y }, { block_width - credits_margin, driver_info_y - credits_y - 1 } });
- const Foundation::Rect credits_rect_right({ { block_width, credits_y }, { block_width - credits_margin, driver_info_y - credits_y - 1 } });
-
- const Foundation::WrappedString credits_text_left("Programming by:\nThomas Egeskov Petersen\nJens-Christian Huus\nMichel de Bree\n \nAdditional design and suggestions by:\n Torben Korgaard Hansen\nThomas Laurits Mogensen\nThomas Bendt", block_width);
- const Foundation::WrappedString credits_text_right("reSID-fp Engine by:\nDag Lem\nAntti S. Lankila \n \npicoPNG by:\nLode Vandevenne\n \nghc::filesystem for c++11 by:\nSteffen Schumann\n \nminiz by:\nRich Geldreich", block_width);
-
- m_MainTextField->PrintAligned(credits_rect_left, credits_text_left, Foundation::TextField::HorizontalAlignment::Center);
- m_MainTextField->PrintAligned(credits_rect_right, credits_text_right, Foundation::TextField::HorizontalAlignment::Center);
-
+ const int num_blocks = 3;
+ const int block_width = dimensions.m_Width / (num_blocks + 1);
+ const int credits_margin = (dimensions.m_Width - (block_width * num_blocks)) >> 1;
+
+ const auto OutputBlock = [&](const int inBlock, const std::string& InText)
+ {
+ const Foundation::Rect rect({ { credits_margin + (inBlock * block_width), credits_y }, { block_width, driver_info_y - credits_y - 1 } });
+ m_MainTextField->PrintAligned(rect, Foundation::WrappedString(InText, block_width), Foundation::TextField::HorizontalAlignment::Center);
+ };
+
+ OutputBlock(0, "Programming by:\nThomas Egeskov Petersen\nJens-Christian Huus\nMichel de Bree\nThomas Jansson\n \nAdditional design and suggestions by:\n Torben Korgaard Hansen\nThomas Laurits Mogensen\nThomas Bendt");
+ OutputBlock(1, "reSID-fp Engine by:\nDag Lem\nAntti S. Lankila \n \npicoPNG by:\nLode Vandevenne\n \nminiz by:\nRich Geldreich");
+ OutputBlock(2, "ghc::filesystem for c++11 by:\nSteffen Schumann\n \nRtMidi by:\nGary P. Scavone");
+
if (m_DriverInfo->IsValid())
{
const std::string& driver_name = m_DriverInfo->GetDescriptor().m_DriverName;
@@ -98,7 +111,10 @@ namespace Editor
else
PrintCenteredText(driver_info_y, "Driver has not been loaded!");
- PrintCenteredText(continue_info_y, "Press SPACE to continue, or F10 for disk menu!");
+ if(m_AddMidiPortSelectionOption)
+ PrintCenteredText(continue_info_y, "Press SPACE to continue, F1 to choose midi output device or F10 for disk menu!");
+ else
+ PrintCenteredText(continue_info_y, "Press SPACE to continue or F10 for disk menu!");
m_MainTextField->Print(build_x - static_cast(build_string.length()), build_y, Foundation::Color::Grey, build_string);
}
@@ -131,7 +147,14 @@ namespace Editor
m_ExitScreenCallback();
return true;
}
- else if (inKeyEvent == SDLK_F10)
+ if (inKeyEvent == SDLK_F1 && m_AddMidiPortSelectionOption)
+ {
+ // Invoke midi device selection screen and invoke midi device
+ TryStartDialogForMidiOutDeviceSelection();
+
+ return true;
+ }
+ if (inKeyEvent == SDLK_F10)
{
m_ExitScreenToLoadCallback();
return true;
@@ -141,6 +164,42 @@ namespace Editor
}
+ bool ScreenIntro::TryStartDialogForMidiOutDeviceSelection()
+ {
+ std::vector selections;
+
+ const auto MidiOutPorts = RtMidiUtils::RtMidiOut_GetPorts(m_RtMidiOut);
+ if(MidiOutPorts.empty())
+ return false;
+
+ for (const auto& MidiOutPort : MidiOutPorts)
+ {
+ std::string selection_string = "Midi device: " + std::to_string(MidiOutPort.m_PortNumber) + (MidiOutPort.m_PortNumber < 10 ? " [" : " [") + MidiOutPort.m_PortName + "]";
+ selections.push_back(selection_string);
+ }
+
+ m_ComponentsManager->StartDialog(
+ std::make_shared
+ (
+ 60,
+ MidiOutPorts.size() + 3,
+ 0,
+ "Select midi output device!",
+ selections,
+ [this, MidiOutPorts](const unsigned int inSelectionIndex)
+ {
+ RtMidiUtils::RtMidiOut_OpenPort(m_RtMidiOut, MidiOutPorts[inSelectionIndex]);
+ m_ExitScreenCallback();
+ },
+ []() {}
+ )
+ );
+
+ return true;
+ }
+
+
+
void ScreenIntro::PrintCenteredText(int inY, const std::string& inText)
{
const Foundation::Extent& dimensions = m_MainTextField->GetDimensions();
diff --git a/SIDFactoryII/source/runtime/editor/screens/screen_intro.h b/SIDFactoryII/source/runtime/editor/screens/screen_intro.h
index c5a6a280..b05c9bdc 100644
--- a/SIDFactoryII/source/runtime/editor/screens/screen_intro.h
+++ b/SIDFactoryII/source/runtime/editor/screens/screen_intro.h
@@ -8,6 +8,8 @@
#include
#include
+class RtMidiOut;
+
namespace Editor
{
class DriverInfo;
@@ -20,6 +22,7 @@ namespace Editor
CursorControl* inCursorControl,
DisplayState& inDisplayState,
Utility::KeyHookStore& inKeyHookStore,
+ RtMidiOut* inRtMidiOut,
std::shared_ptr& inDriverInfo,
std::function inExitScreenCallback,
std::function inExitScreenToLoadCallback
@@ -34,13 +37,18 @@ namespace Editor
bool ConsumeKeyEvent(SDL_Keycode inKeyEvent, unsigned int inModifiers) override;
private:
+ bool TryStartDialogForMidiOutDeviceSelection();
+
void PrintCenteredText(int inY, const std::string& inText);
Foundation::Image* CreateImageFromPNGData(const void* inData, int inDataSize);
+ bool m_AddMidiPortSelectionOption;
+
std::function m_ExitScreenCallback;
std::function m_ExitScreenToLoadCallback;
std::shared_ptr& m_DriverInfo;
Foundation::Image* m_Logo;
+ RtMidiOut* m_RtMidiOut;
};
}
diff --git a/SIDFactoryII/source/runtime/editor/screens/statusbar/status_bar_edit.cpp b/SIDFactoryII/source/runtime/editor/screens/statusbar/status_bar_edit.cpp
index 52afb44a..3b9948b2 100644
--- a/SIDFactoryII/source/runtime/editor/screens/statusbar/status_bar_edit.cpp
+++ b/SIDFactoryII/source/runtime/editor/screens/statusbar/status_bar_edit.cpp
@@ -1,7 +1,9 @@
#include "status_bar_edit.h"
#include "foundation/graphics/textfield.h"
#include "foundation/graphics/color.h"
+#include "runtime/execution/executionhandler.h"
#include "utils/usercolors.h"
+#include
using namespace Utility;
using namespace Foundation;
@@ -14,9 +16,11 @@ namespace Editor
const EditState& inEditState,
const DriverState& inDriverState,
const AuxilaryDataCollection& inAuxilaryDataCollection,
+ const Emulation::ExecutionHandler& inExecutionHandler,
std::function inOctaveMousePressCallback,
std::function inSharpFlatMousePressCallback,
std::function inSIDMousePressCallback,
+ std::function inOutputDevicePressCallback,
std::function inContextHighlightMousePressCallback,
std::function inFollowPlayerMousePressCallback
)
@@ -24,18 +28,21 @@ namespace Editor
, m_EditState(inEditState)
, m_DriverState(inDriverState)
, m_AuxilaryDataPlayMarkers(inAuxilaryDataCollection)
+ , m_ExecutionHandler(inExecutionHandler)
{
m_TextSectionOctave = std::make_shared(12, inOctaveMousePressCallback);
m_TextSectionSharpFlat = std::make_shared(15, inSharpFlatMousePressCallback);
m_TextSectionSID = std::make_shared(19, inSIDMousePressCallback);
m_TextSectionContextHighlight = std::make_shared(18, inContextHighlightMousePressCallback);
m_TextSectionFollowPlay = std::make_shared(15, inFollowPlayerMousePressCallback);
+ m_TextSectionOutputDevice = std::make_shared(17, inOutputDevicePressCallback);
m_TextSectionList.push_back(m_TextSectionOctave);
m_TextSectionList.push_back(m_TextSectionSharpFlat);
m_TextSectionList.push_back(m_TextSectionSID);
m_TextSectionList.push_back(m_TextSectionContextHighlight);
m_TextSectionList.push_back(m_TextSectionFollowPlay);
+ m_TextSectionList.push_back(m_TextSectionOutputDevice);
}
@@ -43,8 +50,7 @@ namespace Editor
{
}
-
-
+
void StatusBarEdit::UpdateInternal(int inDeltaTick, bool inNeedUpdate)
{
if (m_CachedEditState != m_EditState || inNeedUpdate)
@@ -78,6 +84,16 @@ namespace Editor
m_NeedRefresh = true;
}
+ const Emulation::ExecutionHandler::OutputDevice outputDevice = m_ExecutionHandler.GetOutputDevice();
+
+ if (outputDevice != m_CachedOutputDevice || inNeedUpdate)
+ {
+ std::string output_device_string = (outputDevice == Emulation::ExecutionHandler::OutputDevice::ASID ? "ASID" : "RESID");
+ m_TextSectionOutputDevice->SetText("Output: " + output_device_string);
+ m_CachedOutputDevice = outputDevice;
+ m_NeedRefresh = true;
+ }
+
const AuxilaryDataEditingPreferences::NotationMode notation_mode = editor_preferences.GetNotationMode();
if (notation_mode != m_CachedNotationMode || inNeedUpdate)
{
diff --git a/SIDFactoryII/source/runtime/editor/screens/statusbar/status_bar_edit.h b/SIDFactoryII/source/runtime/editor/screens/statusbar/status_bar_edit.h
index be2681be..c3022458 100644
--- a/SIDFactoryII/source/runtime/editor/screens/statusbar/status_bar_edit.h
+++ b/SIDFactoryII/source/runtime/editor/screens/statusbar/status_bar_edit.h
@@ -8,6 +8,7 @@
#include "runtime/editor/auxilarydata/auxilary_data_hardware_preferences.h"
#include "runtime/editor/screens/statusbar/status_bar.h"
#include "runtime/editor/driver/driver_state.h"
+#include "runtime/execution/executionhandler.h"
#include
#include
@@ -27,9 +28,11 @@ namespace Editor
const EditState& inEditState,
const DriverState& inDriverState,
const AuxilaryDataCollection& inAuxilaryDataCollection,
+ const Emulation::ExecutionHandler& inExecutionHandler,
std::function inOctaveMousePressCallback,
std::function inSharpFlatMousePressCallback,
std::function inSIDMousePressCallback,
+ std::function inOuputDevicePressCallback,
std::function inContextHighlightMousePressCallback,
std::function inFollowPlayerMousePressCallback
);
@@ -48,10 +51,12 @@ namespace Editor
std::shared_ptr m_TextSectionSID;
std::shared_ptr m_TextSectionContextHighlight;
std::shared_ptr m_TextSectionFollowPlay;
+ std::shared_ptr m_TextSectionOutputDevice;
const EditState& m_EditState;
const AuxilaryDataCollection& m_AuxilaryDataPlayMarkers;
const DriverState& m_DriverState;
+ const Emulation::ExecutionHandler& m_ExecutionHandler;
EditState m_CachedEditState;
DriverState m_CachedDriverState;
@@ -59,5 +64,6 @@ namespace Editor
AuxilaryDataEditingPreferences::NotationMode m_CachedNotationMode;
AuxilaryDataHardwarePreferences::SIDModel m_CachedSIDModel;
AuxilaryDataHardwarePreferences::Region m_CachedRegion;
+ Emulation::ExecutionHandler::OutputDevice m_CachedOutputDevice;
};
-}
\ No newline at end of file
+}
diff --git a/SIDFactoryII/source/runtime/emulation/asid/asid.cpp b/SIDFactoryII/source/runtime/emulation/asid/asid.cpp
new file mode 100644
index 00000000..686a6f02
--- /dev/null
+++ b/SIDFactoryII/source/runtime/emulation/asid/asid.cpp
@@ -0,0 +1,256 @@
+#include "asid.h"
+
+#include "libraries/rtmidi/RtMidi.h"
+
+#include
+#include
+
+namespace Emulation
+{
+ ASid::ASid(RtMidiOut* inRtMidiOut)
+ : m_RtMidiOut(inRtMidiOut)
+ {
+ using namespace Utility;
+
+ // Reset the ASID buffer
+ for (unsigned int i = 0; i < ASID_NUM_REGS; ++i)
+ {
+ m_ASIDRegisterBuffer[i] = 0;
+ m_ASIDRegisterUpdated[i] = false;
+ }
+ }
+
+
+ bool ASid::isPortOpen()
+ {
+ return m_RtMidiOut->isPortOpen();
+ }
+
+ void ASid::SetMuted(bool inMuted)
+ {
+ if(inMuted == m_Muted)
+ return;
+
+ if(inMuted)
+ SendSetChannelsSilent();
+
+ m_Muted = inMuted;
+ }
+
+ void ASid::SendSIDRegisterWriteOrderAndCycleInfo(std::vector inSIDWriteInfoList)
+ {
+ #define WRITE_ORDER_UNUSED 0xff
+
+ struct write_order
+ {
+ unsigned char ucIndex;
+ unsigned char ucCycles;
+ };
+
+ // Physical out buffer, including protocol overhead
+ unsigned char ASidOutBuffer[ASID_NUM_REGS*2+4];
+
+ // Write order list, initialized with all slots unused
+ std::array WriteOrder;
+ WriteOrder.fill({0, WRITE_ORDER_UNUSED});
+
+ // Get the write order from the analysis, for all the three voices
+ int index = 0;
+ int PreviousSidRegister = -1;
+
+ for (int voice = 0; voice < 3 ; ++voice)
+ {
+ unsigned char PreviousCycleOffset = 0;
+ for (const auto& SIDWriteInfo : inSIDWriteInfoList)
+ {
+ unsigned char ucASIDreg;
+ ucASIDreg = GetASIDPositionFromRegisterIndex((SIDWriteInfo.m_AddressLow & 0xff) + 7 * voice);
+ WriteOrder[ucASIDreg].ucIndex = index;
+
+ // The cycles are specified as "post wait times", so will be used on the next cycle value
+ if (PreviousSidRegister >= 0)
+ {
+ WriteOrder[PreviousSidRegister].ucCycles = SIDWriteInfo.m_CycleOffset - PreviousCycleOffset;
+ PreviousCycleOffset = SIDWriteInfo.m_CycleOffset;
+ }
+
+ PreviousSidRegister = ucASIDreg;
+ ++index;
+ }
+ }
+
+ // Last register does not need to wait any cycles
+ WriteOrder[PreviousSidRegister].ucCycles = 0;
+
+ // Add the remaining registers last, without waits
+ for (auto& wo : WriteOrder)
+ {
+ if (wo.ucCycles == WRITE_ORDER_UNUSED)
+ {
+ wo.ucIndex = index++;
+ wo.ucCycles = 0;
+ }
+ }
+
+ // Sysex start data for an ASID message
+ index = 0;
+
+ ASidOutBuffer[index++] = 0xf0;
+ ASidOutBuffer[index++] = 0x2d;
+ ASidOutBuffer[index++] = 0x30; // Write order
+
+ // Fill in the write order payload. Only 7 bits may be used in MIDI
+ for (auto& wo : WriteOrder)
+ {
+ // Index serves double duty as index (5 bits) and msb (1 bit) of cycles
+ ASidOutBuffer[index++] = (wo.ucIndex & 0x1f) + ((wo.ucCycles & 0x80) >> 1);
+ ASidOutBuffer[index++] = wo.ucCycles & 0x7f;
+ }
+
+ // Sysex end marker
+ ASidOutBuffer[index++] = 0xf7;
+
+ // Send to physical MIDI port
+ if (m_RtMidiOut->isPortOpen())
+ m_RtMidiOut->sendMessage(ASidOutBuffer, index);
+ }
+
+ void ASid::WriteToSIDRegister(unsigned char inSidReg, unsigned char inData)
+ {
+ if (m_Muted)
+ return;
+ if (inSidReg > 0x18)
+ return;
+
+ // Get the ASID transformed register
+ unsigned char MappedAddress = GetASIDPositionFromRegisterIndex(inSidReg);
+
+ // If a write occurs to a waveform register, check if first block is already allocated
+ if (MappedAddress >= 0x16 && MappedAddress <= 0x18 && m_ASIDRegisterUpdated[MappedAddress])
+ {
+ MappedAddress += 3;
+
+ // If second block is also updated, move it to the first to make sure to always keep the last one
+ if(m_ASIDRegisterUpdated[MappedAddress])
+ m_ASIDRegisterBuffer[MappedAddress - 3] = m_ASIDRegisterBuffer[MappedAddress];
+ }
+
+ // If we're trying to update a control register that is already mapped, flush it directly
+ if(m_ASIDRegisterUpdated[MappedAddress])
+ {
+ if(MappedAddress >= 0x16)
+ SendToDevice();
+ }
+
+ // Store the data
+ m_ASIDRegisterBuffer[MappedAddress] = inData;
+ m_ASIDRegisterUpdated[MappedAddress] = true;
+ }
+
+ void ASid::SendToDevice()
+ {
+ if(m_Muted)
+ return;
+
+ const bool RequireUpdate = [&RegisterUpdated = m_ASIDRegisterUpdated]()
+ {
+ for (unsigned int i = 0; i < ASID_NUM_REGS; ++i)
+ {
+ if (RegisterUpdated[i])
+ return true;
+ }
+
+ return false;
+ }();
+
+ if (!RequireUpdate)
+ return;
+
+ if (m_RtMidiOut->isPortOpen())
+ {
+ // Sysex start data for an ASID message
+ m_ASIDOutBuffer[0] = 0xf0;
+ m_ASIDOutBuffer[1] = 0x2d;
+ m_ASIDOutBuffer[2] = 0x4e;
+
+ size_t index = 3;
+
+ // Setup mask bytes (one bit per register)
+ unsigned char ucReg;
+
+ for (unsigned char ucMask = 0; ucMask<4; ++ucMask)
+ {
+ ucReg = 0x00;
+ for (unsigned char ucRegOffset = 0; ucRegOffset < 7; ++ucRegOffset)
+ {
+ if (m_ASIDRegisterUpdated[ucMask*7+ucRegOffset])
+ ucReg |= (1<sendMessage(m_ASIDOutBuffer, index);
+ }
+
+ // Prepare for next buffer
+ for (int i = 0; i < ASID_NUM_REGS; ++i)
+ m_ASIDRegisterUpdated[i] = false;
+ }
+
+ void ASid::SendSetChannelsSilent()
+ {
+ for (unsigned int i = 0; i < ASID_NUM_REGS; ++i)
+ {
+ m_ASIDRegisterBuffer[i] = 0;
+ m_ASIDRegisterUpdated[i] = false;
+ }
+
+ for(unsigned int i=0; i<3; ++i)
+ {
+ unsigned char channel_offset = static_cast(i * 7);
+
+ WriteToSIDRegister(0x04 + channel_offset, 0);
+ WriteToSIDRegister(0x05 + channel_offset, 0);
+ WriteToSIDRegister(0x06 + channel_offset, 0);
+ }
+
+ SendToDevice();
+ }
+
+ unsigned char ASid::GetASIDPositionFromRegisterIndex(unsigned char inSidRegister)
+ {
+ // Conversion between SID register and ASID position
+ static const unsigned char SIDRegisterMap[] =
+ {
+ 0x00, 0x01, 0x02, 0x03, 0x16, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x17, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x18, 0x10, 0x11,
+ 0x12, 0x13, 0x14, 0x15, 0x19, 0x1a, 0x1b
+ };
+
+ return SIDRegisterMap[inSidRegister];
+ }
+}
diff --git a/SIDFactoryII/source/runtime/emulation/asid/asid.h b/SIDFactoryII/source/runtime/emulation/asid/asid.h
new file mode 100644
index 00000000..a3ff485e
--- /dev/null
+++ b/SIDFactoryII/source/runtime/emulation/asid/asid.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "runtime/editor/driver/driver_utils.h"
+#include
+
+#define ASID_NUM_REGS 28
+
+class RtMidiOut;
+
+namespace Emulation
+{
+ class ASid
+ {
+ public:
+ ASid(RtMidiOut* inRtMidiOut);
+
+ bool isPortOpen();
+ void SetMuted(bool inMuted);
+
+ void SendSIDRegisterWriteOrderAndCycleInfo(std::vector inSIDWriteInfoList);
+ void WriteToSIDRegister(unsigned char inSidReg, unsigned char inData);
+ void SendToDevice();
+
+ private:
+ void SendSetChannelsSilent();
+ unsigned char GetASIDPositionFromRegisterIndex(unsigned char inSidRegister);
+
+ bool m_Muted = false;
+ RtMidiOut* m_RtMidiOut = nullptr;
+
+ // Physical out buffer, including protocol overhead
+ unsigned char m_ASIDOutBuffer[ASID_NUM_REGS + 12];
+
+ // Registers
+ unsigned char m_ASIDRegisterBuffer[ASID_NUM_REGS];
+ bool m_ASIDRegisterUpdated[ASID_NUM_REGS];
+ };
+}
diff --git a/SIDFactoryII/source/runtime/emulation/cpumemory.h b/SIDFactoryII/source/runtime/emulation/cpumemory.h
index 072fa30c..5545d532 100644
--- a/SIDFactoryII/source/runtime/emulation/cpumemory.h
+++ b/SIDFactoryII/source/runtime/emulation/cpumemory.h
@@ -10,11 +10,16 @@ namespace Emulation
{
class IPlatformFactory;
- class CPUMemory : public IMemoryRandomReadAccess
+ class CPUMemory final : public IMemoryRandomReadAccess
{
public:
CPUMemory(unsigned int inSize, Foundation::IPlatform* inPlatform);
- ~CPUMemory();
+ virtual ~CPUMemory();
+
+ CPUMemory(const CPUMemory&) = delete;
+ CPUMemory& operator=(const CPUMemory&) = delete;
+ CPUMemory(CPUMemory&&) = delete;
+ CPUMemory& operator=(CPUMemory&&) = delete;
const unsigned char& operator[](int inAddress) const override
{
diff --git a/SIDFactoryII/source/runtime/execution/executionhandler.cpp b/SIDFactoryII/source/runtime/execution/executionhandler.cpp
index a28c2957..1684894f 100644
--- a/SIDFactoryII/source/runtime/execution/executionhandler.cpp
+++ b/SIDFactoryII/source/runtime/execution/executionhandler.cpp
@@ -1,5 +1,6 @@
#include "executionhandler.h"
+#include "libraries/residfp/siddefs-fp.h"
#include "runtime/emulation/cpuframecapture.h"
#include "runtime/emulation/cpumos6510.h"
#include "runtime/emulation/sid/sidproxy.h"
@@ -10,11 +11,13 @@
#include "foundation/base/assert.h"
#include "foundation/platform/imutex.h"
#include "foundation/platform/iplatform.h"
+#include "runtime/emulation/asid/asid.h"
#include "utils/configfile.h"
#include "utils/logging.h"
#include "utils/global.h"
+#include
#include
#include